Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .claude/knowledge/architecture-boundaries.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,9 @@ Key architectural decisions, service boundaries, data flow, integration points,
- **OWASP categories are baked into the rule catalog at var-init time**, not via `init()`. The merged `catalog` var calls `withSecurityOWASP(mergeRuleCatalogs(...))` in `rules/catalog.go`; an `init()` would run *after* the `catalog` var initializer copied stale entries. The mapping lives in `rules/catalog_security_owasp.go`. All read paths (`Catalog`, `RuleCatalogForConfig`, SARIF, CLI) flow through `rules.Catalog()`.
- Config-controlled artifact paths (`baseline.path`, `cache.path`, `ai.cache.path`) are resolved relative to the config dir and contained within it by `config.containConfigArtifactPaths` (in `config/io.go`, `LoadFile`). Paths escaping the config directory are rejected.
- The language-agnostic OWASP-gap line rules (CORS, debug, bind-all, weak-hash/cipher, deserialization, Dockerfile root) live in `checks/security/security_owasp_extra.go` and run per-line via `findingsForFile`. NOTE: `findingsForFile` is the **non-TypeScript** path — `securityTargetFindings` routes TS/JS targets to a separate `typeScriptTargetFindings` pipeline, so these rules do not run on TS/JS targets. String-literal signals (CORS, bind-all) match the raw line; call/identifier signals match the masked line.
- **The MCP server is hand-rolled (no SDK) and transport-agnostic.** Shared JSON-RPC message builders + the synchronous method router live in `internal/cli/mcp_dispatch.go` (`buildResultMessage`/`buildErrorMessage`/`buildProgressMessage`, `serverCapabilities`, `dispatchSyncMethod`). Both transports reuse them. stdio (`mcp_run.go`/`mcp_requests.go`) keeps an async goroutine + cancel-map for `tools/call` (cancellation via `notifications/cancelled`); HTTP (`mcp_http.go`) runs `tools/call` synchronously per-request and streams progress over SSE, cancellation driven by the request context. When adding a capability, wire it into `dispatchSyncMethod` (HTTP picks it up automatically) AND the stdio method switch in `mcp_run.go`.
- **MCP server→client requests (sampling/roots)** flow through `internal/cli/mcp_client.go`: `clientCaller`/`clientBridge` + a `serverRequester` (pending-id→chan registry). The transport supplies a `send` closure and feeds inbound responses (classified by `isResponseMessage`: id present, no method) back via `serverRequester.deliver`. stdio demuxes responses in the read loop (`mcp_run.go handleLine`); HTTP needs the client to open the `GET {mcp-path}` SSE stream — server-initiated requests are written there and answered on later POSTs, correlated per session in `internal/cli/mcp_http_session.go`. Client capabilities are captured at `initialize` (`parseClientCapabilities`). The `clientCaller`/progress emitter reach tools via context (`withClientCaller`/`withProgress`), not signatures.
- **MCP fix tools** (`internal/cli/mcp_fix.go`): `verify_fix` wraps `service.VerifyFix` (caller diff); `propose_fix` wraps `service.GenerateVerifiedFix` with a generator resolved as sampling-first (`samplingGenerator` calls the client LLM) then `internalfix.NewAIGenerator(cfg.AI)`; `apply_fix` verifies then writes the tree via `runnersupport.ApplyUnifiedDiff` (the one `destructiveHint` tool), confirming via `clientCaller.elicit` (`elicitation/create`) when supported. Verification test execution is trust-gated, so it needs `CODEGUARD_ALLOW_CONFIG_COMMANDS=1` or it fails closed. Fix failures return `toolErrorResultData` (isError + structuredContent with attempted diff + remaining findings).
- **MCP `config_path` confinement**: caller-supplied `config_path` is confined (`confinePath` in `mcp_tools.go`) to the server config dir + cwd + client roots; the server's own `--config` default is never confined. This closed a remote arbitrary-file-read finding. Streaming: `core.ScanOptions.OnSectionComplete` (tagged `json:"-"` — marshaling a non-nil func errors) fires in `runner/support.FinalizeSection`.
- **`codeguard serve --mcp --http`** serves Streamable HTTP for remote hosts (Devin): `POST {mcp-path}` returns `application/json` for sync methods and `text/event-stream` for `tools/call`; optional static-bearer auth (`--auth-token`/`$CODEGUARD_MCP_AUTH_TOKEN`, constant-time compared), `GET /healthz`, body/concurrency caps, SIGINT/SIGTERM graceful shutdown. No OAuth. MCP resources (`codeguard://rules`, `codeguard://config`, `codeguard://rules/{rule_id}`) and prompts (`review-diff`, `triage-findings`, `explain-rule`) wrap the same SDK accessors the tools use. Devin onboarding pack: `examples/hooks/devin/`.
- The taint engine emits a single rule ID per language, chosen by sink: `goTaintRuleID`/`pyTaintRuleID` return `security.ssrf.{go,python}` when the sink is an outbound-HTTP callee (`goHTTPSinkArgIndex`/`pyHTTPSinkArgIndex`), else the generic `security.taint.{go,python}`. To add an SSRF sink, add it to the arg-index map; the rule routing and OWASP A10 mapping follow automatically.
2 changes: 2 additions & 0 deletions .claude/knowledge/testing-patterns.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,5 @@ Testing strategies, test infrastructure quirks, how to run/debug specific test s
- The trust policy (`internal/codeguard/trust`) is a process-global. Tests that exercise command-driven checks or local (127.0.0.1) AI endpoints must enable it. `tests/checks` and `tests/codeguard` each have a `TestMain` (`trust_main_test.go`) that sets `Policy{AllowConfigCommands: true, AllowConfigAIEndpoints: true}`. If you add a new test package that runs config commands or hits a loopback test server, add a similar `TestMain` or the tests will fail with "refusing to run config-supplied command" / "ssrf guard" errors.
- `tests/security` deliberately has NO trust `TestMain`: it verifies the secure *default*. Tests there set/restore the policy locally with `trust.Set(...)` + `t.Cleanup(trust.ResetFromEnv)`. Do not add a package-wide opt-in there.
- All tests live under `tests/` as external (`_test`) packages; there are no white-box tests inside `internal/`/`pkg/`. Internal packages are still importable from `tests/` because they are within the module.
- The MCP server smoke tests (`tests/mcp/`) drive the **real binary in a subprocess**, not the in-process handler, to keep tests external. Pattern: a `Test...HelperProcess` func gated by an env var (`GO_WANT_MCP_HELPER_PROCESS` for stdio, `GO_WANT_MCP_HTTP_HELPER_PROCESS` for HTTP) re-execs `os.Args[0]` and calls `cli.Run(...)`. stdio replays NDJSON transcripts over stdin; HTTP reserves a free `127.0.0.1:0` port, launches `serve --mcp --http`, polls `/healthz` for readiness, then issues real HTTP requests. Add new MCP behavior as a transcript + assertion (stdio) or a subtest in `http_test.go` (HTTP).
- **Bidirectional (server→client) MCP tests** live in `tests/mcp/sampling_test.go`: the test acts as the MCP client, advertises `sampling`/`roots` at `initialize`, and answers the server's server-initiated requests. stdio uses interactive `StdinPipe`/`StdoutPipe` (not the replay harness). HTTP opens the `GET /mcp` SSE stream (waits for the `: ready` comment to avoid the attach race), reads the request off the stream, and POSTs the response with the matching `Mcp-Session-Id`. propose_fix verification is expected to fail on the throwaway diff — assert the round trip fired, not a verified patch. The HTTP helper passes `-config` via `CODEGUARD_TEST_HTTP_CONFIG`.
36 changes: 35 additions & 1 deletion docs/agent-native.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,37 @@ This document is a short status brief for `codeguard` features aimed at AI agent
- `codeguard serve --mcp`
- exposes MCP tools for `scan`, `validate_patch`, and `explain`
- also exposes `validate_config` and `list_rules`
- exposes verified auto-fix tools `verify_fix` (verify a caller-supplied diff), `propose_fix` (generate a fix, then verify it), and `apply_fix` (verify then write to the working tree) — see below
- all tools carry read-only / non-destructive annotations and output schemas
- exposes `resources`: `codeguard://rules`, `codeguard://config`, and the `codeguard://rules/{rule_id}` template
- exposes `prompts`: `review-diff`, `triage-findings`, `explain-rule`
- declares a `logging` capability (accepts `logging/setLevel`)
- consumes the client's `sampling` and `roots` capabilities when advertised (server→client requests; see below)
- `scan` streams a progress notification per check section as it completes
- supports `initialize`, `tools/list`, `tools/call`, `ping`, progress notifications, and cancellation
- covered by CLI compatibility tests and host-shaped smoke tests in `tests/cli/` and `tests/mcp/`

- Verified auto-fix
- `verify_fix` applies a caller-supplied candidate diff in an isolated workspace, re-scans the changed lines, runs the nearest inferred tests, and returns the result only if it passes (fails closed)
- `propose_fix` first generates the candidate, then runs the same verification. The generator is the client's LLM via MCP `sampling` when the client supports it (no API key needed), otherwise a configured AI provider
- `verify_fix`/`propose_fix` wrap `service.VerifyFix` / `service.GenerateVerifiedFix`; verification test execution requires the server to run with command trust enabled (`CODEGUARD_ALLOW_CONFIG_COMMANDS=1`), otherwise it fails closed
- `verify_fix`/`propose_fix` do not mutate the working tree; on verification failure they return `isError` with `structuredContent` (attempted diff + remaining findings) so an agent can iterate
- `apply_fix` verifies first and only then writes the diff to the working tree (the one destructive tool); when the client supports `elicitation` it asks the user to confirm before writing

- Server→client requests (`sampling`, `roots`, `elicitation`)
- the server issues `sampling/createMessage` (fix generation), `roots/list` (workspace folders), and `elicitation/create` (confirm before `apply_fix` writes) over both transports — on HTTP via the `GET` SSE stream and a session registry
- client roots are cached per connection and invalidated on `notifications/roots/list_changed`
- advertised client `roots` are added to the allowed set for caller-supplied `config_path` confinement; a caller-supplied `config_path` is always confined to the server's config dir, the working directory, and any client roots, and is rejected otherwise

- `codeguard serve --mcp --http`
- serves the same MCP server over Streamable HTTP for remote / cloud-hosted hosts (e.g. Devin)
- `tools/call` streams progress over SSE; other methods return a single JSON response
- optional static bearer auth (`--auth-token` / `$CODEGUARD_MCP_AUTH_TOKEN`, `--auth-header`), `GET /healthz`, request-size and concurrency limits, and graceful shutdown
- covered by `internal/cli/mcp_http_test.go`

- Devin integration pack
- HTTP and stdio MCP configs plus host scripts under [examples/hooks/devin](/Users/alex/Documents/GitHub/codeguard/examples/hooks/devin/README.md:1)

- Patch validation API
- `codeguard validate-patch` accepts a unified diff on stdin
- `codeguard.RunPatch(ctx, cfg, diffText)` is available in the public Go SDK
Expand Down Expand Up @@ -49,7 +77,12 @@ This document is a short status brief for `codeguard` features aimed at AI agent

## File map

- MCP server: [internal/cli/mcp_run.go](/Users/alex/Documents/GitHub/codeguard/internal/cli/mcp_run.go:1), [internal/cli/mcp_tools.go](/Users/alex/Documents/GitHub/codeguard/internal/cli/mcp_tools.go:1), [internal/cli/mcp_protocol.go](/Users/alex/Documents/GitHub/codeguard/internal/cli/mcp_protocol.go:1)
- MCP server core: [internal/cli/mcp_run.go](/Users/alex/Documents/GitHub/codeguard/internal/cli/mcp_run.go:1), [internal/cli/mcp_dispatch.go](/Users/alex/Documents/GitHub/codeguard/internal/cli/mcp_dispatch.go:1), [internal/cli/mcp_tools.go](/Users/alex/Documents/GitHub/codeguard/internal/cli/mcp_tools.go:1), [internal/cli/mcp_protocol.go](/Users/alex/Documents/GitHub/codeguard/internal/cli/mcp_protocol.go:1)
- MCP HTTP transport: [internal/cli/mcp_http.go](/Users/alex/Documents/GitHub/codeguard/internal/cli/mcp_http.go:1), session/SSE registry [internal/cli/mcp_http_session.go](/Users/alex/Documents/GitHub/codeguard/internal/cli/mcp_http_session.go:1)
- MCP resources and prompts: [internal/cli/mcp_resources.go](/Users/alex/Documents/GitHub/codeguard/internal/cli/mcp_resources.go:1), [internal/cli/mcp_prompts.go](/Users/alex/Documents/GitHub/codeguard/internal/cli/mcp_prompts.go:1)
- MCP verified auto-fix tools: [internal/cli/mcp_fix.go](/Users/alex/Documents/GitHub/codeguard/internal/cli/mcp_fix.go:1)
- MCP server→client requests (sampling/roots), progress + path confinement: [internal/cli/mcp_client.go](/Users/alex/Documents/GitHub/codeguard/internal/cli/mcp_client.go:1)
- Devin pack: [examples/hooks/devin/README.md](/Users/alex/Documents/GitHub/codeguard/examples/hooks/devin/README.md:1)
- Patch validation CLI: [internal/cli/commands.go](/Users/alex/Documents/GitHub/codeguard/internal/cli/commands.go:101)
- Patch validation SDK: [pkg/codeguard/sdk_run.go](/Users/alex/Documents/GitHub/codeguard/pkg/codeguard/sdk_run.go:29)
- Agent explain output: [internal/cli/info.go](/Users/alex/Documents/GitHub/codeguard/internal/cli/info.go:33)
Expand All @@ -62,3 +95,4 @@ This document is a short status brief for `codeguard` features aimed at AI agent

1. Broaden agent-config governance from pattern matching into richer contradictory-instruction and permission-model analysis.
2. Add end-to-end CI coverage for the composite action flow, not just unit coverage for the comment publisher and markdown formatter.
3. Add OAuth resource-server support to the HTTP transport (currently static bearer only) if hosts require Devin's OAuth mode.
Loading
Loading