You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
CodeQL was first enabled on main on 2026-09-18, so the initial run surfaced the entire pre-existing codebase as "new" (33 alerts on main; the PR-head analysis on #477 additionally surfaced the builtin file/HTTP/shell tools — same classes, dispositioned at the end). This issue triages every alert so the team can bulk-dismiss the noise with justifications and fix the genuine handful.
Headline: no genuine critical/high survives. The one critical (STS request-forgery) is correctly mitigated. The rest are CodeQL not modelling forge's sanitizers / egress enforcer / host-gates, plus 5 genuine but low-severity / hardening items.
✅ Mitigated / false positive (28) — dismiss with justification
go/request-forgery — aws_sigv4/sts_client.go:74 (CRITICAL).STSClient.GetCallerIdentity GETs a caller-supplied presignedURL, but sigv4_parser.go ParseTokenhost-gates it to exactly sts.<region>.amazonaws.com (with the user:pass@host smuggling case handled) and requires Action=GetCallerIdentity + https, before the fetch. CodeQL can't connect the cross-function validation. The SSRF is closed. → Dismiss "used in tests"/won't-fix: host-allowlisted upstream.
go/path-injection — memory_store.go (9) + loop.go:1136 (1).memory_store.filename() runs sanitizeTaskID(taskID) before filepath.Join; loop.go reads via confinedFilesPath(ctx, fc.Path) (confined to the agent files dir). Both confine before the sink. → Dismiss: sanitized/confined.
go/clear-text-logging (7) — optimizer telemetry.go, inband_expand.go, server.go, and init.go. These log cid/stage/error message/content hash/token counts / a WhatsApp-relocation error string — no keys, tokens, or prompt content (verified in the feat(optimizer): context-compression + memory optimizer for Claude Code #466 review: optimizer logging is hash+metadata only). → Dismiss: non-sensitive metadata.
go/allocation-size-overflow (9) — skill_env.go, runner.go:3340, logger.go, analyzer/*. All are make([]T, 0, len(slice)+…) or bounded counts derived from already-in-memory local content, not an attacker-controlled wire integer. (Contrast the genuine wire-uint32 case fixed in feat(llm): native AWS Bedrock Converse provider (#205) #461 — that one was real; these are not.) → Dismiss: bounded local sizes.
go/weak-sensitive-data-hashing — gateway_credential.go:76. SHA-256 here builds GatewayCredKey — a content-addressed, filesystem-safe cache key for the gateway token store, not password/credential-at-rest hashing (reviewed in feat(settings): local-dev model-gateway api_key_helper overlay + login gate (#455 slice 1) #464). The token itself is stored encrypted/0600 via oauth.SaveCredentials. bcrypt/scrypt would be nonsensical for a lookup key. → Dismiss: not a password hash.
⚠️ Real — fix (all low-severity / hardening)
go/reflected-xss — forge-core/llm/oauth/server.go:87. The OAuth callback handler reflects error_description (an untrusted query param) into HTML unescaped: fmt.Fprintf(w, "<...><p>%s</p>...", desc). Genuine reflected XSS. Bounded (loopback 127.0.0.1 callback server, live only during the brief OAuth window), so low practical severity — but the fix is trivial: html.EscapeString(desc) (and the same for any other reflected query.Get(...) written to the page). Fix.
go/path-injection — forge-ui/skill_validator.go:130.skillDir := filepath.Join(agentDir, "skills", meta.Name) joins an unsanitized frontmatter name and os.Stats it. Impact is limited to a stat (existence probe → a uniqueness warning), and meta.Name is the operator's own local skill file, so low — but a name: ../../x would stat outside agentDir. Fix: validate meta.Name is a plain slug (^[a-z0-9-]+$) before the join.
actions/missing-workflow-permissions — .github/workflows/notify-docs.yaml:11. No explicit permissions: block → the job runs with the workflow-default token scope. Fix: add least-privilege permissions: (e.g. contents: read, plus only what the notify step needs).
Browser tool — go/bad-redirect-check (browser/resolve.go) + js/incomplete-url-scheme-check (browser/extract.js:19). URL-handling in the browser automation tool; worth a look for open-redirect / scheme-check completeness. Low; the browser tool is a local-operator dev tool. Examine, harden if confirmed.
PR-head extra set (builtin file/HTTP/shell tools)
The #477 PR-head analysis additionally flagged the agent tool layer — same classes, same dispositions:
go/path-injection on the file tools (file_read/write/patch/edit/create, directory_tree, read_skill, pathutil) → mitigated by PathValidator (all confined to WorkDir).
go/request-forgery on web_fetch.go, http_request.go, webhook_call.go → mitigated by the runtime egress enforcer (requests go through the egress-enforced client / allowlist). CodeQL doesn't model the enforcer.
Bulk-dismiss the ~28 mitigated/FP alerts in the code-scanning UI with the justifications here (mostly "won't fix — mitigated by <sanitizer/egress/host-gate>").
CodeQL baseline triage
CodeQL was first enabled on
mainon 2026-09-18, so the initial run surfaced the entire pre-existing codebase as "new" (33 alerts onmain; the PR-head analysis on #477 additionally surfaced the builtin file/HTTP/shell tools — same classes, dispositioned at the end). This issue triages every alert so the team can bulk-dismiss the noise with justifications and fix the genuine handful.Headline: no genuine critical/high survives. The one critical (STS request-forgery) is correctly mitigated. The rest are CodeQL not modelling forge's sanitizers / egress enforcer / host-gates, plus 5 genuine but low-severity / hardening items.
Disposition summary
go/request-forgery(STS)go/path-injection(memory_store ×9, loop.go ×1)go/clear-text-logginggo/allocation-size-overflowgo/weak-sensitive-data-hashinggo/reflected-xss(oauth callback)go/path-injection(skill_validator)actions/missing-workflow-permissionsgo/bad-redirect-check+js/incomplete-url-scheme-check(browser tool)✅ Mitigated / false positive (28) — dismiss with justification
go/request-forgery—aws_sigv4/sts_client.go:74(CRITICAL).STSClient.GetCallerIdentityGETs a caller-suppliedpresignedURL, butsigv4_parser.go ParseTokenhost-gates it to exactlysts.<region>.amazonaws.com(with theuser:pass@hostsmuggling case handled) and requiresAction=GetCallerIdentity+ https, before the fetch. CodeQL can't connect the cross-function validation. The SSRF is closed. → Dismiss "used in tests"/won't-fix: host-allowlisted upstream.go/path-injection—memory_store.go(9) +loop.go:1136(1).memory_store.filename()runssanitizeTaskID(taskID)beforefilepath.Join;loop.goreads viaconfinedFilesPath(ctx, fc.Path)(confined to the agent files dir). Both confine before the sink. → Dismiss: sanitized/confined.go/clear-text-logging(7) — optimizertelemetry.go,inband_expand.go,server.go, andinit.go. These logcid/stage/error message/content hash/token counts / a WhatsApp-relocation error string — no keys, tokens, or prompt content (verified in the feat(optimizer): context-compression + memory optimizer for Claude Code #466 review: optimizer logging is hash+metadata only). → Dismiss: non-sensitive metadata.go/allocation-size-overflow(9) —skill_env.go,runner.go:3340,logger.go,analyzer/*. All aremake([]T, 0, len(slice)+…)or bounded counts derived from already-in-memory local content, not an attacker-controlled wire integer. (Contrast the genuine wire-uint32case fixed in feat(llm): native AWS Bedrock Converse provider (#205) #461 — that one was real; these are not.) → Dismiss: bounded local sizes.go/weak-sensitive-data-hashing—gateway_credential.go:76. SHA-256 here buildsGatewayCredKey— a content-addressed, filesystem-safe cache key for the gateway token store, not password/credential-at-rest hashing (reviewed in feat(settings): local-dev model-gateway api_key_helper overlay + login gate (#455 slice 1) #464). The token itself is stored encrypted/0600 viaoauth.SaveCredentials. bcrypt/scrypt would be nonsensical for a lookup key. → Dismiss: not a password hash.go/reflected-xss—forge-core/llm/oauth/server.go:87. The OAuth callback handler reflectserror_description(an untrusted query param) into HTML unescaped:fmt.Fprintf(w, "<...><p>%s</p>...", desc). Genuine reflected XSS. Bounded (loopback127.0.0.1callback server, live only during the brief OAuth window), so low practical severity — but the fix is trivial:html.EscapeString(desc)(and the same for any other reflectedquery.Get(...)written to the page). Fix.go/path-injection—forge-ui/skill_validator.go:130.skillDir := filepath.Join(agentDir, "skills", meta.Name)joins an unsanitized frontmatternameandos.Stats it. Impact is limited to a stat (existence probe → a uniqueness warning), andmeta.Nameis the operator's own local skill file, so low — but aname: ../../xwould stat outsideagentDir. Fix: validatemeta.Nameis a plain slug (^[a-z0-9-]+$) before the join.actions/missing-workflow-permissions—.github/workflows/notify-docs.yaml:11. No explicitpermissions:block → the job runs with the workflow-default token scope. Fix: add least-privilegepermissions:(e.g.contents: read, plus only what the notify step needs).go/bad-redirect-check(browser/resolve.go) +js/incomplete-url-scheme-check(browser/extract.js:19). URL-handling in the browser automation tool; worth a look for open-redirect / scheme-check completeness. Low; the browser tool is a local-operator dev tool. Examine, harden if confirmed.PR-head extra set (builtin file/HTTP/shell tools)
The #477 PR-head analysis additionally flagged the agent tool layer — same classes, same dispositions:
go/path-injectionon the file tools (file_read/write/patch/edit/create,directory_tree,read_skill,pathutil) → mitigated byPathValidator(all confined toWorkDir).go/request-forgeryonweb_fetch.go,http_request.go,webhook_call.go→ mitigated by the runtime egress enforcer (requests go through the egress-enforced client / allowlist). CodeQL doesn't model the enforcer.go/command-injectionontools/devtools/local_shell.go→ by-design (the agent's Bash-equivalent tool; unsandboxed local-operator tool, never deployed — same posture reviewed in feat(cli): interactive forge surface — bareforgebuilder + Claude Code launcher #477).Recommended actions
meta.Namevalidation, workflow permissions, browser-URL check).forgebuilder + Claude Code launcher #477).