Skip to content

selfradiance/ActionProof

Repository files navigation

ActionProof

ActionProof is a narrow local TypeScript CLI that returns a deterministic allow or deny decision before a credentialed, side-effecting request would execute.

What It Does Not Do

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

What It Does

v0.3.0 supports exactly three request types:

  • send_email
  • form_submit
  • pay_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:

  1. a request JSON file
  2. a policy JSON file

It then:

  • validates both files with Zod
  • evaluates the request in a fixed rule order
  • returns allow or deny
  • 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.

Request Shapes

send_email

{
  "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"
}

form_submit

{
  "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.

pay_invoice

{
  "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.

Policy Shape

{
  "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".

Deterministic Evaluation Order

send_email rules run in this order:

  1. allowedTools
  2. allowedActorIds
  3. blockedRecipients
  4. allowedRecipientDomains
  5. allowAttachments
  6. maxRecipients
  7. maxSubjectLength
  8. maxBodyChars
  9. allowedPurposes

form_submit rules run in this order:

  1. FORM_SUBMIT_TOOL_ALLOWED
  2. FORM_SUBMIT_ACTOR_ALLOWED
  3. FORM_SUBMIT_URL_VALID
  4. FORM_SUBMIT_DOMAIN_NOT_BLOCKED
  5. FORM_SUBMIT_DOMAIN_ALLOWED
  6. FORM_SUBMIT_BLOCKED_FIELD_NAMES
  7. FORM_SUBMIT_METHOD_ALLOWED
  8. FORM_SUBMIT_APPROVAL_ASSERTED
  9. FORM_SUBMIT_ALLOW

pay_invoice rules run in this order:

  1. PAY_INVOICE_TOOL_ALLOWED
  2. PAY_INVOICE_ACTOR_ALLOWED
  3. PAY_INVOICE_POLICY_PRESENT
  4. PAY_INVOICE_VENDOR_ALLOWED
  5. PAY_INVOICE_CATEGORY_ALLOWED
  6. PAY_INVOICE_CURRENCY_ALLOWED
  7. PAY_INVOICE_AMOUNT_POSITIVE_INTEGER
  8. PAY_INVOICE_AMOUNT_WITHIN_LIMIT
  9. PAY_INVOICE_APPROVAL_ASSERTED
  10. PAY_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.

CLI Usage

Build first:

npm install
npm run build

Run 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.json

If --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.

Exit Codes

  • 0 = allow
  • 1 = deny
  • 2 = invalid input or runtime error

Scripts

  • npm test
  • npm run typecheck
  • npm run build

Fixtures

Sample request and policy files live in fixtures/.

Decision Log

See DECISIONS.md.

License

MIT