ActionProof is a narrow local TypeScript CLI that returns a deterministic allow or deny decision before a credentialed, side-effecting request would execute.
v0.3.0 does not do any of the following:
- control a real browser
- intercept live browser activity
- enforce the decision against a runtime
- verify approver identity
- verify signatures
- send real email
- submit real forms
- pay real invoices
- connect to payment rails
- parse real invoices
- verify invoice authenticity
- call external APIs
- use MCP
- integrate with AgentGate
- put an LLM in the critical path
- provide sandboxing
- act as DLP
- detect fraud
- provide accounting compliance
- make broad browser security claims
v0.3.0 supports exactly three request types:
send_emailform_submitpay_invoice
Given a JSON description of a proposed send_email, form_submit, or pay_invoice request and a JSON policy, ActionProof returns a deterministic allow/deny decision and a rule trace, locally, with no network calls and no LLM. It does not execute the request, intercept browser activity, pay invoices, or enforce the decision against any agent runtime.
ActionProof evaluates a proposed form submission request before execution. It evaluates the JSON intent description, not the actual browser action.
ActionProof evaluates a proposed payment-like JSON intent before execution. It checks local policy only: amount limits, vendor allowlists, category rules, currency rules, and structural approval assertion checks. It does not connect to payment rails, verify invoice authenticity, or verify human identity.
The CLI reads:
- a request JSON file
- a policy JSON file
It then:
- validates both files with Zod
- evaluates the request in a fixed rule order
- returns
allowordeny - includes the exact decisive rule code and reason
- prints readable terminal output
- writes a machine-readable JSON decision artifact
Future tool classes require fresh scope review before they are added.
{
"actor": "ops-bot",
"tool": "send_email",
"to": ["alice@example.com"],
"cc": ["vendor.contact@vendor.test"],
"subject": "Status update",
"body": "Deployment completed successfully.",
"hasAttachments": false,
"purpose": "status_update"
}{
"tool": "form_submit",
"actor": "browser-agent",
"targetUrl": "https://example.com/signup",
"method": "POST",
"fields": [
{ "name": "email", "hasValue": true },
{ "name": "name", "hasValue": true }
],
"purpose": "submit newsletter signup",
"approvalAsserted": {
"asserted": true,
"approver": "james",
"approvedAt": "2026-04-25T15:00:00Z"
}
}targetUrl must parse as a URL. method must be POST, PUT, or PATCH. Form fields contain field names and hasValue only; field values are intentionally excluded.
approvalAsserted is optional at the schema level. When policy requires it, ActionProof checks only that the assertion is structurally present: asserted is true, approver is non-empty, and approvedAt is a valid ISO 8601 datetime. It does not verify approver identity, approval recency, signatures, tokens, or keys.
{
"tool": "pay_invoice",
"actor": "agent-123",
"vendor": "Acme Cloud",
"amount_cents": 4800,
"currency": "USD",
"category": "software",
"invoice_id": "INV-1001",
"approvalAsserted": true
}pay_invoice is a local spend intent check only. It does not pay invoices, connect to payment rails, parse real invoices, verify invoice authenticity, or verify human identity. approvalAsserted is a structural boolean only; ActionProof checks whether it is true when policy requires approval, and does not verify identity, signatures, tokens, keys, or recency.
amount_cents and policy.payInvoice.maxAmountCents must be positive safe integers.
{
"allowedTools": ["send_email", "form_submit", "pay_invoice"],
"allowedActorIds": ["ops-bot", "support-agent-1", "browser-agent", "agent-123"],
"allowedRecipientDomains": ["example.com", "vendor.test"],
"blockedRecipients": ["ceo@example.com"],
"allowAttachments": false,
"maxRecipients": 3,
"maxSubjectLength": 120,
"maxBodyChars": 500,
"allowedPurposes": ["status_update", "customer_support"],
"formSubmit": {
"allowedDomains": ["example.com"],
"blockedDomains": ["bank.com", "amazon.com"],
"allowedMethods": ["POST", "PUT", "PATCH"],
"blockedFieldNames": ["password", "credit_card", "ssn", "api_key"],
"requireApprovalAssertion": true
},
"payInvoice": {
"allowedVendors": ["Acme Cloud", "Example Hosting"],
"allowedCategories": ["software", "hosting"],
"allowedCurrencies": ["USD"],
"maxAmountCents": 5000,
"requireApproval": true
},
"defaultDecision": "deny"
}defaultDecision remains fixed to "deny".
send_email rules run in this order:
allowedToolsallowedActorIdsblockedRecipientsallowedRecipientDomainsallowAttachmentsmaxRecipientsmaxSubjectLengthmaxBodyCharsallowedPurposes
form_submit rules run in this order:
FORM_SUBMIT_TOOL_ALLOWEDFORM_SUBMIT_ACTOR_ALLOWEDFORM_SUBMIT_URL_VALIDFORM_SUBMIT_DOMAIN_NOT_BLOCKEDFORM_SUBMIT_DOMAIN_ALLOWEDFORM_SUBMIT_BLOCKED_FIELD_NAMESFORM_SUBMIT_METHOD_ALLOWEDFORM_SUBMIT_APPROVAL_ASSERTEDFORM_SUBMIT_ALLOW
pay_invoice rules run in this order:
PAY_INVOICE_TOOL_ALLOWEDPAY_INVOICE_ACTOR_ALLOWEDPAY_INVOICE_POLICY_PRESENTPAY_INVOICE_VENDOR_ALLOWEDPAY_INVOICE_CATEGORY_ALLOWEDPAY_INVOICE_CURRENCY_ALLOWEDPAY_INVOICE_AMOUNT_POSITIVE_INTEGERPAY_INVOICE_AMOUNT_WITHIN_LIMITPAY_INVOICE_APPROVAL_ASSERTEDPAY_INVOICE_ALLOW
If every check passes, the request is allowed. If any check fails, the first failing rule becomes the decisive rule. If a request is not explicitly covered by the allow conditions, defaultDecision: "deny" is what wins.
Build first:
npm install
npm run buildRun with explicit paths:
node dist/cli.js \
--request fixtures/requests/pay_invoice_allowed.request.json \
--policy fixtures/policies/pay_invoice.policy.json \
--output ./pay-invoice.decision.jsonIf --output is omitted, ActionProof writes ./actionproof-decision.json.
The output path must not resolve to the same path as the request or policy input file.
0= allow1= deny2= invalid input or runtime error
npm testnpm run typechecknpm run build
Sample request and policy files live in fixtures/.
See DECISIONS.md.
MIT