fix(e2e): reject loose credential numeric limits

This commit is contained in:
Vincent Koc
2026-06-18 20:50:45 +02:00
parent 7b7e40cb0e
commit a2f5ac82d5
2 changed files with 44 additions and 8 deletions

View File

@@ -23,18 +23,22 @@ const DEFAULT_CHUNKED_PAYLOAD_MAX_CHUNKS = 4096;
const COMMAND_TIMEOUT_MS = optionalPositiveInteger(
process.env.OPENCLAW_TELEGRAM_USER_CREDENTIAL_COMMAND_TIMEOUT_MS?.trim(),
120_000,
"OPENCLAW_TELEGRAM_USER_CREDENTIAL_COMMAND_TIMEOUT_MS",
);
const BROKER_TIMEOUT_MS = optionalPositiveInteger(
process.env.OPENCLAW_TELEGRAM_USER_CREDENTIAL_BROKER_TIMEOUT_MS?.trim(),
30_000,
"OPENCLAW_TELEGRAM_USER_CREDENTIAL_BROKER_TIMEOUT_MS",
);
const CHUNKED_PAYLOAD_MAX_BYTES = optionalPositiveInteger(
process.env.OPENCLAW_QA_CREDENTIAL_PAYLOAD_MAX_BYTES?.trim(),
DEFAULT_CHUNKED_PAYLOAD_MAX_BYTES,
"OPENCLAW_QA_CREDENTIAL_PAYLOAD_MAX_BYTES",
);
const CHUNKED_PAYLOAD_MAX_CHUNKS = optionalPositiveInteger(
process.env.OPENCLAW_QA_CREDENTIAL_PAYLOAD_MAX_CHUNKS?.trim(),
DEFAULT_CHUNKED_PAYLOAD_MAX_CHUNKS,
"OPENCLAW_QA_CREDENTIAL_PAYLOAD_MAX_CHUNKS",
);
function usage(): never {
@@ -156,16 +160,17 @@ function optionalString(source: JsonObject, key: string) {
return undefined;
}
function optionalPositiveInteger(value: string | undefined, fallback: number) {
if (!value) {
function optionalPositiveInteger(value: string | undefined, fallback: number, label = "value") {
const text = value?.trim();
if (!text) {
return fallback;
}
if (!/^\d+$/u.test(value)) {
throw new Error(`Expected positive integer, got ${value}.`);
if (!/^\d+$/u.test(text)) {
throw new Error(`${label} must be a positive integer. Got: ${JSON.stringify(text)}.`);
}
const parsed = Number(value);
const parsed = Number(text);
if (!Number.isSafeInteger(parsed) || parsed < 1) {
throw new Error(`Expected positive integer, got ${value}.`);
throw new Error(`${label} must be a positive integer. Got: ${JSON.stringify(text)}.`);
}
return parsed;
}
@@ -256,12 +261,14 @@ async function resolveConvexLeaseConfig(opts: Map<string, string>) {
process.env.OPENCLAW_QA_CREDENTIAL_LEASE_TTL_MS?.trim() ||
fileEnv.OPENCLAW_QA_CREDENTIAL_LEASE_TTL_MS,
20 * 60 * 1_000,
"OPENCLAW_QA_CREDENTIAL_LEASE_TTL_MS",
),
heartbeatIntervalMs: optionalPositiveInteger(
opts.get("heartbeat-interval-ms") ||
process.env.OPENCLAW_QA_CREDENTIAL_HEARTBEAT_INTERVAL_MS?.trim() ||
fileEnv.OPENCLAW_QA_CREDENTIAL_HEARTBEAT_INTERVAL_MS,
30_000,
"OPENCLAW_QA_CREDENTIAL_HEARTBEAT_INTERVAL_MS",
),
ownerId:
opts.get("owner-id") ||
@@ -633,4 +640,4 @@ if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href)
await main();
}
export { parseChunkedPayloadMarker };
export { optionalPositiveInteger, parseChunkedPayloadMarker };

View File

@@ -142,7 +142,9 @@ describe("telegram user credential IO", () => {
import(
`${new URL("../../scripts/e2e/telegram-user-credential.ts", import.meta.url).href}?case=loose-timeout-${value}-${Date.now()}`
),
).rejects.toThrow(`Expected positive integer, got ${value}.`);
).rejects.toThrow(
`OPENCLAW_TELEGRAM_USER_CREDENTIAL_COMMAND_TIMEOUT_MS must be a positive integer. Got: ${JSON.stringify(value)}.`,
);
}
} finally {
if (previous === undefined) {
@@ -176,6 +178,33 @@ describe("telegram user credential IO", () => {
).toThrow("Chunked payload marker exceeds 67108864 bytes.");
});
it("rejects loose numeric credential limits instead of parsing prefixes", async () => {
const credentialModule = (await import(
`${new URL("../../scripts/e2e/telegram-user-credential.ts", import.meta.url).href}?case=limits-${Date.now()}`
)) as {
optionalPositiveInteger(value: string | undefined, fallback: number, label?: string): number;
};
expect(credentialModule.optionalPositiveInteger(undefined, 30_000)).toBe(30_000);
expect(credentialModule.optionalPositiveInteger(" 120000 ", 30_000)).toBe(120_000);
expect(() =>
credentialModule.optionalPositiveInteger(
"1e3",
30_000,
"OPENCLAW_QA_CREDENTIAL_LEASE_TTL_MS",
),
).toThrow('OPENCLAW_QA_CREDENTIAL_LEASE_TTL_MS must be a positive integer. Got: "1e3".');
expect(() =>
credentialModule.optionalPositiveInteger(
"9007199254740992",
30_000,
"OPENCLAW_QA_CREDENTIAL_PAYLOAD_MAX_BYTES",
),
).toThrow(
'OPENCLAW_QA_CREDENTIAL_PAYLOAD_MAX_BYTES must be a positive integer. Got: "9007199254740992".',
);
});
it("fails hung child processes instead of waiting for the outer proof timeout", async () => {
await expect(
runCommand(process.execPath, ["-e", "setInterval(() => {}, 1000)"], undefined, {