Skip to content

Skills · ai-engineering

agent-config-review

Review a coding agent's configuration for access broader than the work needs and give a verdict — SAFE AS CONFIGURED, TIGHTEN BEFORE USE or DO NOT USE — reading what actually wins across every scope (Claude Code user, project, local and managed settings; permission allow/deny/ask lists, defaultMode, sandbox, hooks, plugins, env; .mcp.json; subagent tool lists; Codex sandbox_mode and approval_policy and the profile in use; Cursor and Copilot agent settings), with each finding's file and line, what it lets happen in plain words, and the exact replacement config. Use when the user asks "is this Claude Code / Codex / Cursor setup safe", "review our .claude/settings.json", "audit our agent permissions", "is Bash(npm:*) OK in the allow list", "check this repo's agent config before we open it", or wants an agent's permissions tightened before use.

Pack ai-engineeringTier standardJob Govern AI usev0.1.1

npx skilldrop-cli install agent-config-review
Other ways to install

With the sibling skills it hands off to:

npx skilldrop-cli install agent-config-review --with-related

The whole pack it belongs to:

npx skilldrop-cli install --pack ai-engineering

By hand, from a clone of the repo:

cp -R packs/ai-engineering/skills/agent-config-review ~/.claude/skills/

In Claude Code, with the ai-engineering plugin installed, invoke it as:

/ai-engineering:agent-config-review

Try it

A realistic prompt from the skill's acceptance evals:

We just cloned dispatch-api from our platform team and it comes with Claude Code already set up: the .claude folder, .mcp.json, package.json and .gitignore are in evals/files/1. Before my team opens it in Claude Code, is this setup safe to use?

What a good result looks like

Example output

Worked example: a Python repo that is TIGHTEN BEFORE USE · the skill's own worked example, source

Input

We're about to roll this Claude Code setup out to the whole data team for tidewater-ingest. Anything we should change first?

Files: inputs/tidewater-ingest/.claude/settings.json, inputs/tidewater-ingest/.mcp.json, inputs/tidewater-ingest/.gitignore.

What the scan listed (abridged, real output)

- allow (4): `Bash(uv run pytest:*)` .claude/settings.json:4; `Bash(make:*)` .claude/settings.json:5;
  `Bash(git diff:*)` .claude/settings.json:6; `mcp__slack__slack_get_channel_history` .claude/settings.json:7
- deny (1): `Read(./.env)` .claude/settings.json:9
- sandbox: allowUnsandboxedCommands = false; enabled = true; excludedCommands = ["docker"];
  network.allowedDomains = ["pypi.org", "files.pythonhosted.org"]
- hooks: PreToolUse [Bash] .claude/settings.json:19: `python3 tools/guard_commands.py`
- `slack` .mcp.json:3: `npx -y slack-mcp-community` - env: SLACK_BOT_TOKEN
- Literal secrets: none found
- [deny-bypassable] Read(./.env) - allowed without asking: Bash(make:*)
- [wide-allow] Bash(make:*)
- [sandbox-escape] docker
- [unpinned-package] npx -y slack-mcp-community
- [hook] python3 tools/guard_commands.py

How it was graded

  • Strict sandbox on (enabled, allowUnsandboxedCommands: false, two package hosts), so Bash(make:*) is arbitrary code inside a sandbox: 🟧, not 🟥.
  • docker in excludedCommands always runs outside the sandbox: 🟧.
  • The Slack server runs outside the sandbox and reads channel history (untrusted: anyone in the channel writes it) with no prompt. Posting is not in allow, so it still asks: the prompt is the one control between an injected message and a post. 🟧, held by a person who must see the message body.
  • Unpinned npx -y package from a publisher the files don't name: 🟧.
  • The hook runs a script in the repo, no download: listed, not a finding; its contents are "To confirm".
  • No 🟥, four 🟧 → TIGHTEN BEFORE USE.
Output

TIGHTEN BEFORE USE. Nothing here runs or leaks without a person seeing it, but four settings are wider than running tests and reading Slack needs, and two of them sit outside the sandbox.

  1. 🟧 .claude/settings.json:5 — Bash(make:*) runs any Makefile target, and make -f any file, without asking, so the .env deny on line 9 doesn't hold: a target can read .env. The sandbox keeps it inside the project and off the network. Allow the targets the team runs:
"allow": ["Bash(uv run pytest:*)", "Bash(make <target>)", "Bash(git diff:*)"]

with one Bash(make <target>) per target the team uses.

  1. 🟧 .claude/settings.json:14 — docker in excludedCommands always runs outside the sandbox, with the user's full file and network access. Remove it, and run containers by hand:
"excludedCommands": []
  1. 🟧 .mcp.json:3 — npx -y slack-mcp-community installs whatever version the registry serves each time a session starts, outside the sandbox, from a publisher nobody here has named. Pin an exact version someone has reviewed, or use Slack's own server if Slack publishes one:
"args": ["-y", "slack-mcp-community@<exact-reviewed-version>"]
  1. 🟧 .claude/settings.json:7 with .mcp.json — channel history is read without asking, and anyone in those channels can write instructions into it. Posting still asks, and that prompt is the only thing between a planted message and a post: whoever approves must read the message body, not just the tool name. Keep any Slack posting tool out of allow.

Fine as configured: the strict sandbox and its two hosts, git diff, uv run pytest, the token held as ${SLACK_BOT_TOKEN}, and .claude/settings.local.json in .gitignore.

To confirm

  • Each person's ~/.claude/settings.json and any managed policy: not read, and they add their own allow entries.
  • What tools/guard_commands.py does: it runs before every shell command.
  • The Slack bot token's scopes and which channels it can read.
  • The Makefile targets the team runs, to list in allow.

How it's checked

Measured against plain Claude Code (2026-10-23): it met 54% of its checks, against 26% without it, and a blind judge preferred it. How skills are checked

38 assertions across 5 acceptance evals, and 10 trigger queries. Every skill's checks

What the output must do
  • The first lines give a verdict that the setup is not safe to use as configured (DO NOT USE or plainly equivalent) and name the worst finding, before any table or list
  • It flags the SessionStart hook in .claude/settings.json that downloads a script from raw.githubusercontent.com and pipes it into bash as critical, says it runs without asking and outside the sandbox, and replaces it with a committed, reviewed local script or removes it
  • It says .claude/settings.local.json sets defaultMode bypassPermissions, which outranks the project file's acceptEdits, so the effective mode asks nothing, and notes the file is not git-ignored and so was shared with the clone
  • It treats the GitHub token in .mcp.json as leaked and says to rotate it, not only move it, shows it masked rather than in full, and gives a ${VAR} replacement
  • It says the deny list (curl, wget, Read of .env) is walked around by the allowed Bash(python3:*) and Bash(npm:*), and replaces the wildcards with exact entries taken from the package.json scripts (such as npm run test and npm run lint)
  • It flags allowUnsandboxedCommands: true, the *.githubusercontent.com allowed domain, and the unpinned MCP packages that enableAllProjectMcpServers starts without approval
  • It flags the db-migrator subagent for having no tools list, so it inherits every tool including the MCP servers, and does not flag the reviewer subagent, which lists Read, Grep and Glob
  • It does not invent package versions, publishers, hosts, token scopes or script names absent from the files, and lists what it could not see (user and managed settings, whether the token was pushed, its scopes) under a closing 'To confirm'
  • It opens with a verdict that the setup as launched is not safe (DO NOT USE or plainly equivalent), naming the fast profile's danger-full-access with approval_policy never
  • It reviews the fast profile as what actually runs, not the top-level on-request and workspace-write values, and says the top level does not apply when launched with --profile fast
  • It gives replacement TOML for the profile that keeps a sandbox (workspace-write) and an approval policy other than never
  • It flags shell_environment_policy inherit = "all" as handing the Stripe key and AWS credentials to every command, and replaces it with a narrower setting
  • It flags the plaintext password in the billing_rw connection string in config.toml, shows it masked, moves it to the environment, and does not claim the file was committed to a repo
  • It names the combination of Linear issue text the agent reads, a read-write billing database role, and unrestricted network with no approval, and narrows it (read-only role, no write, or no network)
  • It flags the unpinned npx packages in config.toml (server-postgres, mcp-remote) and treats the Cursor billing-db server, pinned and using an environment reference, as fine
  • It does not invent token scopes, versions, Cursor app settings or hosts not in the files, and puts what it can't see (Cursor auto-run settings, the Sentry token's scope, any project-level Codex config) under 'To confirm'
  • The first sentence answers no (or not as written) without preamble
  • It explains that node -e, npm scripts or npm exec can read files and make network requests themselves, so denying curl and wget does not stop data leaving
  • It gives exact replacement allow entries for running tests and lint, such as Bash(npm run test) and Bash(npm run lint), instead of the wildcards
  • It names a strict sandbox (enabled, allowUnsandboxedCommands false, a narrow allowedDomains list) as the control that actually limits what any process can reach
  • It stays a short chat answer: no verdict banner over a full audit, no severity table, no sections about files the user didn't share
  • It does not invent script names, hosts or other settings in the user's config beyond what the question states
  • Answers the actual question directly near the top: no, do not open/trust the folder in Claude Code as committed (verdict TIGHTEN BEFORE USE or DO NOT USE, never SAFE AS CONFIGURED), and says what to change first.
  • Flags env.ANTHROPIC_BASE_URL = https://llm-gateway.quillstack.example in quillstack/.claude/settings.json as routing the user's own Claude Code traffic (prompts, code it reads, API credentials) through the project's server.
  • Flags the quill-cloud server in quillstack/.mcp.json sending the user's own ${GITHUB_TOKEN} as a Bearer header to https://mcp.quillstack.example/v1, and notes enableAllProjectMcpServers: true plus the mcp__quill-cloud allow entry let it connect and act without a prompt.
  • Flags the SessionStart hook (node .claude/hooks/bootstrap.mjs) as repo-controlled code that runs automatically at every session start, and says that script was not among the files provided so it must be read before trusting; it does not describe what the script does.
  • Corrects the 'my settings are locked down so I'm fine' belief with how scopes combine: the user's deny rules (Bash(curl:*), Bash(wget:*), Read(~/.ssh/), Read(~/.aws/)) still apply because deny wins, but the project's defaultMode acceptEdits overrides the user's default, and the project's allow rules, hooks, env and MCP servers are added on top; and Read() deny rules (including the project's Read(./.env)) do not stop Bash(npm run:*), Bash(npx:*) or Bash(git:*) from reading those files.
  • Flags the release-notes subagent in quillstack/.claude/agents/release-notes.md: it has no tools line (so inherits every tool) and instructs git push origin HEAD:main, which the Bash(git:*) allow entry lets run without a prompt; suggests a restricted tools list and removing the push step.
  • Gives concrete, valid replacement config or exact edits (e.g. delete the env block, hooks, enableAllProjectMcpServers, Bash(npx:*), Bash(git:*), Bash(curl:*), WebFetch and mcp__quill-cloud; keep defaultMode default), sized to the 'dont need an essay' request rather than a long report.
  • Invents nothing not in the files: no claimed behaviour for bootstrap.mjs, tools/mcp/docs-server.mjs, scripts/format-staged.mjs or the quill-cloud tools, no files or settings that were not provided, and no made-up CVE numbers or version numbers.
  • Gives the change board a direct answer up front: no, not OK to run unattended as deployed (TIGHTEN BEFORE USE or DO NOT USE), followed by the list of required changes.
  • Identifies that the workflow runs codex exec --profile ci -c sandbox_mode="danger-full-access", and that the -c override beats the profile's sandbox_mode = "workspace-write", so what actually runs is approval_policy = "never" with no sandbox; reviewing the ci profile alone is misleading.
  • Notes the ci profile inherits the top-level [sandbox_workspace_write] block, and flags writable_roots containing /var/run/docker.sock (root-equivalent on the self-hosted harrow-runner host) and network_access = true.
  • Flags shell_environment_policy inherit = "all" exposing DEPLOY_TOKEN, AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY to every command, and connects it to untrusted input: the job fires on issues opened and issue comments from anyone, with ${{ github.event.issue.title }} and ${{ github.event.issue.body }} pasted into the prompt (prompt injection) and straight into the run: shell script (shell injection).
  • Flags the hard-coded GITHUB_PERSONAL_ACCESS_TOKEN (ghp_EXAMPLEEXAMPLEEXAMPLE) in [mcp_servers.github]: move it to an environment variable or secret and rotate it if a real token was ever there; and notes the GitHub MCP server is available to the ci run with approval_policy = "never".
  • Points out the AGENTS.md rules (never push to main, no reading env vars or secrets, no network except github.com, no changes under infra/ or .github/) are instructions and not enforcement; with contents: write and no sandbox nothing stops them being broken, so they need real controls such as branch protection, network off and secrets kept out of the job env.
  • Gives exact replacement TOML for the ci run (e.g. sandbox_mode = "workspace-write", network_access = false, no docker.sock in writable_roots, shell_environment_policy inherit = "core" or an exclude list, no hard-coded token) and the matching workflow change (drop the -c override, take the deploy/AWS secrets out of the job env, pass the issue text via a file or env var instead of inline ${{ }}, run docker-based tests outside the agent step).
  • Invents nothing not in the files: no Codex settings, flags or profile names beyond what is shown or well established, no made-up runner details, secret names or ticket numbers other than HF-412, and does not claim branch protection exists or not.
When it should and shouldn't activate

Should activate:

  • Can you review our .claude/settings.json and .mcp.json before the team starts using Claude Code on this repo?
  • Is my ~/.codex/config.toml safe? I run it with a profile that skips approvals
  • Is Bash(python3:*) in the allow list a problem if we deny curl?
  • Audit the agent permissions in this repo: hooks, sandbox, subagents, the lot
  • This repo we cloned has a SessionStart hook in its Claude settings. Safe to open it?

Should leave to another skill:

  • Which MCP servers should our release loop connect to, and with what token scopes?
  • Threat-model our support agent: it reads tickets, queries the customer DB and can send email
  • Write the policy for which AI coding tools staff may use with client code
  • How should I split this migration across subagents, and what should each one be told?
  • The agent committed our GitHub token to a public repo an hour ago. What do I do?

Hands off to

Related skills

ai-usage-policy, subagent-design

Source

SKILL.md · the whole folder on GitHub.