Skip to content

CodeQL baseline triage: 28 mitigated/FP, ~5 genuine low/hardening (no real critical/high) #482

Description

@initializ-mk

CodeQL baseline triage

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.

Disposition summary

Rule # alerts Disposition
go/request-forgery (STS) 1 (critical) ✅ Mitigated
go/path-injection (memory_store ×9, loop.go ×1) 10 ✅ Mitigated
go/clear-text-logging 7 ✅ False positive
go/allocation-size-overflow 9 ✅ False positive
go/weak-sensitive-data-hashing 1 ✅ False positive
go/reflected-xss (oauth callback) 1 ⚠️ Real (low) — fix
go/path-injection (skill_validator) 1 ⚠️ Real (low) — fix
actions/missing-workflow-permissions 1 ⚠️ Real (hardening) — fix
go/bad-redirect-check + js/incomplete-url-scheme-check (browser tool) 2 🔎 Examine (likely low)

✅ 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 ParseToken host-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)

  1. 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.
  2. 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.
  3. 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).
  4. 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.
  • go/command-injection on tools/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 — bare forge builder + Claude Code launcher #477).

Recommended actions

  1. Fix the 3–4 genuine low items above (XSS escape, meta.Name validation, workflow permissions, browser-URL check).
  2. 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>").
  3. Consider adding CodeQL suppressions or a baseline so these don't re-flag every PR, and so the required check goes green (currently red purely from this baseline — it should not block feature PRs like feat(cli): interactive forge surface — bare forge builder + Claude Code launcher #477).

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions