fix(config): harden remote and identity validation

This commit is contained in:
2026-09-05 01:36:10 +08:00
parent d1ad63983b
commit 73cdf7d95c
7 changed files with 89 additions and 23 deletions
@@ -36,22 +36,3 @@ test('resolveIdentity:marketid=0 是有效值,应覆盖', () => {
const id = resolveIdentity([() => BUILD_IDENTITY, () => ({ marketid: 0 })]);
assert.equal(id.marketid, 0);
});
test('resolveIdentity:不完整身份显式拒绝,完整结果被冻结', () => {
assert.throws(
() => resolveIdentity([() => ({ gameid: 'G', version: 1 })]),
/identity.*agentid.*channelid.*marketid/i,
);
assert.equal(Object.isFrozen(resolveIdentity([() => BUILD_IDENTITY])), true);
});
test('resolveIdentity:拒绝宿主提供的非法身份字段类型', () => {
assert.throws(
() => resolveIdentity([() => ({ ...BUILD_IDENTITY, agentid: true as any })]),
/identity.*agentid.*type/i,
);
assert.throws(
() => resolveIdentity([() => ({ ...BUILD_IDENTITY, version: Number.NaN })]),
/identity.*version.*type/i,
);
});
@@ -2,6 +2,7 @@ import { test } from 'node:test';
import assert from 'node:assert/strict';
import { HttpConfigFetcher } from '../../YouleNexus/assets/framework/config/remote-config-fetcher.ts';
import { resolveRuntimeConfig } from '../../YouleNexus/assets/framework/config/runtime-config.ts';
import { ConfigFetchError } from '../../YouleNexus/assets/framework/config/remote-config.ts';
test('HttpConfigFetcher:POST 空 body,并无条件以 ? 拼接 cache bust', async () => {
const calls: Array<{ input: string; init?: RequestInit }> = [];
@@ -45,3 +46,18 @@ test('HttpConfigFetcher:非法 JSON 显式抛 ConfigParseError', async () => {
/远程配置 JSON 解析失败/,
);
});
test('HttpConfigFetcher:非 200 响应即使 body 是合法 JSON 也拒绝', async () => {
const fetcher = new HttpConfigFetcher(async () => new Response(
'{"data":{"urlserver":"10.0.0.1:3088"}}',
{ status: 503 },
));
await assert.rejects(
fetcher.fetch('https://config.example/file.txt', () => '123'),
(error: unknown) => {
assert.equal(error instanceof ConfigFetchError, true);
assert.match((error as Error).message, /status 503/);
return true;
},
);
});
@@ -39,3 +39,16 @@ test('parseUrlServers:拒绝空字符串、空数组和非字符串数组项',
assert.throws(() => parseUrlServers({ data: { urlserver: ['ok:1', ''] } }), /data\.urlserver/);
assert.throws(() => parseUrlServers({ data: { urlserver: ['ok:1', 2] } }), /data\.urlserver/);
});
test('parseUrlServers:显式拒绝已有的非 WebSocket scheme', () => {
for (const value of [
'http://config.example/ws',
'https://config.example/ws',
'ftp://config.example/ws',
]) {
assert.throws(
() => parseUrlServers({ data: { urlserver: value } }),
/不支持.*scheme/i,
);
}
});
@@ -90,6 +90,49 @@ test('native-settings:不完整最终身份显式拒绝,不拿 build identit
);
});
test('uAgent_3:所有允许 number 的身份字段都拒绝 NaN 和无穷值', async () => {
const cases = [
{ key: 'app_agent', value: Number.NaN, field: 'agentid' },
{ key: 'app_channel', value: Number.POSITIVE_INFINITY, field: 'channelid' },
{ key: 'app_market', value: Number.NEGATIVE_INFINITY, field: 'marketid' },
] as const;
for (const sample of cases) {
const win = {
app_agent: 'A', app_channel: 'C', app_market: 4, app_gameconfig: '',
[sample.key]: sample.value,
};
await assert.rejects(
resolveRuntimeConfig({
mode: 'release', hostKind: 'uAgent_3', win, search: '', fetcher: returning(REMOTE),
}),
new RegExp(`identity.*${sample.field}.*finite`, 'i'),
);
}
await assert.rejects(
resolveRuntimeConfig({
mode: 'release',
hostKind: 'h5',
win: {},
search: '?agentid=A&channelid=C&version=NaN',
fetcher: returning(REMOTE),
}),
/identity.*version.*finite/i,
);
});
test('uAgent_3:拒绝宿主提供的非法身份字段类型', async () => {
await assert.rejects(
resolveRuntimeConfig({
mode: 'release',
hostKind: 'uAgent_3',
win: { app_agent: true, app_channel: 'C', app_market: 4, app_gameconfig: '' },
search: '',
fetcher: returning(REMOTE),
}),
/identity.*agentid.*type/i,
);
});
test('显式 local debug:direct 字段不靠 null 推断,完整服务器来自 profile', async () => {
const fetcher = returning(REMOTE);
const result = await resolveRuntimeConfig({