Hardened configurations for AI coding agents. Drop-in configs that let Claude Code, Codex CLI, Cursor, and Grok CLI read, stage, and commit — but never push, rewrite history, or destroy data.
Claude Code · Cursor · Codex CLI · Grok CLI · Skills · How it works · Getting started
AI coding agents are powerful but dangerous when given unrestricted shell access. A single hallucinated git push --force or git reset --hard can cause irreversible damage.
This repo provides production-ready dotfile configurations for three major AI coding agents, enforcing a strict commit-only workflow through three independent safety layers.
Every destructive operation is blocked at three layers simultaneously, so even a confused or adversarially-prompted agent cannot cause irreversible damage:
- Rules & instructions — the agent is told which commands are forbidden and why
- Permission deny-lists — the harness rejects matching commands before execution
- PreToolUse hooks — shell scripts parse the command payload and hard-block on match
Important
All three layers must be bypassed for a destructive command to execute. Each layer works independently — if one fails, the others still catch it.
The hook scripts are hardened against evasion: they strip single- and double-quoted spans before matching (so git commit -m "never git push" is not blocked), split compound commands on &&, ||, ;, |, and newlines to check each segment independently (so cd /tmp && git push still trips), and collapse git -c key=value prefixes so -c flags cannot smuggle a subcommand past the anchors.
scripts/test-git-block-hook keeps the rules and all four hooks in lockstep: it asserts a canonical set of dangerous commands is blocked and benign commands are allowed across every hook, and that each forbidden token in git.md has a matching pattern in all four hook scripts — so a new rule cannot drift out of sync with the enforcement. Run the full suite with make verify (lint + parity test + sync dry-runs).
| Category | Commands |
|---|---|
| Git push | git push, --force, --force-with-lease |
| Git history | git reset, rebase, commit --amend |
| Git worktree | git checkout --, restore, switch, clean -f, git worktree |
| Git deletion | git branch -D, git rm, git stash (all forms) |
| Git recovery | git gc, prune, reflog expire, update-ref |
| Destructive shell | rm -rf, rm -fr, sudo, mkfs, dd |
| Pipe-to-shell | curl | bash, wget | bash, curl | sh, wget | sh |
| Shell config edit † | ~/.bashrc, ~/.zshrc, ~/.bash_profile, ~/.zprofile |
| Credential read † | ~/.ssh, ~/.gnupg, ~/.aws, ~/.azure, ~/.config/gh, ~/.git-credentials, ~/.docker, ~/.kube, ~/.npmrc, ~/.pypirc, ~/.gem |
Note
† The shell-config-edit row is enforced at the Claude permissions layer only — as Edit/Write deny rules in settings.json. Credential reads are enforced on Claude (Read deny rules) and Cursor (the beforeReadFile hook plus Read(...) denies in cli-config.json). The other rows are blocked at all three layers on every agent.
claude/.claude/
├── CLAUDE.md # rule loader + index
├── settings.json # permissions, hooks, env, plugins
├── statusline-command.sh # custom TUI status line
├── hooks/
│ └── block-dangerous-git.sh # PreToolUse hook (regex-based)
└── rules/
├── comments.md # commenting style (scoped to code files)
├── communication.md # brevity rules
├── development.md # coding philosophy
├── git.md # forbidden commands
├── markdown.md # output formatting (scoped to *.md/*.mdx)
├── models.md # model routing for delegated work
└── orchestration.md # per-turn analyze → plan → delegate SOP
comments.md and markdown.md carry a paths: frontmatter block so Claude Code only loads them when editing matching files. models.md and orchestration.md are Claude-only delegation tooling — they are deliberately excluded from the generated Cursor rules.
Key settings in settings.json:
- Telemetry disabled —
DISABLE_ERROR_REPORTING, feedback survey off - Permissions — read-only git + staging allowed, all destructive ops denied
- Hook —
block-dangerous-git.shfires on every Bash tool call - Attribution blanked — no "Generated with" or "Co-Authored-By" trailers
cursor/.cursor/
├── hooks.json # hook registration (failClosed)
├── cli-config.json # Cursor CLI enforced permissions + sandbox
├── permissions.json # IDE-side allowlists (best-effort, JSONC)
└── hooks/
├── block-dangerous-git.sh # beforeShellExecution — dangerous git/shell
└── block-credential-reads.sh # beforeReadFile — blocks secret file reads
cursor/templates/
└── cursorignore # per-project .cursorignore template
Cursor is covered by three configuration layers, in decreasing order of enforcement:
- Hooks (
hooks.json+hooks/) — the real enforcement. Both entries set"failClosed": true, so a hook that errors blocks rather than fails open.block-dangerous-git.shruns onbeforeShellExecution;block-credential-reads.shruns onbeforeReadFileand denies reads of secret material —.env*,*.pem,*.key,id_rsa*/id_ed25519*, and anything under~/.ssh,~/.aws,~/.gnupg,~/.azure,~/.config/gh,~/.kube,~/.docker, plus.git-credentials. Both speak Cursor's JSON protocol ({"permission":"allow"}/{"permission":"deny","agent_message":"..."}) and fail closed ifjqis missing. cli-config.json— enforced permissions for the Cursor CLI (cursor-agent): aShell(...)deny-list mirroring the git blocklist plussudo/mkfs/dd,Read(...)denies for credential paths, an allowlist of read-only git + build commands,approvalMode: "allowlist", and sandbox on.permissions.json— the IDE-side allowlist and auto-run guardrails. Best-effort, not a security boundary (see the comment at the top of the file); it steers the IDE agent but the hooks are what actually block.
Rules are generated at sync time from claude/.claude/rules/*.md — each .md becomes a .mdc with Cursor frontmatter injected and the Claude frontmatter stripped; rules with a paths: list become file-scoped (globs: + alwaysApply: false). models.md and orchestration.md are skipped. This keeps Claude as the single source of truth.
Note
Cursor does not read ~/.cursor/rules/*.mdc globally — global "User Rules" are UI-only with no backing file. The generated .mdc files exist for convenience: copy the ones you want into a project's .cursor/rules/. Likewise .cursorignore is per-project only; use the cursor/templates/cursorignore template as a starting point.
codex/.codex/
├── AGENTS.md # combined behavioral rules
├── config.toml # model, sandbox, privacy + inline PreToolUse hook
├── hooks/
│ └── block-dangerous-git.sh # PreToolUse hook (case-based)
└── rules/
└── default.rules # prefix_rule exec policy
Key settings in config.toml:
- All telemetry off — analytics, feedback, OTEL exporters all disabled
- Sandbox —
workspace-writemode (can write files, cannot escape workspace) - Hook — registered inline under
[[hooks.PreToolUse]](withfeatures.hooks = true);block-dangerous-git.shruns before every Bash call. Codex reads this fromconfig.tomldirectly — no separatehooks.json - Exec policy —
default.rulesenumerates every allowed and forbidden command with justifications - Attribution blanked —
commit_attribution = ""
grok/.grok/
├── AGENTS.md # combined behavioral rules
├── config.toml # privacy, permissions, compat decoupling
└── hooks/
├── block-dangerous-git.sh # PreToolUse hook (deny-JSON protocol)
└── git-safety.json # PreToolUse hook registration
Key settings in config.toml:
- Telemetry off —
[features]telemetry/feedback and[telemetry]trace/mixpanel all disabled - Permissions —
[permission]deny/allow lists mirror the Claude settings (read-only git + staging allowed, all destructive ops denied); deny wins over allow and holds even under always-approve yolo = false— kept off, but the deny-list and hook still enforce even if a user flips it on- Compat decoupling —
[compat.claude]/[compat.cursor]/[compat.codex]turn off foreign scans so Grok loads rules/hooks only from its own~/.grok/sources, avoiding double-loading - Hook —
git-safety.jsonregistersblock-dangerous-git.shas aBashPreToolUse hook. Grok's runner fails open on anything but an explicit deny, so the hook emits a{"decision":"deny"}JSON on stdout (and exits 2) to block
skills/ holds hand-authored agent skills — self-contained instruction packages an agent loads on demand. Each is a directory with a SKILL.md (name + trigger description in frontmatter) and optional references/, scripts/, and gateways/.
| Skill | Purpose |
|---|---|
bet |
Map every expected behavior of a unit into a .tree spec before writing tests |
clean-ui |
Build and audit clean, geometrically correct web + mobile interfaces |
effect-ts |
Idiomatic Effect-TS patterns — services/layers, errors, concurrency, schema, testing |
fumadocs |
Write, refactor, and debug Fumadocs documentation sites idiomatically |
handoff |
Compact the current conversation into a handoff doc + ready-to-paste kickoff prompt |
rate |
Score work on quality axes, fix every gap, re-score until all axes hit 10 |
rate-iteration-cc |
The rate loop with an independent multi-model rater panel |
vercel-observability |
Pull, query, and export Vercel observability data via CLI, Drains, and REST |
delegate-grok |
Delegate from Grok to the Claude Code CLI for planning, review, and implementation |
Each skill fans out to three consumer roots — ~/.claude/skills/, ~/.agents/skills/ (Codex/Cursor), and ~/.grok/skills/. A marker file in a skill directory narrows that fan-out:
.claude-only— only~/.claude/skills/(e.g.rate-iteration-cc, which shells out to the grok/codex CLIs).grok-only— only~/.grok/skills/(e.g.delegate-grok)- no marker — all three roots
Markers gate distribution and are never copied into the targets. bin/sync-skills copies (never symlinks — Codex's loader rejects symlinks) and tracks what it wrote in a per-target .sync-skills-manifest, so it only prunes skills it previously managed and leaves externally-installed ones untouched.
bin/sync-skills # fan out all skills to the three roots
bin/sync-skills --dry-run # preview
bin/sync-skills --unlink # remove managed skillsThe sync scripts use a split strategy — symlinks for files that benefit from live editing (settings, hooks), copies for files that would cause duplicate context loading if symlinked (CLAUDE.md, rules). This prevents AI agents from loading config twice when working inside this repo.
# Claude Code — selective symlinks + copies to ~/.claude/
bin/sync-claude
# Cursor — symlinks for hooks, generated .mdc rules from claude sources to ~/.cursor/
bin/sync-cursor
# Codex CLI — selective symlinks + copies to ~/.codex/
bin/sync-codex
# Grok CLI — symlinks for hooks, copies for AGENTS.md + config.toml to ~/.grok/
bin/sync-grok
# Skills — copy each skill to ~/.claude, ~/.agents, ~/.grok (honoring markers)
bin/sync-skillsTo remove managed entries:
bin/sync-claude --unlink
bin/sync-cursor --unlink
bin/sync-codex --unlink
bin/sync-grok --unlink
bin/sync-skills --unlinkPreview without writing:
bin/sync-claude --dry-run
bin/sync-cursor --dry-run
bin/sync-codex --dry-run
bin/sync-grok --dry-run
bin/sync-skills --dry-runAfter syncing, run make verify to lint the shell scripts, run the hook parity test, and dry-run all four sync scripts.
cp -r claude/.claude ~/ # Claude Code
cp -r cursor/.cursor ~/ # Cursor
cp -r codex/.codex ~/ # Codex CLI
cp -r grok/.grok ~/ # Grok CLI# From the repo root — works but causes duplicate context loading when
# editing inside this repo (AI agents resolve symlinks)
stow claude
stow codexNote
The hook scripts require jq to parse tool call payloads. They fail closed — if jq is missing, all commands are blocked rather than silently allowed.
- Add allowed commands — edit
permissions.allowinsettings.json(Claude) or addprefix_rule(..., decision="allow")indefault.rules(Codex) - Unblock a git command — remove it from all three layers (rules, deny-list, hook script) to maintain consistency
- Add rules — drop a
.mdfile inclaude/.claude/rules/(cursor.mdcrules are auto-generated on nextbin/sync-cursor) or append tocodex/.codex/AGENTS.md - Plugins — edit
enabledPluginsinsettings.json(Claude) or configure inconfig.toml(Codex)
- This hook stops Claude Code running dangerous git commands — original hook approach and git safety patterns
- Trail of Bits claude-code-config — credential read deny-lists, shell config protections, and destructive command patterns