Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

claude-code-security-harness

A security harness for AI coding agents. Fifteen guards that sit between your agent and your machine — and instructions for building them.

Shown with the policy and rescan config in place — see BUILD-ORDER.md for what to create first.

$ npm install left-pad
⛔ blocked — bare install; prefix it with your scanner

$ sfw npm install left-pad
🔎 scanning… clean
✅ installed

$ sfw npm install shiny-new-thing
⛔ blocked — published 4 hours ago, below your 14-day floor
   a package this fresh has not been looked at by anyone yet

$ rm ./dist/bundle.js
✅ runs — a throwaway path, no interruption

$ rm src/index.js
❓ approve? plain rm (file delete)

$ rm -rf build/
⛔ blocked — recursive force-delete
   no override; run it yourself if you meant it

$ git push --force
⛔ blocked — force push

# next morning, at session start
🔒 supply chain: N trees, 0 new findings
⚠️ integrity: 1 file changed since last baseline — hooks/bash-scope-guard.sh

The first two commands are the point: the gate does not refuse the install, it redirects it. So is rm ./dist/bundle.js further down, which runs untouched. A guard that stops everything gets uninstalled by the end of the week.

Supply chain is the centre of this harness. Six of the fifteen guards defend the install path and what happens to it afterwards, and they are why the rest exists — an agent that can install a package can run a stranger's code on your machine without ever asking you. The other nine are the perimeter around that: what your agent may delete, read, edit and push.


What you need

Thirteen of the fifteen need nothing but a shell and jq.

Tool Needed by Cost
jq fourteen of the fifteen a package install
Socket Firewall (sfw) the install guard free, no account, no quota
Socket scan (socket) the periodic re-scan free tier, draws on a per-scan quota
Aikido Safe Chain the install guard's second opinion, optional free

One setting comes before all of this. Tell your package manager not to run install-time scripts: ignore-scripts=true in npm's user config. Packages are allowed to execute their own code as part of being installed, without a prompt, and that is the route the install guard exists to close. The cost is real — anything that compiles a native component during install will now fail, often unhelpfully. Two things make that workable: the audit hook logs every installed package that wanted to run code, so a failure has a name, and the setting is a default rather than a gate, so --ignore-scripts=false on that one install lets it through once you have decided it is worth it.

Written and tested on macOS. The portability notes in each specification cover the differences that bite on Linux.


The harness at a glance

Every script in the harness, and one line on what each does. The config files are shown where they feed in; the tables in the next section say the same things at more length.

LAYER 0 ─ shared foundations. Two files the rest lean on.
│
├─ hooks/lib/hook-fired-log.sh
│     every guard appends a line here when it runs; thirteen of them read it
└─ hooks/lib/allowed-prefixes.sh
      the paths your agent is allowed to touch — you write this one


LAYER 1 ─ standalone guards. Each stands on its own.
│
├─ workflow-posture-check.sh
│     denies pull_request_target in a workflow. Needs no config at all
├─ holy-files-guard.sh
│     warns before an edit to a file you marked critical
├─ worktree-scope-guard.sh
│     blocks an edit that escapes the worktree you are working in
├─ bash-destructive-guard.sh
│     hard-denies destructive shell and git commands, asks on the rest
└─ git-hooks/pre-push
      rejects a push carrying a commit whose author was forged


LAYER 2 ─ scope guards. One allowlist, two surfaces.
│
├─ bash-scope-guard.sh                      ← allowed-prefixes.sh (owns it)
│     blocks a command that names a path outside your scope
└─ read-scope-guard.sh                      ← allowed-prefixes.sh
      the same boundary for file reads and searches


╔══════════════════════════════════════════════════════════════════════════╗
║ LAYER 3 ─ SUPPLY CHAIN · six guards · the centre of this harness         ║
║ The install gate, and everything that keeps looking after the install.   ║
║ Two config files feed it, and they are separate.                         ║
╚══════════════════════════════════════════════════════════════════════════╝

 security/supply-chain-policy.json                security/supply-chain-
 the install gate's rules                         rescan.config.json
    │                                             which trees get re-scanned
    │                                                       │
    ├──► supply-chain-guard.sh                              │
    │      the install gate for npm and pip. Scanner        │
    │      prefix, malware scan, minimum age, no            │
    │      git-URL dependencies                             │
    │      │                                                │
    │      └──► supply-chain-guard-selftest.sh              │
    │             drives the gate with a known-bad input    │
    │             every session, and says if it stopped     │
    │             denying. The smoke it drives lives in     │
    │             test-supply-chain-guard.sh                │
    │                                                       │
    └──► npm-audit-on-install.sh                            │
           after an install, reports only what is newly     │
           vulnerable against your own baseline             │
           │                                                │
           └──► security/audit-diff.sh                      │
                  the set-diff maths, shared so there is    │
                  only one copy of it                       │
                  │                                         │
                  ▼                                         │
      supply-chain-rescan-worker.sh ◄───────────────────────┤
        re-scans installed trees on a cadence, because      │
        clean when you installed it is not clean now        │
                  │                                         │
                  ▼                                         │
      supply-chain-rescan.sh ◄──────────────────────────────┤
        dispatches that re-scan at session start and        │
        reports what it found                               │
                                                            │
      non-npm-tree-detector.sh ◄────────────────────────────┘
        finds dependency trees nothing is scanning, and
        keeps saying so until you decide


LAYER 4 ─ watching the guards themselves.
│
├─ integrity-check.sh
│     fingerprints every file that executes by itself and reports a change
│     ← bash-scope-guard, holy-files-guard, bash-destructive-guard,
│       pre-push, worktree-scope-guard, supply-chain-guard
├─ hook-freshness-audit.sh                            ← hook-fired-log.sh
│     names the guards that have gone quiet, against a registry you write
└─ observability-self-check.sh                        ← hook-fired-log.sh
      answers "did my guards actually run", because a hook that stops
      firing produces no output at all, and tracks whether the audit
      above is still running

The same layers, with the order to build them in and the prerequisites each one needs, are in BUILD-ORDER.md. Change one of these two pictures and change the other.


The fifteen guards

Supply chain — six guards, and the reason for the rest

Guard What it does Needs config
supply-chain-guard The install gate. Requires a scanner prefix, runs a malware scan before the real install, enforces a minimum package age, refuses git-URL dependencies, and can route the install through a second, independent feed a policy file
supply-chain-guard-selftest Drives the gate with a known-bad input every session and confirms it still denies none
npm-audit-on-install After an install, diffs vulnerabilities against your previous baseline and reports only what is new. Records which lifecycle scripts were skipped shares the guard's policy + per-repo marker
supply-chain-rescan Dispatches the periodic re-scan at session start and reports its result shares one config
supply-chain-rescan-worker Re-scans installed trees on a cadence, because a package that was clean when you installed it can be flagged next week shares one config
non-npm-tree-detector Finds dependency trees on your machine that nothing is scanning, and keeps saying so until you decide shares one config

Command safety

Guard What it does Needs config
bash-destructive-guard Hard-denies destructive shell and git commands. Asks on the cautionary ones. Splits chained commands so a dangerous fragment cannot hide behind a safe one optional
bash-scope-guard Blocks a command that names a path outside your working scope an allowlist you write
read-scope-guard The same boundary for file reads and searches. Guarding writes and not reads leaves your keys, your credentials and other people's code readable shares the allowlist
worktree-scope-guard Blocks an edit that escapes the worktree you are working in a marker
holy-files-guard Warns before an edit to a file you marked critical. Pauses outright when something creates a new hook script a pattern list

Publishing and identity

Guard What it does Needs config
workflow-posture-check Denies any attempt to add a pull_request_target trigger to a workflow. That trigger runs with your repository's secrets while checking out a stranger's code none
git-hooks/pre-push Rejects a push carrying a commit whose author was forged. Git accepts any author string you type, without verification an author list

Watching the guards

Guard What it does Needs config
integrity-check Fingerprints every file on your machine that executes by itself — hooks, agent settings, shell startup files, scheduled jobs — and reports at session start when one changed none
observability-self-check Answers "did my guards actually run". A hook that silently stops firing produces no output at all, which looks exactly like a quiet, healthy session a registry you write

Every specification carries the pass-by-pass logic, the failure rules, the edge cases that bit, and a list of acceptance tests you can run against whatever your agent generated.


This repository publishes instructions, not scripts

Every file under hooks/ is a build specification written for your coding agent. You point your agent at one, it generates the script, in your layout, wired to your config. You review what it wrote.

That is deliberate. Copying a stranger's shell scripts into the path that decides what your agent may delete is a strange thing to do in the name of security. Instructions let you read the reasoning, watch the code appear, and keep a build that fits your machine rather than someone else's.

Each specification carries the exact logic, the failure rules, the edge cases, and a test list you can run against what your agent produced.


When it acts

Most tools guard the install. This one keeps looking afterwards.

  • At install time — a malware scan before the real install, a minimum age so you are not the first person to run a package, and a refusal of git-URL dependencies.
  • After the install — a vulnerability diff against what you had before, and a record of the lifecycle scripts that were skipped.
  • Days later — a periodic re-scan, because a package that was clean when you installed it can be flagged next week.
  • Before a command runs — destructive shell and git commands, reads and writes outside your working scope, edits to files you marked critical, and workflow changes that would hand your repository secrets to an untrusted pull request.
  • Always — a fingerprint of every file that runs by itself, so an edit to a guard is reported, and a check that the guards actually fired, because a hook that silently stops running produces no output at all.

How they are built

Two facts are shared by every guard: what your agent hands a hook, and how a hook answers so the host actually stops the call. Those live once, in HOST-CONTRACT.md, rather than fifteen times. Get either wrong and the guard still builds, still passes its own tests, and blocks nothing.

The parts that took the longest are not the pattern matching. They are these.

  • Fail closed, or fail safe, and say which. A guard that denies a tool call denies on any internal error. A session-start notice never blocks a session, whatever happens to it. Each specification states its posture and does not borrow the other one's word because it sounds stronger.
  • Every deny gate proves itself. A canary drives each gate with a known-bad input and a known-good one, every session, and reports if either answer changed. The pairing is the point: a gate that denies everything passes a positive-only test while being useless.
  • A failed scan is never reported as clean. Coverage is tracked separately from findings. A timeout, a refused request or a quota limit means not scanned, which is a different word from nothing found.
  • It must not cry wolf. A guard that fires on ordinary work gets switched off, and then you have neither the guard nor the belief that guards like it are workable. False positives are recorded and tuned, not tolerated.
  • Notice, do not act. The parts that watch and record never change anything. Only the gates decide.
  • Deny wins across a whole command. Every install in a chained command has to clear every gate. Checking only the first one is how a scanned install carries an unscanned one along behind it.
  • A warning nobody sees is not a warning. An advisory emitted in the wrong shape is discarded by the host in silence: the hook fires, prints correctly, exits zero, and reaches nobody. The specification for this states the delivery rule as a fail rule and tests the consumer, not the producer.
  • No override where one would be asked for. A gate whose escape hatch can be requested by an instruction your agent read somewhere is not a gate. Where a bypass genuinely has to exist, it takes a real advisory identifier and it is logged.

Getting started

Read BUILD-ORDER.md first. Several guards share files, and building them in the wrong order produces a stack that runs, reports healthy, and defends less than you think.

If you want one useful thing today, build hooks/workflow-posture-check.md. It needs no config and no third-party tool, and it blocks a well-known route for stealing a repository's secrets.


Limitations

Version 1 gates the npm, npx and pip commands, and not every install path receives every check. Other clients for the same registries — pnpm, yarn, bun, uv, poetry, pipx — reach them ungated; wider command coverage is planned for version 2.


Prior work

This is the successor to claude-code-safety-hooks, which published five of these guards as working scripts. That repository was a snapshot; this one is the whole harness, and it publishes the reasoning rather than the code.

Built for Claude Code and its hook configuration. The concepts carry to any agent host with a point where tool calls can be intercepted.

Security

A specification that would build a guard weaker than it claims is worth reporting privately rather than in an issue. SECURITY.md says what counts, what does not, and where to send it.

License

MIT. See LICENSE.

About

A security harness for AI coding agents. Supply chain, command scope, config integrity. Build specifications.

Topics

Resources

Security policy

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages