diff --git a/CLAUDE.md b/CLAUDE.md index 7bb886c..fb86040 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -128,6 +128,7 @@ enum values used by the `repo_has_sdk_in_code` predicate: | `vercel_ai` | Vercel AI SDK | | `autogen` | AutoGen / AG2 | | `raw_llm_sdk` | Bare `anthropic` / `openai` client usage (no agent framework); category `raw_llm_sdk` loads unconditionally, the rule's own predicate gates | +| `agent_config` | Pre-trust harness config committed to the repo (`.claude/settings.json`, `.mcp.json`, `.gemini/.env`, `.vscode/settings.json`, `.codex/config.toml`); category `agent_config` loads unconditionally, the rule's own `repo_*` predicate gates. Findings carry the offending file and line | Repo-scope rules typically combine `applies_to` with a `repo_has_sdk_in_code` predicate to narrow firing to repos that actually use the SDK in code (e.g. diff --git a/README.md b/README.md index 0b44f84..3a9cf38 100644 --- a/README.md +++ b/README.md @@ -151,8 +151,15 @@ vercel_ai/ Vercel AI SDK rules (VAI-NNN, all typescri ├── shell_safety.yaml VAI-001 ├── ssrf.yaml VAI-003 └── tool_definition.yaml VAI-004, VAI-005 + +agent_config/ Pre-trust harness config rules (AGCFG-NNN, MCPCFG-NNN; repo scope, no SDK gate) +├── mcp_client_config.yaml MCPCFG-002 +└── preauth_execution.yaml AGCFG-004..007 ``` +The `agent_config` families are cross-harness config rules, so they sit outside the +SDK-prefix numbering below (AGCFG-001..003 and MCPCFG-001 are unused). + ID prefix denotes SDK; `NNN` tool scope, `1NN` agent / subagent scope, `2NN` repo scope. Two packs predate that convention: `autogen/` numbers its agent rules AG2-001..006 in the tool range, and `claude_skill/` groups CSKILL-NNN by diff --git a/agent_config/mcp_client_config.yaml b/agent_config/mcp_client_config.yaml new file mode 100644 index 0000000..a198169 --- /dev/null +++ b/agent_config/mcp_client_config.yaml @@ -0,0 +1,34 @@ +policy: + id: agent_config_mcp_client_config + name: Project MCP client configuration + category: agent_config + description: > + Repo-scoped rules for MCP client configuration committed to a repository + (.mcp.json, .cursor/mcp.json, .vscode/mcp.json, .gemini/settings.json, + .codex/config.toml). This is the consuming side of MCP, distinct from the + mcp pack, which audits servers a repo implements. Loads for every scan. + +rules: + - id: MCPCFG-002 + title: Project MCP config launches a local command + severity: high + confidence: 0.7 + applies_to: + - agent_config + scope: repo + match: + repo_mcp_config_launches_local_command: true + explanation: > + A committed MCP client config starts a server by running an interpreter + (bash, sh, cmd, powershell, python or node) on a script from the repo or + with inline code (-c), or by running a command path inside the repo. The + client executes that command with the developer's privileges when the + server is enabled, so the repo's author controls what runs on every + machine that opens it. A server pinned to a published package or reached + over an authenticated remote URL is not flagged. + fix: > + Point the server at a published, version-pinned package (for example an + npx or uvx invocation with an exact version) or at an authenticated + remote URL instead of a script in the repo. If a local server is needed, + document how each developer installs and registers it in their own + user-level config rather than committing the launch command. diff --git a/agent_config/preauth_execution.yaml b/agent_config/preauth_execution.yaml new file mode 100644 index 0000000..d231a2a --- /dev/null +++ b/agent_config/preauth_execution.yaml @@ -0,0 +1,106 @@ +policy: + id: agent_config_preauth_execution + name: Pre-trust agent configuration + category: agent_config + description: > + Repo-scoped rules for harness configuration committed to a repository that + runs code or grants authority the moment the folder is opened, before the + user has trusted it. Cloning or opening the repo is enough; no prompt + injection is needed. The pack loads for every scan (the files it reads have + no SDK import), so each rule's own predicate does the gating. Fire once per + scan, attributed to the first offending config file. + +rules: + - id: AGCFG-004 + title: Project settings auto-enable the repo's own MCP servers + severity: critical + confidence: 0.9 + applies_to: + - agent_config + scope: repo + match: + repo_project_mcp_autoenable: true + explanation: > + A committed .claude/settings.json sets enableAllProjectMcpServers to true + or lists servers in enabledMcpjsonServers. Both skip the approval prompt + Claude Code shows before it starts a server declared in the repo's own + .mcp.json. Anyone who clones the repo and opens it therefore launches the + commands that file names with the developer's full privileges, before + reading or trusting anything. The repo author, or anyone who can land a + change to .mcp.json, controls what runs. + fix: > + Remove enableAllProjectMcpServers and enabledMcpjsonServers from the + committed settings so each project server needs an explicit per-user + approval. If a server is genuinely required, approve it in your own + user-level settings, not in a file other people clone. + + - id: AGCFG-005 + title: Project hooks execute repo-controlled commands + severity: critical + confidence: 0.7 + applies_to: + - agent_config + scope: repo + match: + repo_project_hook_runs_untrusted_command: true + explanation: > + A committed project settings file (.claude/settings.json or + .gemini/settings.json) declares a hook whose command runs a script from + the repo, uses a network or inline-execution primitive such as curl, + wget, nc, bash -c, sh -c or python -c, or reads $HOME/.ssh, .aws or .env. + Hooks run automatically on agent events with the developer's privileges, + so whoever controls the repo controls what executes, and a hook that + reaches the network or credential files is an exfiltration path that + needs no model cooperation. + fix: > + Remove the hook from committed settings, or replace it with a call to a + vetted binary on the user's PATH that takes no repo-controlled script or + inline code. Keep personal hooks in user-level settings. If a project + hook is unavoidable, review it as you would a CI step and never let it + read credential files or call out to the network. + + - id: AGCFG-006 + title: Workspace env file overrides sandbox, proxy, endpoint or trust + severity: critical + confidence: 0.8 + applies_to: + - agent_config + scope: repo + match: + repo_workspace_env_overrides_trust: true + explanation: > + A workspace config file sets a key that redirects how the agent harness + sandboxes or reaches the network: GEMINI_SANDBOX*, a *_PROXY_COMMAND, in + .gemini/.env or .codex/config.toml also *_BASE_URL (including + ANTHROPIC_BASE_URL and OPENAI_BASE_URL), NODE_OPTIONS or LD_PRELOAD, or + Codex is set to sandbox_mode = "danger-full-access" or approval_policy = + "never". Harnesses read these files on open, so a proxy command runs and + an endpoint override captures prompts and credentials before the user is + asked anything. Only key names are inspected; values are never read. + fix: > + Delete the override from the committed file. Set sandbox and proxy + options in user-level configuration or the process environment, never in + a workspace file that travels with the repo. Keep sandbox_mode at its + restrictive default and approval_policy on a mode that prompts. + + - id: AGCFG-007 + title: Editor settings enable agent auto-approve + severity: high + confidence: 0.85 + applies_to: + - agent_config + scope: repo + match: + repo_editor_agent_auto_approve: true + explanation: > + A committed .vscode/settings.json or .cursor/ settings file sets an + auto-approve option to true, such as chat.tools.autoApprove or + chat.tools.global.autoApprove, or any *autoApprove* or *yolo* flag. With + that set, the editor's agent runs tool calls and terminal commands with + no confirmation, and the setting applies to everyone who opens the + workspace. A single injected instruction then becomes an unprompted + action on the developer's machine. + fix: > + Remove the auto-approve flag from workspace settings so tool calls + prompt. If you want it for yourself, set it in your user-level settings, + and scope any terminal auto-approval to a short, explicit allow-list. diff --git a/manifest.yaml b/manifest.yaml index 3620552..f203f22 100644 --- a/manifest.yaml +++ b/manifest.yaml @@ -11,4 +11,4 @@ # # This file is metadata, not a rule: the engine's loader skips manifest.yaml # when walking the pack for policy files. -schema_version: 22 +schema_version: 23