Integrations
Ways to wire paranoid into a workflow: the GitHub Action, a pre-commit hook, and Claude Code (a Stop hook plus an MCP server). All run the same binary you use locally; none is a separate product.
GitHub Action
The composite action at alainrk/paranoid/action@main
runs verify on the pull request range, uploads the
report as an artifact, appends it to the job summary, optionally
posts a sticky PR comment, and sets the check status from the
verdict. Full inputs, outputs, and the SARIF upload example are
on the Exit codes & CI page.
- uses: alainrk/paranoid/action@main
with:
sandbox: none # or auto, when the runner has docker
claims: summary.md # optional
pre-commit
This repository ships .pre-commit-hooks.yaml with
one hook, paranoid-verify. Add it to a target
project's .pre-commit-config.yaml:
repos:
- repo: https://github.com/alainrk/paranoid
rev: main # pin a tag or commit
hooks:
- id: paranoid-verify
Then, in the target project:
pre-commit install --hook-type pre-commit --hook-type pre-push
pre-commit run paranoid-verify --all-files # try it once by hand
What the hook runs
paranoid verify --sandbox none --offline --base HEAD
- --sandbox none: no clean-room re-run of the test suite. A container build on every commit would be far too slow for a pre-commit hook; the sandbox rules are reported as skipped instead. Run
paranoid verifyby hand, or in CI, for the full clean-room check. - --offline: no registry lookups. A pre-commit hook should not need the network at all; DEP001 to DEP004 are reported as skipped.
- --base HEAD: compare against the last commit.
--headstays at its default, the working tree. - --format text (the default) is used as is. Add
args: [--fail-under, "80"](or any otherverifyflag) in the target project's own hook entry to raise the bar, since pre-commit appendsargsafterentry.
What "the commit range" means at each stage
stages: [pre-commit, pre-push] runs the exact same
command at both points, and --base HEAD means
different things depending on when it runs.
-
At the
pre-commitstage, HEAD is the last real commit and the working tree holds whatever is about to be committed, so the check is meaningful. One caveat: paranoid diffs HEAD against the working tree, not against the git index, so unstaged changes present at commit time are included too. -
At the
pre-pushstage, the working tree is normally clean, so--base HEADfinds nothing to check. This stage is only a second chance to catch whatgit commit --no-verifyskipped, not an audit of the commits about to be pushed. For that, runparanoid verify --base origin/mainby hand, or use the GitHub Action.
Why language: golang
pre-commit's golang language builds the hook's own
repository with go install ./... inside an isolated
Go environment it manages itself. Since this repo has a
go.mod at the root and its command lives at
cmd/paranoid, that install produces a binary named
paranoid (Go names the binary after the directory
holding package main), which lands on
PATH inside pre-commit's environment and matches
the hook's entry line. No manual install step or
wrapper script is needed; the only prerequisite is a Go
toolchain, which pre-commit's golang language can
provide on its own.
There is no GitLab CI component or merge-request comment integration.
Claude Code
A settings.json hook config, and an MCP server. Neither needs extra Go code; the hook is pure configuration. Full docs, the settings snippet, and the security notes: integrations/claude-code/README.md.
Stop hook and PreToolUse variant
{
"hooks": {
"Stop": [
{
"matcher": "",
"hooks": [
{
"type": "command",
"command": "paranoid verify --sandbox none --offline --format text --fail-under 80 --strict 1>&2"
}
]
}
],
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"if": "Bash(git commit *)",
"command": "paranoid verify --sandbox none --offline --format text --fail-under 80 --strict 1>&2"
}
]
}
]
}
}
Copy the hooks block into .claude/settings.json
(committed, shared) or .claude/settings.local.json
(personal). Claude Code's Stop hook contract blocks the session
only on exit code 2, with the command's stderr fed back as the
reason; the trailing 1>&2 redirects
verify's report (stdout by default) to stderr so
Claude actually sees it, and --strict maps a warn
verdict onto exit 2 too, since a plain exit 1 does not block.
The PreToolUse variant uses Claude Code's if field
to gate on the Bash tool's command text
(Bash(git commit *)), the real mechanism for
scoping a hook to one command; matcher alone only
matches the tool name.
MCP server
paranoid mcp runs a Model Context Protocol server
over stdio: newline-delimited JSON-RPC 2.0, hand-rolled on the
standard library, no SDK dependency. It implements
initialize, tools/list, and
tools/call for one tool, verify
(arguments: path, base,
head, claims, session,
offline, sandbox), which runs the same
pipeline as paranoid verify and returns the JSON
report as the tool result. Wire it into Claude Code as an MCP
server, for example:
{
"mcpServers": {
"paranoid": {
"command": "paranoid",
"args": ["mcp"]
}
}
}
Trust boundary
Both integrations are only as trustworthy as the binary they run
and the files that configure them. If the agent under audit can
edit the installed paranoid binary,
.paranoid-baseline.json, or the hook configuration,
it can make paranoid say pass. Install paranoid outside the
repository the agent can write to, and point the hook at that
installed binary on PATH, never a repo-local build.
SF006 watches .paranoid-baseline.json and hook
configuration files for exactly this: it does not prevent the
edit, it surfaces it as a finding in the next run a human reads.