{
  "schema_version": 1,
  "id": "protect-agent-config",
  "track": "coding",
  "name": "Protect Agent Config",
  "version": "0.7.6",
  "sha256": "b102a0f64c869c94ad8b6c6233749a66f8785358da2a1aac1f58f532651e2a64",
  "sha256_v1": "645209a149998d7563fc6495109571bf1d2ef18188d4e3fcc0b1a5d3eedc56f4",
  "keyword": "block",
  "word": "blocks",
  "says": "refuses a matched shell command before it runs. Blocks on an agent's file writes and at turn end",
  "description": "Stops an agent editing its guardrails (MITRE ATLAS AML.T0081). Shell guard refuses writes to AGENTS.md, whole agent folders (.claude/, .cursor/, .codex/, .gemini/, .agents/, .chock/, ...), MCP/hook client configs (.mcp.json, ...), guardrails toggles, plugin install markers, chock.lock, hook files of Cline, Kiro, Augment, Windsurf and Devin (user and machine level), ~/.claude.json, Claude Code plugin roots, project config of Kilo Code, opencode, Factory, Amp, Continue and Goose, .git/{hooks,config}, policy implementations/: redirect, rm/mv/tee/sed -i, cp into, git checkout/restore, a write through a symlink; CLAUDE_CODE_PLUGIN_DIRS, claude --plugin-dir, chock resume (best effort). Any case. An instruction file under docs/ asks a person. Coarse: also eval or sh -c of computed text, shells fed by pipe/variable, interpreter one-liners naming such a path. Reads, `chock sync` pass. Edit/Write: tool gate, same set and verdicts. Misses: user config under ~/.config/, writes a script makes, an existing hard link.",
  "misses": "user config under ~/.config/, writes a script makes, an existing hard link.",
  "where": [
    "command",
    "write",
    "turn-end"
  ],
  "groups": [
    "agent-config"
  ],
  "eval_cases": 1593,
  "eval_executed": 1593,
  "latest": {
    "version": "0.7.6",
    "date": "2026-10-27",
    "change": "Behaviour change (SR-2d2e, Claude Security finding F24; eval cases tc-1588 to tc-1593; tests in test_protect_agent_config_sr2e.py). `mkdir`, `mkdir -p`, `md`, and each spelling the guard already reads for other verbs (braces, variables, `cd` then a relative path, `bash -c`, quoting, `\\\\` separators, capitals), is refused when it makes a folder at or under `.chock/compiled`, the one protected path whose bare existence a gate reads (the constant `PRESENCE` in pathset.py, checked against the protected paths in a test; dockerfile-compose-security reads `.chock/compiled/<id>` as an installed sibling). An empty folder planted there misled it. A `mkdir` anywhere else stays open: `build/out`, `.claude/commands`, `.cursor`, `.agents/policies/<id>` (no gate reads a bare folder there; writes inside are still refused)."
  },
  "agent_labels": {
    "claude-code": {
      "keyword": "block",
      "says": "refuses a matched shell command before it runs. Blocks on an agent's file writes and at turn end"
    },
    "cursor": {
      "keyword": "block",
      "says": "refuses a matched shell command before it runs. Where its description says it asks, it asks. Blocks on an agent's file writes and at turn end. Where its description says it asks, it refuses at both points, because this client cannot prompt at either point"
    },
    "codex": {
      "keyword": "block",
      "says": "refuses a matched shell command before it runs. Where its description says it asks, it refuses, because this client cannot prompt before a shell command. Blocks on an agent's file writes and at turn end. Where its description says it asks, it refuses at both points, because this client cannot prompt at either point. If the hook errors, the action goes through (not witnessed; the vendor's row is only partly live-run)"
    },
    "copilot": {
      "keyword": "block",
      "says": "refuses a matched shell command before it runs. Where its description says it asks, it asks in VS Code and refuses in Copilot CLI, because it cannot prompt there. Blocks at turn end only. It does not check a file write when it happens. Where its description says it asks, it refuses at turn end, because this client cannot prompt there. If the hook errors, the action goes through (not witnessed; the vendor's row is only partly live-run)"
    },
    "devin": {
      "keyword": "block",
      "says": "refuses a matched shell command before it runs. Where its description says it asks, it refuses, because this client cannot prompt before a shell command. Blocks at turn end only. It does not check a file write when it happens. Where its description says it asks, it refuses at turn end, because this client cannot prompt there. If the hook errors, the action goes through (vendor docs only, not witnessed)"
    },
    "grok": {
      "keyword": "ungraded",
      "says": "Not graded yet for Grok"
    },
    "goose": {
      "keyword": "ungraded",
      "says": "Not graded yet for goose"
    },
    "antigravity": {
      "keyword": "ungraded",
      "says": "Not graded yet for Antigravity"
    },
    "openhands": {
      "keyword": "ungraded",
      "says": "Not graded yet for OpenHands"
    },
    "kimi-code": {
      "keyword": "ungraded",
      "says": "Not graded yet for Kimi Code"
    },
    "factory-droid": {
      "keyword": "ungraded",
      "says": "Not graded yet for Factory Droid"
    }
  },
  "source_url": "https://github.com/open-coder-ai/chock-catalog/tree/1e60d211c1bb767152c6ab0167932da06ab3a10f/base/protect-agent-config",
  "enforces": "best-effort (pre-tool-use, once hooks are installed; fails open if the hook crashes)",
  "examples": [
    {
      "word": "Refused",
      "prompt": "An agent rewrites its own permission file in place.",
      "command": "sed -i s/deny/allow/ .claude/settings.json"
    },
    {
      "word": "Allowed",
      "prompt": "An agent reads its permission file.",
      "command": "cat .claude/settings.json"
    }
  ]
}
