本地 dev 不再信任默认 CLI 配置里的远程 token #526

Closed
suzmii wants to merge 1 commits from fix/dev-loopback-token-trust into master
2 changed files with 90 additions and 10 deletions
+37 -1
View File
@@ -3259,19 +3259,54 @@ function shouldTrustExistingSpacetimeToken(
return false;
}
// 本地 standalone 只认自己签发的 token:远程账号的 token(默认 CLI 配置里那种)
// 形如有效但验签必然失败,会在 pre-publish check 上抛 401 InvalidSize。
// 因此先按签发者判断,再做「与环境/CLI 一致」这类弱判断。
const shellToken = String(env.GENARRATIVE_SPACETIME_TOKEN ?? '').trim();
if (shellToken && shellToken === normalizedToken) {
// 显式覆盖(运维手动指定)优先,保持既有语义。
return true;
}
if (!isLoopbackSpacetimeServer(serverUrl)) {
const loopback = isLoopbackSpacetimeServer(serverUrl);
if (!loopback) {
return true;
}
// 本地 standalone 只认自己签发的 token:默认 CLI 配置里那枚远程账号 token
// 形如有效但验签必然失败(pre-publish check 401 InvalidSignature),不能信任。
if (!isLocallyIssuedSpacetimeToken(normalizedToken)) {
return false;
}
const cliToken = resolveCliToken();
return Boolean(cliToken && cliToken === normalizedToken);
}
/// JWT 的 iss 是否指向本机服务器(localhost/127.0.0.1/::1 或 *.localhost)。
function isLocallyIssuedSpacetimeToken(token) {
try {
const payload = String(token).split('.')[1];
if (!payload) {
return false;
}
const claims = JSON.parse(
Buffer.from(payload, 'base64url').toString('utf8'),
);
const issuer = String(claims.iss ?? '').trim();
if (issuer === 'localhost' || issuer.endsWith('.localhost')) {
return true;
}
if (!issuer) {
return false;
}
const url = new URL(issuer.includes('://') ? issuer : `http://${issuer}`);
return ['127.0.0.1', 'localhost', '::1'].includes(url.hostname);
} catch {
return false;
}
}
function isLoopbackSpacetimeServer(serverUrl) {
try {
const url = new URL(serverUrl);
@@ -3544,6 +3579,7 @@ export {
createWatchConfigs,
DevRunner,
isDirectModuleExecution,
isLocallyIssuedSpacetimeToken,
isSpacetimePublishPermissionError,
isStaleExternalGenerationWorkerProcess,
normalizeCargoVersionRequirement,
+53 -9
View File
@@ -30,6 +30,7 @@ import {
createWatchConfigs,
DevRunner,
isDirectModuleExecution,
isLocallyIssuedSpacetimeToken,
isSpacetimePublishPermissionError,
isStaleExternalGenerationWorkerProcess,
normalizeCargoVersionRequirement,
@@ -44,6 +45,16 @@ import {
shouldTrustExistingSpacetimeToken,
} from './dev.mjs';
/// 造一个带指定 iss 的假 JWT(只用于断言签发者判定)。
const fakeJwt = (issuer: string): string =>
[
Buffer.from(JSON.stringify({ alg: 'none' })).toString('base64url'),
Buffer.from(JSON.stringify({ iss: issuer, sub: 'fake' })).toString(
'base64url',
),
'signature',
].join('.');
const originalFetch = globalThis.fetch;
afterEach(() => {
@@ -336,16 +347,13 @@ describe('dev scheduler argument routing', () => {
}
});
test('本地 SpacetimeDB 信任与当前 CLI 一致的 env token', () => {
test('本地 SpacetimeDB 信任与当前 CLI 一致且由本机签发的 token', () => {
const localToken = fakeJwt('localhost');
expect(
shouldTrustExistingSpacetimeToken(
'owner-cli-token',
'http://127.0.0.1:3101',
{
env: {},
resolveCliToken: () => 'owner-cli-token',
},
),
shouldTrustExistingSpacetimeToken(localToken, 'http://127.0.0.1:3101', {
env: {},
resolveCliToken: () => localToken,
}),
).toBe(true);
});
});
@@ -1834,3 +1842,39 @@ spacetimedb tool version 2.8.3; spacetimedb-lib version 2.8.3;
}
});
});
describe('本地 token 信任判定', () => {
it('拒绝远程账号签发的 token 用于本地服务器(曾经的 401 InvalidSignature)', () => {
expect(
isLocallyIssuedSpacetimeToken(fakeJwt('https://api.spacetimedb.com')),
).toBe(false);
expect(isLocallyIssuedSpacetimeToken('not-a-jwt')).toBe(false);
expect(
shouldTrustExistingSpacetimeToken(
fakeJwt('https://api.spacetimedb.com'),
'http://127.0.0.1:3101',
{
env: {},
resolveCliToken: () => fakeJwt('https://api.spacetimedb.com'),
},
),
).toBe(false);
});
it('接受本机签发的 token', () => {
expect(isLocallyIssuedSpacetimeToken(fakeJwt('localhost'))).toBe(true);
expect(
isLocallyIssuedSpacetimeToken(fakeJwt('http://127.0.0.1:3101')),
).toBe(true);
expect(
shouldTrustExistingSpacetimeToken(
fakeJwt('localhost'),
'http://127.0.0.1:3101',
{
env: {},
resolveCliToken: () => fakeJwt('localhost'),
},
),
).toBe(true);
});
});