{
  "markdown": "<picture>\n  <source media=\"(prefers-color-scheme: dark)\" srcset=\"https://raw.githubusercontent.com/manumarri-sudo/notari/main/docs/assets/notari-mark-dark.svg\">\n  <img src=\"https://raw.githubusercontent.com/manumarri-sudo/notari/main/docs/assets/notari-mark.svg\" alt=\"\" width=\"38\">\n</picture>\n\n# notari\n\n> **Notari** issues a signed **Change Passport** for every AI-authored pull\n> request: a receipt any reviewer or auditor can re-verify from the signature\n> alone, without re-running Notari or trusting a screenshot, recording which\n> files a human approved the agent to touch and\n> whether it stayed inside them. A human signs the boundary once; in CI, Notari\n> checks each pull request against it and stamps the passport **PASS**,\n> **NEEDS_REVIEW**, or **BLOCK**. There is no model in that verdict, so it cannot\n> be prompt-injected. It does **not** judge whether the code is correct: it\n> attests to *where* the change went and whether it leaked a secret, the part a\n> human most often skims past on a large agent PR. **Alpha**; treat\n> [the security model](https://github.com/manumarri-sudo/notari/blob/main/docs/SECURITY-MODEL.md)\n> as the source of truth over any one-line claim.\n\n## Start here: one command\n\n```bash\nuvx notari init\n```\n\nNothing to install first. In one command, inside any git repo, that generates an\napprover keypair and a gate keypair, signs a secure-by-default perimeter, writes\nthe hardened GitHub workflow (`pull_request_target`, SHA-pinned, the pull request\nchecked out into a data-only directory), gitignores your private keys, and then\nprints your honest posture plus the exact steps still missing. Measured from a\nclean machine with no Python tooling configured, the CLI resolves and runs in\nabout a second and a half.\n\nPrefer a persistent install? `pipx install notari` or `pip install notari`, then\n`notari init`. Either way the next two commands are the whole daily loop:\n\n```bash\nnotari begin \"add rate limiting\" --scope \"src/api/**\"    # sign what the agent may touch\nnotari verify --strict                                    # in CI: PASS, NEEDS_REVIEW, or BLOCK\n```\n\n> `notari init` automatically forbids directories that look sensitive (`auth`,\n> `migrations`, `infra`, `terraform`, `deploy`, and similar). Forbidden beats contract\n> scope, so scoping a task *into* one of them returns BLOCK by design. When you really\n> do need to touch one, set the boundary explicitly and sign it:\n> `notari guard --key approver.pem --allow \"src/**\" --forbid \"migrations/**\"`.\n\n## What you actually get\n\nFour surfaces, each stated at its real strength rather than its most flattering one:\n\n**1. The gate, which is the provable boundary.** In CI, outside the agent's reach,\nevery changed path outside `.notari/` is measured against a signed scope and a signed\nperimeter, each touched file is scanned for 26 vendor secret patterns, and renames\n(both endpoints), mode-only changes, binaries, symlinks, submodules, and\n`.gitattributes` diff-hiding are all in the inventory rather than blind spots. You name\nthe scope per task (`--scope`), so a narrow scope means \"only these paths\", and the\nexplicit `--scope '**'` means \"anything the signed perimeter does not forbid\", stated\nplainly on the passport either way so a PASS is never ambiguous. Secret detection is a\nfinite pattern set, so it catches the common vendor-format leaks rather than proving no\nsecret exists. There is no model in the decision path, so there is nothing to\nprompt-inject.\n\n**2. The receipt, which outlives the run.** A Change Passport (`passport.json` plus a\nPR-ready `passport.md`) whose Ed25519 signature anyone can re-check later with\n`notari verify-passport`, months on, on a different machine, so a forged or tampered\nverdict fails and you trust the signed receipt rather than a screenshot. This checks\nthat the gate genuinely issued this verdict, not that the code is correct, and it does\nnot re-run the gate. Behind it sits an HMAC-chained audit log that detects edits and\ninsertions cryptographically, and trailing truncation against a sealed high-water-mark\nonce `notari audit verify` has run; the passport's footer cites the exact chain entry\nfor its run.\n\n**3. The remediation path, so a BLOCK is actionable.** `notari explain` turns a BLOCK\ninto a per-finding fix and a paste-ready agent prompt, and `notari agent-brief` hands\nthe next agent its approved scope before it starts. All local, all human-gated, no\ntelemetry, and no raw code, diffs, or secret values ever leave your machine.\n\n**4. The guard on your laptop, which is defense-in-depth and not a hard boundary.** An\noptional `PreToolUse` hook gates the agent's tool calls as they happen (`rm -rf`,\n`git push --force`, `DROP TABLE`, `vercel --prod`, `.env` reads), critical calls need a\nsingle-use ten-minute approval that on macOS is hardware-attested through Touch ID on\nthe Secure Enclave, and files the agent writes get an AST pass that flags\n`shutil.rmtree` and `exec(b64decode(...))` before a later `python foo.py` can run them.\n\nThat fourth row is deliberately labelled: an application-layer gate can be routed\naround by a determined adversary, and the limits are enumerated in\n[the security model](https://github.com/manumarri-sudo/notari/blob/main/docs/SECURITY-MODEL.md)\nrather than buried. The boundary that holds is the CI one.\n\n**New here?** Start with the\n[Quickstart](https://github.com/manumarri-sudo/notari/blob/main/docs/QUICKSTART.md)\n(zero to a blocked bad PR in ~10 minutes). Then:\n[Security & threat model](https://github.com/manumarri-sudo/notari/blob/main/docs/SECURITY-MODEL.md) ·\n[Product & tiers](https://github.com/manumarri-sudo/notari/blob/main/docs/PRODUCT.md).\n\n**Found a bypass, a confusing error, or a reason you'd never adopt this?** That is\nexactly the feedback this alpha exists for:\n[open an issue](https://github.com/manumarri-sudo/notari/issues).\n\n**Want to attack it first?** `bash examples/bypass_probes.sh` runs twelve bypass\nshapes against a freshly signed perimeter and prints the verdict for each:\nrename into a forbidden path, mode-only change, case variant, a secret hidden\nbehind a `.gitattributes -diff` entry, an agent widening its own signed\ncontract, and more. Eleven BLOCK; the twelfth is the documented line-split\nsecret limit. A thirteenth shape that earns an undeserved PASS is the single\nmost useful thing you can send.\n\n<!-- mcp-name: io.github.manumarri-sudo/notari -->\n\n[![PyPI](https://img.shields.io/pypi/v/notari.svg)](https://pypi.org/project/notari/)\n[![Python versions](https://img.shields.io/pypi/pyversions/notari.svg)](https://pypi.org/project/notari/)\n[![CI](https://img.shields.io/github/actions/workflow/status/manumarri-sudo/notari/ci.yml?branch=main&label=ci)](https://github.com/manumarri-sudo/notari/actions/workflows/ci.yml)\n[![Typed](https://img.shields.io/badge/typed-strict-blue.svg)](https://mypy.readthedocs.io/)\n[![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)\n\nAn AI agent opens a pull request. Which files was it *not* approved to touch? On\na 40-file agent diff that is the question a human skims past, and it is the one\nNotari answers, at the merge boundary in CI, with a signed Change Passport a\nreviewer can re-verify instead of trusting a screenshot. The verdict is a\ndeterministic function of the diff, the signed contract, and the policy, so\nthere is no model in it to jailbreak. It records *where* the change went and\n*whether* it leaked a secret; it does not prove the in-scope code is correct or\nfree of a backdoor. That is the honest scope.\n\n<picture>\n  <source media=\"(prefers-color-scheme: dark)\" srcset=\"https://raw.githubusercontent.com/manumarri-sudo/notari/main/docs/assets/notari-flow-dark.svg\">\n  <img src=\"https://raw.githubusercontent.com/manumarri-sudo/notari/main/docs/assets/notari-flow-light.svg\" alt=\"How Notari works: a human signs the task boundary, the AI agent writes the diff, CI verifies the change against the signed boundary and issues a PASS, NEEDS_REVIEW, or BLOCK verdict, recorded in a signed Change Passport any reviewer can re-check.\" width=\"920\">\n</picture>\n\nAn agent edits in and out of scope, and Notari verifies each diff\n(PASS / NEEDS_REVIEW / BLOCK) against the signed contract (from\n[`examples/change_control_demo.sh`](https://github.com/manumarri-sudo/notari/blob/main/examples/change_control_demo.sh),\nunedited):\n\n<img src=\"https://raw.githubusercontent.com/manumarri-sudo/notari/main/docs/assets/notari-demo.gif\" alt=\"Terminal recording of the demo: setup, a PASS on an in-scope change, BLOCK verdicts on forbidden-path and secret-introducing changes, and notari explain remediation.\" width=\"920\">\n\n```bash\n# 0. one-time: create a human approver key. Keep the PRIVATE half off the build\n#    box; pin the PUBLIC half so a PR cannot edit the trust root (in CI this is a\n#    repo/org secret or variable, NOT a path inside the checkout).\nnotari keygen --out approver.pem\nexport NOTARI_APPROVER_PUBKEYS=\"$(cat approver.pem.pub)\"\n\n# 1. sign the standing boundary ONCE: what agents may touch, what is off-limits.\nnotari guard --key approver.pem --allow \"src/**\" --forbid \"migrations/**\"\n\n# 2. capture AND SIGN the approved task (a signed contract can't be forged by the agent).\nnotari begin \"Add rate-limiting to the login endpoint\" --scope \"src/auth/**\" --key approver.pem\n#    commit .notari/{perimeter.json,perimeter.sig,contract.json,contract.sig} to the BASE branch.\n\n# 3. the agent writes the diff and opens the PR.\n\n# 4. CI verifies the PR against the signed boundary, in strict mode.\nnotari verify --strict        # PASS · NEEDS_REVIEW · BLOCK   (BLOCK fails the build)\n#    add --sign-key gate.pem (or NOTARI_GATE_KEY in CI) to ALSO sign the verdict,\n#    so a reviewer can re-check it with `notari verify-passport`.\n```\n\nThat full sequence, pinned external approver key, signed perimeter, signed\ncontract, `--strict`, is the real adversarial boundary. Drop any of it (`notari\nbegin` / `notari verify` with no flags) and Notari still runs, but in *cooperative*\nmode: honest review automation for a non-adversarial agent, not a boundary\nagainst one trying to escape. Verdict signing is opt-in (it needs a gate key); it\nis not on by default. See [docs/SECURITY-MODEL.md](docs/SECURITY-MODEL.md) for\nthe full deployment checklist.\n\n`notari verify` reads `git diff <base>..HEAD`, checks every changed file against the\nscope you approved, scans each touched file for hardcoded secrets and sensitive surfaces\n(CI config, lockfiles, test deletions), evaluates any logged exceptions, and writes\na **Change Passport**: `passport.json` + a PR-ready `passport.md`, that cites the\nHMAC-chained audit entry for the run, so a reviewer can trace the verdict back to a\ntamper-evident record instead of trusting the markdown alone.\n\n---\n\n## Why this layer\n\nThe agents writing your code have shell access, file write, deploy permissions, and\nyour credentials, and the failures are already on the record: in July 2025 [Replit's\nagent deleted a production database](https://fortune.com/2025/07/23/ai-coding-tool-replit-wiped-database-called-it-a-catastrophic-failure/)\nduring a vibe-coding session and fabricated data to cover it; that same month a\nCursor agent ran `rm -rf ~/` against a developer's home directory; in August an\nautonomous agent committed a customer's GitHub token into a public commit. The\ncommon thread is not that the model is evil, it is that nothing checked the change\nagainst what was actually approved before it shipped.\n\nNotari puts that check where it can be enforced and recorded: **in CI, on the pull\nrequest**, where the gate runs outside the agent's own process and cannot be quietly\nswitched off by the thing it is reviewing. The verdict is a deterministic function of\nthe diff, the contract, and the policy, there is no LLM in the decision path, so the\ngate itself cannot be prompt-injected.\n\n## What Notari is, and what it is not\n\nCalibration matters more than marketing.\n\n- **Notari is a verification-and-evidence artifact, not a content classifier.** It\n  does not predict whether a change is \"good.\" It checks the diff against a recorded\n  contract (scope, secrets, sensitive surfaces) and issues a signed verdict a human\n  can review. There is no model in the gate.\n- **The CI gate is the defensible boundary; the local gate is defense-in-depth.**\n  Notari also ships an optional on-laptop runtime gate (below). That gate is a\n  deterministic speed bump and recorder at the tool-dispatch layer, it raises the\n  bar against careless agents and the common destructive/exfiltration shapes, but it\n  is **not** a hard boundary against a determined adversary actively trying to escape\n  it (write-then-run, flag reordering, inherited-env, ungated network egress are all\n  documented limits). The full, honest threat model is in\n  [docs/SECURITY-MODEL.md](docs/SECURITY-MODEL.md).\n- **Notari does not certify compliance.** The audit log is machine-verifiable evidence\n  your auditor will want to review, not the artifact they will accept on its own.\n- **Notari is not a hosted service.** It is a single Python package. The audit log and\n  signing key live on your disk in mode `0o600`; you own the key, the log, and the\n  verdict. No cloud round-trip on the verify path, no telemetry by default.\n- **Notari spends no AI tokens.** There is no LLM inside it: the verdict, the secret\n  scan, the plain-English `explain`, and the local gate are all deterministic code\n  (regex, globs, AST, counting). No model, no API key, no per-check cost, nothing to\n  meter. The only tokens in the picture are the ones your coding agent already spends\n  writing the code, and, if you *choose* to paste a Notari fix-prompt back to it, the\n  ones it spends on the fix. That is a deliberate design property, not a limitation:\n  a gate with a model in it could be prompt-injected and would bill you per PR; this\n  one cannot and does not.\n\n## Change Control in detail\n\n### 1. `notari begin`, capture the contract\n\n```bash\nnotari begin \"Add rate-limiting to the login endpoint\" \\\n            --scope \"src/auth/**\" --scope \"tests/auth/**\" \\\n            --approved-by alice \\\n            --key approver.pem \\\n            --expires-in 7 --repo owner/repo\n```\n\nWrites `.notari/contract.json`: the approved task (text or a URL to a ticket), the\n`allowed_paths` scope (globs, directory prefixes, or exact paths), the base commit\nthe change starts from, and a `contract_id`. `--key` signs it so `verify --strict`\ncan prove a human authored it (required in strict mode); `--expires-in` sets a lapse\ndeadline; `--repo` binds the approval to one repository so a signed contract can't be\nreplayed elsewhere. Commit the contract (and `.sig`) to the branch, it is the fixed\nrecord the diff is later measured against, and the Change Passport cites it.\n\n### 2. `notari verify`, gate the diff\n\n```bash\nnotari verify --strict        # enforced mode: requires the signed perimeter + contract\nnotari verify                 # local/cooperative: advisory, forgeable by the agent\n```\n\n`verify` (`src/notari/verify.py`, on top of `policy.evaluate_diff`):\n\n1. builds the authoritative changed-path inventory from\n   `git diff --name-status -z --find-renames <base_commit>..<candidate>`, both\n   rename endpoints, binary and mode-only changes included,\n2. matches every changed path (both ends of a rename) against the contract scope,\n3. scans each touched file for the 26 vendor-format secret patterns in\n   [`src/notari/secrets.py`](src/notari/secrets.py), reading the whole file from the\n   candidate commit (not just added lines) so a 100% rename or a UTF-16 file cannot\n   hide a credential,\n4. classifies sensitive surfaces (CI/workflow files, lockfiles, test deletions,\n   git configuration like `.gitattributes` that controls diff visibility),\n5. applies any logged exceptions in `.notari/exceptions.json` (ignored entirely in\n   strict mode, an unsigned waiver file cannot weaken a strict verdict),\n6. composes a verdict, **PASS**, **NEEDS_REVIEW**, or **BLOCK**: and chains a\n   `verification.run` event into the audit log.\n\n`BLOCK` exits non-zero (fails CI); `PASS` and `NEEDS_REVIEW` exit 0, so review is\nsurfaced without hard-stopping the pipeline. The whole thing is deterministic: given\nthe same inputs, the diff, the contract, the signed perimeter, the trusted approver\nkeys, the strict flag, and any (signed) exceptions, it returns the same verdict,\nexplainable line by line. There is no model in the decision path; the only clock\nreference is contract-expiry enforcement (`--expires-in`), which is an explicit\nhuman-set deadline, not a heuristic.\n\n### 3. The Change Passport\n\n`verify` writes `passport.json` (machine-readable, for downstream tooling and status\nchecks) and `passport.md` (PR-ready). The markdown footer cites the\n`verification.run` audit MAC, so the passport traces back to the tamper-evident chain\nrather than asking you to trust the document.\n\n### 4. The GitHub Action\n\n```yaml\n# .github/workflows/notari-change-control.yml\non:\n  pull_request_target:          # NOT pull_request, see \"Why pull_request_target\" below\n    branches: [main]\njobs:\n  change-control:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683  # v4.2.2\n        with:\n          ref: ${{ github.event.pull_request.head.sha }}\n          fetch-depth: 0\n          path: _pr_checkout             # isolate candidate code from trusted runtime\n          persist-credentials: false\n      # Pin to the release commit SHA, not a mutable tag, `notari status`\n      # rejects a non-SHA pin. `notari init` writes the current pin for you.\n      - uses: manumarri-sudo/notari@a084829cabacbbc7e85a5b8323580f514ad54771  # 0.4.2 release action\n        with:\n          head: ${{ github.event.pull_request.head.sha }}\n          head-sha: ${{ github.event.pull_request.head.sha }}\n          checkout-path: _pr_checkout\n          strict: \"true\"\n          block-on-review: \"true\"   # NEEDS_REVIEW fails the job too, so a review-class change can't merge unseen\n          approver-pubkeys: ${{ secrets.NOTARI_APPROVER_PUBKEYS }}\n          gate-key: ${{ secrets.NOTARI_GATE_KEY }}\n          gate-pubkeys: ${{ vars.NOTARI_GATE_PUBKEYS }}\n```\n\n**Why `pull_request_target`:** the `pull_request` event runs the PR's version of the\nworkflow, so an AI agent can modify the workflow to remove the Notari step entirely,\nand the gate never fires. `pull_request_target` runs from the base branch (main),\nso only merged code controls the gate definition (the PR cannot edit the workflow\nthat judges it). Strict mode **refuses to run** under\n`pull_request` to enforce this. **Why `path: _pr_checkout`:** isolates the candidate\ncheckout so no candidate-controlled Python module (pip.py, json.py) can shadow\ntrusted imports during Notari installation. A full secure template is at\n[`docs/secure-workflow.yml`](docs/secure-workflow.yml).\n\nThe composite action ([`action.yml`](action.yml)) runs `notari verify --strict`,\npublishes a commit Status Check, and fails the job on `BLOCK`. The approver public\nkey and gate signing key come from repo/org secrets a PR cannot read or edit, that is\nwhat makes the trust root external. If a PR has no `.notari/contract.json`,\nverification **errors and the job fails closed** (exit 2), rather than passing\nsilently, so deleting or omitting the contract cannot wave a change through.\nInitialize Change Control on the base branch (`notari begin --key`) before requiring\nthe check.\n\n## After a BLOCK: explain and fix\n\nA verdict is where most gates stop. Notari turns the block into the fix, all\nlocal, no telemetry, deterministic (no model decides anything):\n\n```bash\nnotari verify                                     # PASS · NEEDS_REVIEW · BLOCK + a passport\nnotari explain                                    # the passport in plain English + a fix per finding\nnotari explain --fix-prompt                       # a compact prompt to paste into your coding agent\nnotari agent-brief                                # the compact pre-work brief to hand an agent before it starts\n```\n\n`notari explain` reads the passport and says, per finding, what's wrong in plain\nlanguage, the exact `git` command to undo it, and a paste-ready instruction for the\nagent, plus *what Notari does not prove* (it checks the boundary, not code\ncorrectness). `--fix-prompt` and `--agent-brief` emit the compact agent surfaces\nwithout dumping the full passport into context. **Notari never uploads raw code,\ndiffs, prompts, or secret values.**\n\n## Trust spine: sign the boundary once, the agent can't forge it\n\nThe point of running in CI is that the gate lives where the agent can't disable\nit, but that only matters if the agent also can't *fake its approval* or *edit\nits own judge*. Notari closes both with Ed25519 (verification uses a public key,\nforging needs the private key, not the same secret, which is what the symmetric\nHMAC chain could never give you):\n\n<picture>\n  <source media=\"(prefers-color-scheme: dark)\" srcset=\"https://raw.githubusercontent.com/manumarri-sudo/notari/main/docs/assets/notari-trust-dark.svg\">\n  <img src=\"https://raw.githubusercontent.com/manumarri-sudo/notari/main/docs/assets/notari-trust-light.svg\" alt=\"Why the agent can't cheat: editing the CI workflow is defeated by base-branch control, planting an approver key is defeated by trust roots pinned outside the checkout, forging the verdict is defeated by the gate signature, and hiding changes behind symlinks or submodules surfaces as NEEDS_REVIEW.\" width=\"920\">\n</picture>\n\n```bash\nnotari keygen --out approver.pem          # human's key; keep the private half OFF the box\nnotari guard --key approver.pem --forbid 'migrations/**' --forbid 'src/auth/**'\n#   ^ signs .notari/perimeter.json once. From now on EVERY agent's PR is checked\n#     against it automatically, you're paged only when one crosses the line.\n```\n\n- **A human signs the perimeter once; every PR is enforced with no per-change\n  approval.** That is the answer to \"I'm not going to sit and approve a hundred\n  agents.\" Forbidden paths and gate-tamper edits BLOCK; secrets BLOCK by default\n  (a signed perimeter can relax to review); in-bounds work PASSes silently.\n- **The agent can't authorize itself (in strict mode, with an off-box trust\n  root).** Editing the perimeter, the approver keys (`.notari/approvers/**`), or\n  the workflow that runs the gate is itself a BLOCK (gate-tamper detection), and\n  tampering with a signed perimeter or contract invalidates the signature.\n  The contract is protected by its Ed25519 signature, not the gate-tamper glob\n  set, in strict mode an unsigned or forged contract is a BLOCK because its\n  provenance check fails. The committed\n  `.notari/approvers/*.pub` set is a *convenience*, not a boundary: in strict mode\n  only the externally-pinned `NOTARI_APPROVER_PUBKEYS` are trusted, because an\n  agent could otherwise plant a key in the base commit and sign its own approval.\n  Off-box keys + strict is what makes this a real claim rather than a hopeful one.\n- **The verdict can't be forged.** With a gate key (`notari verify --strict\n  --sign-key gate.pem`, or `NOTARI_GATE_KEY` in CI), the passport is signed;\n  anyone re-checks it with `notari verify-passport passport.json` and a flipped\n  verdict or untrusted signer fails.\n- **Or use GitHub's own reviews.** `notari check-approval` requires a human who is\n  not the PR author to have approved the current head commit, an agent can't\n  approve its own PR, and the approval is dismissed when new code is pushed.\n\n**What this requires to be a real boundary (not optional):** the trust root must\nlive where a PR can't edit it. Hold the approver/gate **private keys off the\nbuild machine** (a repo/org secret: `NOTARI_GATE_KEY`, `NOTARI_APPROVER_PUBKEYS`),\n**sign the contract** (`notari begin --key`) and require it in strict mode,\n**pin the Action to the release commit SHA** (not a mutable tag, and not the PR's own\ncheckout; `notari status` rejects a non-SHA pin), and make the\n**Status Check required** in branch protection. One honest caveat on that last step:\nthe required check is a bare commit **status**, and any workflow with `statuses: write`\ncan post the same `notari/change-control` context, so a compromised same-repo workflow\ncould post a green status over a real BLOCK. Notari makes that **detectable** (the\npassport fingerprint records the SHA and MAC of the status it actually wrote, which\n`notari verify-passport` cross-checks), but binding the *source* of the check so it\ncannot be spoofed at all needs a GitHub App check-run, which is on the roadmap. Until\nthen, keep `statuses: write` off untrusted workflows. With those in place the bypasses\nthe security model enumerates are closed against the attacks the test suite\nexercises (including the composite rogue-key-in-base attack, the Action-wrapper\nfail-open, and binary/rename diff coverage, all closed with regression tests);\nwithout them it is honest review automation for a cooperative agent. This has\n**not yet had an external security audit**, so treat \"closed\" as \"closed against\nwhat we currently test,\" and treat\n[docs/SECURITY-MODEL.md](docs/SECURITY-MODEL.md), which tracks the residual open\nitems, as authoritative over this README.\n\n## The local runtime gate (optional, defense-in-depth)\n\nSeparately from CI, Notari can gate an agent's tool calls **as they happen** on your\nlaptop, via Claude Code's `PreToolUse` hook. This is the supporting surface, scoped\nhonestly as defense-in-depth, a deterministic speed bump and recorder, not a hard\nboundary.\n\n```bash\nnotari onboard      # detect agents, install the hook, pick a risk preset\n```\n\nFrom the next session, every `Bash` / `Edit` / `Write` / `NotebookEdit` passes\nthrough the compiled-regex classifier in [`src/notari/policy.py`](src/notari/policy.py):\n`rm -rf`, `git push --force`, `DROP TABLE`, `vercel --prod`, `npm publish`, `.env`\nreads, and the CVE-2025-59536 subcommand-chain bypass are critical-class by default.\nCritical calls are refused with a plain-English reason and a single-use, 10-minute\n`notari approve <token>`; on macOS the approval is hardware-attested through Touch ID\non the Secure Enclave. Files an agent writes are scanned with the same 26 secret\npatterns and an AST pass ([`code_scan.py`](src/notari/code_scan.py)) that flags\ndestructive Python shapes (`shutil.rmtree`, `os.system`, `exec(b64decode(...))`)\nbefore a later `python foo.py` can run them. Every decision lands in\n`~/.notari/audit.log.jsonl`, HMAC-SHA256 chained for tamper evidence.\n\nSupporting surfaces on the local gate, all derived from the same chain on read:\n`notari receipts` (per-session did / changed / uncertain / to-verify), `notari trifecta`\n(lethal-trifecta exposure: untrusted input + private data + exfil vector),\n`notari pins` (tool-description pinning against MCP rug-pulls), and `notari audit`\n(review and verify the chain). `notari scan-secrets` runs the secret detectors standalone over files.\n\n**Why this is defense-in-depth and not the headline:** an application-layer gate can\nbe routed around by a capable adversary (the limits above). The provable boundary is\nthe CI gate, which runs where the agent cannot disable it and produces a signed\nrecord. The local gate is real and useful, but its honest claim is \"raises the bar\nand records everything,\" not \"cannot be bypassed.\"\n\n## The signed audit log\n\nBoth surfaces write to `$NOTARI_HOME/audit.log.jsonl`, mode `0o600`. Each entry's\n`mac` is `HMAC-SHA256(prev_mac || canonical(payload))` under your installation's key\n(auto-generated at first run, stored at `$NOTARI_HOME/key`, mode `0o600`). Writes take\n`fcntl.flock(LOCK_EX)` and re-read the tail MAC inside the lock so concurrent writers\ncan't break the chain. A sealed `<log>.head` high-water-mark lets `verify` detect\ntrailing truncation, not just edits and insertions.\n\n```bash\nnotari audit verify\n# chain intact: 72739 entries verified.\n```\n\nThat count is from 76 days of real dogfooding on the maintainer's machine\n(2026-05-07 to 2026-07-22, re-measured at release); your own\nlog starts at one. The chain is locally tamper-evident (and optionally externally\nanchored), see [docs/SECURITY-MODEL.md](docs/SECURITY-MODEL.md) for exactly what\nthat does and does not buy.\n\n## Install\n\nThe one-command path is at the top of this README (`uvx notari init`). The rest:\n\n```bash\nuvx notari begin --help     # no install; run any subcommand once\npipx install notari         # or install the CLI persistently\npip install notari          # or into an existing venv\n```\n\nOne name everywhere: the PyPI dist, CLI binary, import path, config dir\n(`~/.notari/`), and env vars (`NOTARI_*`) are all `notari`.\nFor a development checkout: `git clone https://github.com/manumarri-sudo/notari && cd\nnotari && pip install -e .`.\n\n## CLI surface\n\n```\nnotari begin          capture the approved task into .notari/contract.json\nnotari verify         compare the diff to the contract, emit PASS / NEEDS_REVIEW / BLOCK\nnotari explain        turn the passport into plain-English remediation (--fix-prompt, --agent-brief, --format html)\nnotari fix-prompt     compact paste-ready fix prompt for the coding agent\nnotari agent-brief    compact pre-work brief to hand an agent before it starts\nnotari onboard        first-run setup for the local gate (detect agents, install hook)\nnotari audit          review what got blocked / allowed / asked; verify the chain\nnotari approve <tok>  confirm a pending one-shot approval (Touch ID on macOS)\nnotari approvals      list (hashed ids) / revoke pending approval tokens\nnotari receipts       per-session did / changed / uncertain / to-verify\nnotari trifecta       lethal-trifecta exposure tracking\nnotari pins           tool-description pins (anti-poisoning, anti-rug-pull)\nnotari scan-secrets   scan files for hardcoded credentials\nnotari scan-prompts   scan files for prompt-injection-shape patterns (signal only)\nnotari commit-hook-install   add a session-summary block to the commit message template\nnotari doctor         diagnose the install\nnotari version        print the version\n```\n\nRun `notari --help` for the full list (including `night`/`off` gate-weakening toggles,\nread [docs/SECURITY-MODEL.md](docs/SECURITY-MODEL.md) before using them).\n\n## What's shipping today vs. on the roadmap\n\n**Shipping (0.3.0), with on-disk evidence:** the Change Control flow\n(`begin`/`verify`/passport + GitHub Action), the HMAC-chained audit log (32k+ entries\ndogfooded, truncation-detectable, `notari audit verify` clean), the local PreToolUse\ngate with Touch ID approvals and 26-pattern secret detection, the write-then-run AST\nscan, and the read-side surfaces (receipts, trifecta, pins). The full test\nsuite (`uv run pytest` for the live count), `ruff`, `ruff format`, and\n`mypy --strict` are green and enforced in CI.\n\n**Roadmap (not shipping today, do not assume present):** PR-comment rendering of the\npassport (the Action publishes a Status Check today), the lethal-trifecta enforcement\nescalation as a CI signal, per-tool hook adapters for Cline / Aider / Continue /\nWindsurf / Zed, WebAuthn for cross-platform hardware-attested approval, and a\n`from notari import gate` BYO-agent library API. The MCP proxy, OS sandbox, desktop\ndashboard, and out-of-band notification channels that earlier previews shipped were\n**removed** in the Change Control pivot and are not coming back in this line.\n\n## Security\n\n`notari` is itself security-critical code. The threat model, the honest list of what\nthe gate stops well and where it can be bypassed, and the responsible-disclosure\naddress are in [SECURITY.md](SECURITY.md) and [docs/SECURITY-MODEL.md](docs/SECURITY-MODEL.md).\nWhen PyPI publishing is wired, releases will be signed via\n[PyPI Trusted Publishing](https://docs.pypi.org/trusted-publishers/) with PEP 740\nattestations (planned, not yet in place).\n\n## Contributing\n\nMissed a dangerous-action class, a scope-bypass on the diff parser, or a false\npositive? Open an issue with a repro. Adapters live under `src/notari/adapters/`.\n\n## License\n\nMIT. See [LICENSE](LICENSE). Vendored third-party code is attributed in\n[NOTICE](NOTICE). Version history in [CHANGELOG.md](CHANGELOG.md). Repo:\n[github.com/manumarri-sudo/notari](https://github.com/manumarri-sudo/notari).\n\n---\n\nBuilt with assistance from Claude (Anthropic).\n",
  "bytes": 32271,
  "sha": "3e85fc88cf40300dc66df9af163fd799c3b15c3ea81bd2ba1f55de84f47d736d",
  "repo_slug": "manumarri-sudo/quill",
  "fonte": "repo",
  "truncated": false,
  "api": "https://agentalog.com/api/listings/mcp_io_github_manumarri_sudo_quill_4df8332e/readme"
}