# abom-cli

The reference implementation of [ABOM](../spec/) — the Agent Bill of Materials.
Scan a repo, emit a **signed** Composition Manifest, **verify** it, and **enforce**
it at the tool boundary — inline or through the MCP broker.

```bash
pip install abom-cli        # (until published: pip install -e .)
abom scan .                 # → abom.json (signed with ed25519)
abom verify abom.json       # check signature
abom verify abom.json --policy policy.json   # + enforce a policy (exit 1 on violations)
abom gate abom.json --tool wire_transfer     # ALLOW/DENY one action (deny-by-default)
abom broker abom.json --upstream https://mcp.internal/rpc  # mediate an agent's tool calls
```

## Commands

| Command | What it does |
|---|---|
| `abom scan [PATH]` | Detect agent components (models, prompts, tools, MCP servers, frameworks, vector stores, guardrails) and emit a signed Composition Manifest. `-o -` writes to stdout. |
| `abom verify [FILE]` | Verify the ed25519 signature; with `--policy`, enforce model allowlist / residency / egress / approval rules. Non-zero exit on findings (CI-friendly). `--trusted-key` pins the authorized signer. |
| `abom gate FILE --tool T` | Decide ALLOW/DENY for one action against the signed manifest, deny-by-default, notarized. `--trusted-key` refuses a manifest it can't authenticate. Exit 1 = DENY. |
| `abom broker FILE --upstream URL` | Run the MCP broker: mediate every `tools/call` against the signed manifest before forwarding to an upstream MCP server. Denied calls return a notarized JSON-RPC error and never reach upstream. `--log` for a durable decision log. |
| `abom witness LOG.mlog` | Cosign a durable transparency log with an independent witness key; refuses a rewrite across runs. Run on a second party's machine for genuine non-equivocation. |
| `abom anchor LOG.mlog` | RFC 3161-timestamp the log root via a TSA (`--tsa`, default freetsa.org) — a third-party existence proof (eIDAS Art. 41 with a qualified TSA). |
| `abom witness-verify COSIG.json` | The auditor's check: verify a witness cosignature with no trust in the operator. |
| `abom keygen` | Show (or create) the local ed25519 signing key (`~/.abom/signing_key.pem`, override with `ABOM_KEY`). |
| `abom version` | Print the tool and spec versions. |

All commands accept **`-v`** (info) / **`-vv`** (debug) / **`-q`** (errors only) / **`--json-logs`** (NDJSON for CI) — logs go to stderr, so the ABOM on stdout stays clean.

### Example

```
$ abom scan .
  ABOM · my-agent @ 1.2.0
  models                  3  gpt-4o-mini, claude-3-5-sonnet, OpenAI (SDK)
  frameworks              2  LangChain, LangGraph
  MCP servers             2  filesystem, github
  tools                   1  lookup_customer
  prompts                 1  prompts/system.txt
  signed: ed25519 · key 5846eabc738b3542
  → wrote abom.json
```

## How detection works

`abom scan` is a static scanner (pure stdlib + `cryptography`):
- **Dependencies** (`requirements*.txt`, `pyproject.toml`, `package.json`) → frameworks, model SDKs, vector stores, guardrails.
- **Source** → concrete model names (`gpt-4o`, `claude-*`, …) and `@tool`-decorated functions.
- **Prompt files** (`*.prompt`, `prompts/*.txt|md`) → hashed.
- **MCP configs** (`mcp.json`, `claude_desktop_config.json`, …) → MCP servers.

Each component records `detected_from` so the manifest is auditable. The output
validates against [`spec/abom-0.1.schema.json`](../spec/abom-0.1.schema.json).

## Signing

`abom scan` signs with **ed25519** (`cryptography`). The key lives at
`~/.abom/signing_key.pem` (override with `ABOM_KEY`); the public key + a short
`key_id` are embedded so `abom verify` is self-contained. Pin the trusted signer
with `--trusted-key <key_id>` on `verify` / `gate` / `broker`: a manifest
re-signed by any other key is then refused, so "signed" authenticates *who* — not
just integrity. `LocalSigner` is dev/CI; a `KMSSigner` seam keeps the private key
in a KMS/HSM for production.

## Dev

```bash
make install          # pip install -e ".[dev]"
make test             # pytest (audit chain, scanner, signing)
make scan && make verify
make build            # wheel + sdist + twine check
python demo/demo.py   # generate → verify → tamper-evidence walkthrough
```

## The MCP broker

`abom broker` moves enforcement **out of the agent's process**. The agent points
its MCP client at the broker instead of the MCP server; the broker checks every
`tools/call` against the signed manifest and only forwards allowed ones. A denied
call is answered with a notarized JSON-RPC error and never reaches the upstream
server — deny-by-default becomes *structural* for brokered tools rather than
cooperative. It does **not** stop out-of-band calls (raw HTTP / subprocess) the
agent makes without transiting the broker; pair it with network egress
containment for that.

The optional `[mcp]` extra (`pip install 'abom-cli[mcp]'`) pulls in `httpx` for
the broker's upstream transport and for `abom scan --mcp`.
