Compare commits

...

1 Commits

Author SHA1 Message Date
Markus Hartung
2f9d9b4a2c fix(security): clear new CodeQL code-scanning alerts (round 4)
- open-sse/executors/github.ts: replace the Math.random() fallback in
  the Copilot correlation-id generators (x-request-id,
  x-interaction-id, x-client-session-id, x-agent-task-id) with a
  CSPRNG-backed randomIdFallback() (node:crypto randomBytes) — closes
  js/insecure-randomness with no behavior change (crypto.randomUUID
  stays the primary path).
- tests/unit/cli/_helpers/shellArgs.mjs: collapse the two sequential
  global .replace() unescape passes into a single left-to-right regex
  replace with alternation — closes js/double-escaping. The prior
  two-pass form let the first pass's output feed the second, which is
  exactly the double-(un)escaping bug pattern the query flags (e.g. an
  escaped-backslash-then-quote sequence could be misread depending on
  pass order).
2026-08-23 19:00:21 -03:00
2 changed files with 14 additions and 4 deletions

View File

@@ -1,3 +1,5 @@
import { randomBytes } from "node:crypto";
import { import {
BaseExecutor, BaseExecutor,
ExecuteInput, ExecuteInput,
@@ -13,6 +15,11 @@ import {
import { sanitizeResponsesInputItems } from "../services/responsesInputSanitizer.ts"; import { sanitizeResponsesInputItems } from "../services/responsesInputSanitizer.ts";
import { stripUnsupportedParams } from "../translator/paramSupport.ts"; import { stripUnsupportedParams } from "../translator/paramSupport.ts";
/** Correlation-id fallback for runtimes without crypto.randomUUID — still CSPRNG-backed. */
function randomIdFallback(): string {
return `${Date.now()}-${randomBytes(9).toString("hex")}`;
}
/** /**
* What a Copilot credential refresh resolves to. * What a Copilot credential refresh resolves to.
* *
@@ -329,7 +336,7 @@ export class GithubExecutor extends BaseExecutor {
...getGitHubCopilotChatHeaders(stream ? "text/event-stream" : "application/json", initiator), ...getGitHubCopilotChatHeaders(stream ? "text/event-stream" : "application/json", initiator),
Authorization: `Bearer ${token}`, Authorization: `Bearer ${token}`,
"x-request-id": "x-request-id":
crypto.randomUUID?.() || `${Date.now()}-${Math.random().toString(36).slice(2)}`, crypto.randomUUID?.() || randomIdFallback(),
}; };
// Per-call / per-conversation / per-turn correlation ids the @github/copilot // Per-call / per-conversation / per-turn correlation ids the @github/copilot
@@ -338,7 +345,7 @@ export class GithubExecutor extends BaseExecutor {
// fresh uuids. A Copilot-aware client may pin the session/task ids across a // fresh uuids. A Copilot-aware client may pin the session/task ids across a
// conversation via its own headers — honor those when present, else mint. // conversation via its own headers — honor those when present, else mint.
const genId = () => const genId = () =>
crypto.randomUUID?.() || `${Date.now()}-${Math.random().toString(36).slice(2)}`; crypto.randomUUID?.() || randomIdFallback();
headers["x-interaction-id"] = this.readClientHeader(clientHeaders, "x-interaction-id") || genId(); headers["x-interaction-id"] = this.readClientHeader(clientHeaders, "x-interaction-id") || genId();
headers["x-client-session-id"] = headers["x-client-session-id"] =
this.readClientHeader(clientHeaders, "x-client-session-id") || genId(); this.readClientHeader(clientHeaders, "x-client-session-id") || genId();

View File

@@ -23,8 +23,11 @@ export function unescapeWindowsShellArg(arg) {
s = s.replace(/\^(.)/g, "$1").replace(/\^(.)/g, "$1"); s = s.replace(/\^(.)/g, "$1").replace(/\^(.)/g, "$1");
// 2. drop the wrapping quotes added by the CRT argv layer // 2. drop the wrapping quotes added by the CRT argv layer
if (s.length >= 2 && s.startsWith('"') && s.endsWith('"')) s = s.slice(1, -1); if (s.length >= 2 && s.startsWith('"') && s.endsWith('"')) s = s.slice(1, -1);
// 3. undo the doubled backslashes and the escaped embedded quotes // 3. undo the doubled backslashes and the escaped embedded quotes in a single
s = s.replace(/\\\\/g, "\\").replace(/\\"/g, '"'); // left-to-right pass — two sequential global replaces would let the first
// pass's output feed the second (e.g. an escaped-backslash-then-quote
// sequence could be misread), which is exactly what js/double-escaping flags.
s = s.replace(/\\\\|\\"/g, (m) => (m === "\\\\" ? "\\" : '"'));
return s; return s;
} }