Compare commits

...

1 Commits

Author SHA1 Message Date
Xiangzhe
ab36b35035 fix(security): sanitize test regex and annotate CodeQL hash false-positives
tests/unit/early-sse-route-intent.test.ts built a RegExp from a hardcoded
string but only escaped `?`/`.`, missing `\` — js/incomplete-sanitization
(#816). Not exploitable (fixed literal input) but the escaping was
genuinely incomplete; now escapes backslash too.

reasoningCache.ts::buildAssistantMessageCacheKey and codexIdentity.ts's two
UUID derivation helpers hash a cache-scope/account-seed with SHA-256 to
produce a lookup key / deterministic ID — not a stored, verified password.
CodeQL's js/insufficient-password-hash overfires on any hash of a
secret-like variable, the same false-positive class already annotated at
src/lib/db/apiKeys.ts:624. Added matching lgtm/nosemgrep annotations and
inline rationale so the intent is clear to reviewers and future scans.

Refs #815 #816 #817 #818
2026-08-14 10:07:07 -03:00
3 changed files with 21 additions and 6 deletions

View File

@@ -37,15 +37,26 @@ function nonEmptyString(value: unknown): string | null {
return normalized || null;
}
/** Keep the historical installation-id layout so existing accounts stay stable. */
/**
* Keep the historical installation-id layout so existing accounts stay stable.
* CodeQL: not password hashing — this derives a deterministic installation UUID
* from an account seed, never verified against a stored credential. Same
* false-positive class as src/lib/db/apiKeys.ts::hashKey.
* lgtm[js/insufficient-password-hash]
*/
function uuidFromLegacyInstallationValue(value: string): string {
const hash = createHash("sha256").update(value).digest("hex");
const hash = createHash("sha256").update(value).digest("hex"); // nosemgrep: insufficient-password-hash
return `${hash.slice(0, 8)}-${hash.slice(8, 12)}-4${hash.slice(13, 16)}-a${hash.slice(17, 20)}-${hash.slice(20, 32)}`;
}
/** RFC4122 v4 from SHA-256. Same seed → same UUID. */
/**
* RFC4122 v4 from SHA-256. Same seed → same UUID.
* CodeQL: not password hashing — deterministic ID derivation from an account
* seed, never verified against a stored credential.
* lgtm[js/insufficient-password-hash]
*/
export function deriveStableUUIDv4(seed: string): string {
const digest = createHash("sha256").update(seed).digest();
const digest = createHash("sha256").update(seed).digest(); // nosemgrep: insufficient-password-hash
const bytes = Buffer.from(digest.subarray(0, 16));
bytes[6] = (bytes[6] & 0x0f) | 0x40;
bytes[8] = (bytes[8] & 0x3f) | 0x80;

View File

@@ -302,7 +302,11 @@ export function buildAssistantMessageCacheKey(
if (!message || message.role !== "assistant") return "";
const transcript = messages.slice(0, messageIndex + 1).map(canonicalizeHistoryMessage);
const digest = createHash("sha256")
// CodeQL: not password hashing — this derives a cache-lookup key from the
// conversation transcript, never verified against a stored credential.
// Same false-positive class as src/lib/db/apiKeys.ts::hashKey.
// lgtm[js/insufficient-password-hash]
const digest = createHash("sha256") // nosemgrep: insufficient-password-hash
.update(normalizedScope)
.update("\x1f")
.update(JSON.stringify(transcript))

View File

@@ -22,7 +22,7 @@ const ROUTES = [
for (const route of ROUTES) {
test(`${route.name} early-heartbeat gate uses the real stream resolver`, () => {
const escapedBodyExpression = route.bodyExpression.replace(/[?.]/g, "\\$&");
const escapedBodyExpression = route.bodyExpression.replace(/[.?\\]/g, "\\$&");
assert.match(
route.source,
new RegExp(