PyxGrant / Platform

Discover, decide, approve, contain, prove.

Finders map who is running. Logs replay what already happened. PyxGrant is the decision in the middle: the path to the sink, dual-control holds, a freeze that reaches every plane, and a receipt a skeptic can check without us. One Go binary. No vendor cloud in the path. Tools, models, other agents, browsers, and payments go through the same engine.

one call, five stagesfrom the demo
  1. Discover
  2. Decide
  3. Approve
  4. Contain
  5. Prove
Architecture

Agents on the left, systems on the right, one decision in the middle.

Callers

Claude Code, Cursor, and other coding agents
MCP clients
Your own agents
Apps calling a model API
Peer agents over A2A
Agentic browsers

PyxGrant

ProxiesMCP over stdio or HTTP, model API, A2A, AG-UI
Hook and decision serviceClaude Code, Cursor, and your code over loopback
Decision engineyour policy file, errors refuse
Approval queuesigned decisions, timeout refuses
Audit loghash-chained, sealed with Ed25519

Systems

MCP tool servers
Model providers
Other agents
The person in the chat
Websites
Payment rails and plant controllers
Discover

Find the agents nobody registered.

Read-only scans of each machine list the coding agents, the MCP servers they load, and plaintext secrets in their configs. Exports from your proxy, IdP, and SaaS tenants fill in the rest.

On the machine. agents scan finds Claude Code, Codex, Gemini, Copilot, Cursor, and Windsurf. discover flags MCP servers not routed through PyxGrant, and lists local model ports (Ollama, LM Studio, vLLM-style) as [UNVERIFIED] — an open port is not proof the runtime is ungoverned. discover -grants classifies an OAuth or enterprise-app export.
Running now. processes lists agent and MCP processes and whether each is behind a gateway, including ones started outside any config.
Shadow AI. shadow ingest classifies proxy, DNS, extension, process, and OAuth signals: sanctioned, shadow, personal tenant, or imitation.
pyxgrant demo · section 18shadow AI
sanctioned       enterprise ChatGPT
personal-tenant  chatgpt.com
shadow           meeting notetaker (Fireflies)
imitation        lookalike host
shadow           browser extension (Grammarly)
found            local model (Ollama), by process

4 signal families seen · 6 of 6 checks pass
Decide

One decision per call — and on the path that led here.

Each call is checked against your policy file: who is asking, which tool, what the arguments would do, and what the session has already read. The answer is allow, allow with redactions, hold for a person, or refuse. If the engine can't decide, it refuses.

Prompt injection. Inbound text is scored with patterns and a local classifier. Prompt injection is not solved; new phrasing can pass. Under hardened, injection then an outbound call is blocked even without the lethal-trifecta private-read; flag-level ingress is blocked, not recorded-and-allowed; a memory write that launders an untrusted directive is refused; an injection-driven block kills the grant on the request, the tool result, the error, and sampling. On the model plane, a tainted run that forces a tool call is refused before forward. A strong hit on any plane quarantines a fingerprint the others refuse. pyxgrant laptop -apply installs the managed hook and starts hook-daemon -engine -profile hardened; it does not pass -admin-approval. A bare subprocess is still a smaller PEP. Pinning, path taint, the blast-radius gate, and a human hold sit behind the score. pyxgrant demo -consequence-close is the miss→stop walk on the Go binary. -guardrail-preset lakera is an opt-in request shape, not a certified integration.
Path sequence. Under the hardened profile, untrusted content that entered the session with no human break since can hold or refuse an irreversible or egress sink. A poisoned resources or prompts read, or a notification, taints that path the same way a tool result does. The finding names which source tainted the path. Off by default outside hardened.
Principal bind. A grant minted for one principal is refused if this session acts as another. Bind happens before the call runs.
Egress cage (hardened). A wrapped child MCP server's outbound HTTP(S) is pinned through the loopback cage by default. Clients that honour proxy env vars are seen; a raw socket is not. pyxgrant laptop (or egress-cage alone) can launch the agent or IDE under a Linux 6.7+ Landlock TCP lock to the sensor port — opt-in, UDP uncaged, not Windows or macOS. Windows child bounds are pyxgrant launch -confine / -confine-fs / -deny-net, not Landlock. Doctor FAILs an installed hook with the cage off. OS egress default-deny is still a separate MDM step.
Blast radius. A recursive wipe or a DELETE with no WHERE is scored and stopped before it reaches the server.
Session taint. Once a session reads HR data, that data can't leave through a public tool. Unrelated content still can.
Tool pinning. A tool that shadows another, or whose description changes after approval, is quarantined and hidden from the model.
Stream hold. A restricted or irreversible tool result on SSE is buffered until the verdict. A deny releases no bytes.
Live path. MCP reputation and tenant isolation sit on the live tool-call path, not only as unused packages.
Reconcile before retry. When an independent read-back shows the first attempt did not land, a retry is held for a person, then admitted exactly once if they approve.
A2A result vs authorization. When enabled, a peer's declared task/context on the result must match what the request authorized. A divergent id is withheld in enforce. Off by default; it is a self-consistency check on an untrusted peer, not an independent system-of-record read-back.
Unadvertised tools. When integrity.deny_unadvertised_tools is on (Hardened sets it), a tools/call for a name the server never listed is refused. Separately, tools/list is filtered to the tools the bound grant allows — the agent never sees a capability it was not granted.
path · untrusted → irreversiblehardened
path   untrusted ingest · no human break
sink   hostile/send_email · egress
source tool-injection
decision  HOLD  path:taint-source:tool-injection

$ pyxgrant approve …
human break · path cleared for later hops
Approve

Risky calls wait for a person, and silence means no.

A held tool call, model request, agent message, or browser step waits in one queue. An approver signs the decision with their own Ed25519 key. If nobody answers in ten minutes by default, the call is refused. The wait is configurable.

Signed approvals. Once approver keys are set, an unsigned or wrong-key decision is refused — on the tool plane, the model plane, and A2A. Until then, approvals are refused, not waved through. Receipts and revocation on those planes match the tool plane when the path is routed through us.
Quorum when the rule says so. require_approvers: N waits for N distinct Ed25519 approver keys, each signing the exact payload — including on the coding-agent hook. A shared-secret HMAC name does not count toward a quorum. Signing twice as one key counts once. The first valid deny refuses the call. One approver is still the default.
What they saw. The signature can bind the rendering shown to the approver, not only the bytes that will run. The Slack and Teams card shows a prefix of that shown hash. A swap between the display and the payload is refused.
No self-approval. The agent's own principal can't approve its call unless you turn that on.
Same bytes. What runs is re-hashed against what was approved, so a swapped call is refused.
Uncompensatable desk. pyxgrant desk lists irreversible actions that landed and have no runnable undo, so they do not close silently.
held call · require_approvers: 2quorum
HOLD pay/wire
     dual-control:2-of-m · shown_hash bound

ops  Ed25519 · payload + what they saw
sec  Ed25519 · same payload · same shown
released · both bound the bytes that run
Contain

Freeze, resume, revoke, or contain with two people.

Freeze pauses calls in flight and holds new ones until you resume — on the tool plane, the model plane, A2A, and AG-UI, including sampling and elicitation responses already returning, and mid-stream, tool-result, and A2A-response legs. Freezing a parent grant expands over the grant lineage on every plane, so a child-bound model or A2A call is held with the tool subtree. It can cover every session, one session, a workflow run, a grant, or a whole class of action such as payments or sending, while read-only work carries on.

Revoke — then prove it. Revoking a grant ends every child grant and every session bound to it, and signs a receipt. pyxgrant grants revoke|verify re-reads the provider afterward: TARGET_VERIFIED means the grant is gone; TARGET_FAILED means the provider still shows it live after a self-reported OK. pyxgrant residual probes named targets; a probe error is UNPROVEN, not clean.
Lifecycle in the console. Assurance can replay a signed session (session-replay — verify the chain, never re-run tools), run a recovery drill that seals a compensation plan without executing undos, show standing-access discover for dormant or over-privileged grants, and present the offline evidence pack path. Revoke-with-proof and pack export stay deliberate operator actions.
Dual-control stop. contain act records pause and isolate with a 15-minute undo. Panic and kill_fleet need a second, different operator on -confirm, and they kill the target grant on the store, the revocation file, and the signed death stream. Break-glass dual-control is single-use: a replayed nonce is refused.
From the console. Freeze a reporting host by hand, or automatically when its risk grade reaches a level you set — including a graduated hitl.risk_ladder (flag → hold → block → freeze) when you turn it on. Resume is always manual.
containmentpyxgrant
$ pyxgrant freeze -grant g_root…  lineage · all planes
$ pyxgrant freeze -class pay      payments only
$ pyxgrant session-replay s_…
$ pyxgrant recover s_…            drill · no undo run
$ pyxgrant grants revoke g_9ec3… --apply
  killed · then re-read provider
TARGET_VERIFIED · gone on the IdP
TARGET_FAILED · still live after “OK”
Prove

A record that holds up without trusting us.

Each decision is written to a hash-chained log, and the chain head is sealed with Ed25519. No plaintext secret is written to it. With the post-quantum driver enabled, receipts also carry an ML-DSA-65 co-signature over the same bytes. Anyone with the public key can verify offline.

Closed-loop. Authorized and what actually ran either match, diverge, or are not yet verified. No receipts is not all-clear.
One-file offline evidence. pyxgrant evidence prove exports a self-contained pack, verifies it against a pinned published key, prints re-derived prevented-spend and closed-loop counts, then flips one byte of a copy so you see FAIL. The pack carries grant-kill certificates. A confirming read-back can mint a signed PhaseVerified receipt. Verify binds principal, grant, and verdict. evidence export|verify is the same pack without the walk. Embedded-key verify is integrity and re-derivation — not authenticity — until you pin an external key.
Bound custody pack. An evidence pack can fold policy, approvals (including per-approver quorum files), receipts, and compensation under one signed manifest. The same external Signer path that seals the audit chain can sign the pack — including a PKCS#11 HSM so the private key never leaves the device.
Prove a negative. pyxgrant noninclusion signs that a declared action did not occur across a run. Absent is refused unless every chain for the run verifies. A forged “absent” fails re-derivation.
Coverage attest. A signed list of what this binary enforces. Not a coverage percentage.
Flight Recorder. One signed, hash-linked trace across every session that shares a run.
Model-plane receipts. When a model call is in the path, the receipt binds which model produced it. Tool receipts stay as they were. Older receipts still verify. Model and A2A audit chains are Ed25519-sealed like the tool plane.
Search it. ediscovery searches by person, session, tool, or decision, and places legal holds.
Account for it. disclosures lists every record where PHI, PII, or a secret was found, and whether it was sent, redacted, or refused. showback totals debit and prevented spend from the signed ledger.
Keep the key apart. If the signing key sits beside the log, a local admin could shorten and re-sign it. PyxGrant warns when it does. Under hardened, the seal must use an external KMS or HSM. A concrete PKCS#11 backend is validated against SoftHSM; the default binary stays software Ed25519 unless you build with the pkcs11 tag. prove -anchor must live in a tree that does not overlap the pack. witness enroll packs an external public key so a seal can be checked off this host. compliance -export writes assessor evidence with authorization_status=not-authorized.

Read the trust page →

pyxgrant evidenceoffline
$ pyxgrant evidence export -out pack.json
pack.json · receipts · chains · JWKS · closed-loop

$ pyxgrant evidence verify pack.json -require-pq
PASS hybrid PQ + Ed25519 · re-derived offline
FAIL one flipped byte

$ pyxgrant demo -killchain
15/15 monitor → … → prove

$ pyxgrant demo -consequence-close
CLEAN  post forwards
MISS   scorer finds nothing · path tainted
STOP   same sink refused
PROVE  pack verifies offline
The console

What the security owner sees.

These are screenshots of pyxgrant console, taken on a test machine while an agent's calls were waiting. Three calls are held for a person, each bound to the exact payload it will run with. Every row carries a signed receipt. Fleet search and paging sit in the same console. Host-sensor rows are shown with chain_verified:false until verified. Governed model-route can advise or enforce. Configuration and administration screens write through a versioned config store — append-only history, rollback as a new version, fail-closed on a bad document — so operators are not editing live policy files by hand.

The PyxGrant console: three pending approvals for refund_payment, delete_record, and run_command, the identity registry, and counts of calls inspected, blocked, held, modified, and redacted.
The console's activity log: held calls awaiting human approval with their timeouts and bound payload hashes, one call refused after its approval was aborted, each marked signed.
A held call that loses its caller is refused, not run. That is the "approval-aborted" row. Configuration lives under /config in the same console; the store defaults to files on disk, or Postgres when you build and point it there.
Coverage

Mapped to OWASP, including the gaps.

This is the product's own map, printed by pyxgrant compliance. It marks what a runtime gateway covers, what it covers in part, and what sits outside it.

LLM Top 10 · 5 covered

Prompt injectionLLM01results
Sensitive disclosureLLM02redact
Output handlingLLM05sanitise
Excessive agencyLLM06hold
Unbounded consumptionLLM10budget

LLM Top 10 · partial or none

Supply chain: tools, not weightsLLM03partial
System prompt leakageLLM07partial
Data and model poisoningLLM04none
Vector and embeddingLLM08none
MisinformationLLM09none

Agentic Top 10 · 8 covered

Goal hijackASI01covered
Tool misuseASI02covered
Identity abuseASI03covered
Supply chainASI04covered
Unexpected code execASI05covered
Memory poisoningASI06covered
Human-agent trustASI09covered
Rogue agentsASI10covered

Agentic Top 10 · 2 partial

Inter-agent communicationASI07partial
Cascading failuresASI08partial
MCP and A2A are proxied and can use mTLS. QUIC and DoH are mapped, not intercepted. Cascades are bounded and can be frozen; a full pipeline graph is not.

Run it on your own machines.

One Go binary, your policy file, and no vendor cloud in the decision path.