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.2.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
  • call external APIs
  • use MCP
  • integrate with AgentGate
  • put an LLM in the critical path
  • provide sandboxing
  • act as DLP
  • make broad browser security claims

What It Does

v0.2.0 has two proof surfaces:

  • send_email
  • form_submit

Given a JSON description of a proposed send_email or form_submit 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, 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.

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": ["[email protected]"],
  "cc": ["[email protected]"],
  "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.

Policy Shape

{
  "allowedTools": ["send_email", "form_submit"],
  "allowedActorIds": ["ops-bot", "support-agent-1", "browser-agent"],
  "allowedRecipientDomains": ["example.com", "vendor.test"],
  "blockedRecipients": ["[email protected]"],
  "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
  },
  "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

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/form_submit_allowed.request.json \
  --policy fixtures/policies/form_submit.policy.json \
  --output ./form-submit.decision.json

If --output is omitted, ActionProof writes ./actionproof-decision.json.

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