HTTP request
│
▼
┌──────────────────────────────────────────────────────────────┐
│ Envoy / Istio / Kong (Proxy-Wasm host) │
│ │
│ ┌────────────────────────────────────────────────────────┐ │
│ │ proxy-wasm-evidence.wasm │ │
│ │ │ │
│ │ 1. EvidenceFilter (HTTP context) │ │
│ │ ├── Reads :method, :path from request headers │ │
│ │ ├── Extracts x-b3-traceid, x-agent-id headers │ │
│ │ ├── Calls infer_side_effect_class(method, path) │ │
│ │ ├── Builds RiskContext → compile_recording_policy │ │
│ │ ├── Produces ActionEvidence with recording_mode │ │
│ │ └── Sets x-aep-recording-mode response header │ │
│ │ │ │
│ │ 2. aep-core (shared types & logic) │ │
│ │ ├── RecordingMode (validation / delta / full) │ │
│ │ ├── SideEffectClass classification enum │ │
│ │ ├── compile_recording_policy (risk → mode mapping) │ │
│ │ ├── ActionEvidence, AepRecord, ProvGraph │ │
│ │ └── BundleSigner (Ed25519 DSSE envelope) │ │
│ └────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ HTTP response (with AEP headers) │
└──────────────────────────────────────────────────────────────┘
↕ x-b3-traceid / x-agent-id headers link both layers
┌──────────────────────────────────────────────────────────────┐
│ wasmagent-js process-internal firewall │
│ (@wasmagent/mcp-firewall) │
│ → shared trace_id joins gateway evidence to tool-call │
│ evidence for full causal chain │
└──────────────────────────────────────────────────────────────┘
│
▼
open-agent-audit (full causal graph assembly)
The deployable Wasm module. It implements the Proxy-Wasm HttpContext trait
via EvidenceFilter and is loaded by the gateway host.
Key responsibilities:
- Request interception — reads HTTP method, path, trace ID, and agent ID
from incoming request headers via
on_http_request_headers. - Response decoration — during
on_http_response_headers, classifies the request's side-effects, builds anActionEvidencerecord, and sets thex-aep-recording-moderesponse header so downstream services can observe the evidence policy decision. - Evidence construction — delegates classification logic to
aep-coreand producesActionEvidencestructs with the computed recording mode.
A pure-Rust library with no Proxy-Wasm dependency. Contains the data model
and policy engine used by proxy-wasm-evidence and any future consumers.
Key types:
| Type | Purpose |
|---|---|
RecordingMode |
Enum: validation, delta, full — how much evidence to capture |
SideEffectClass |
Enum: read, mutate_local, mutate_external, network_egress, unknown |
RiskContext |
Struct: was_vetted, has_consent_anomaly, taint_chain_length, side_effect_class |
RecordingPolicy |
Output of compile_recording_policy — a mode + human-readable reason |
ActionEvidence |
Single action's evidence: action ID, tool name, state-changing flag, digests, recording mode |
AepRecord |
Top-level record: schema version, run/trace/session IDs, list of actions, optional signature |
AepSignature |
Ed25519 signature envelope: algorithm, key ID, hex-encoded signature |
ProvGraph |
PROV-DM causal graph: activities, entities, agents with ancestry traversal |
1. HTTP request arrives at gateway
│
2. EvidenceFilter.on_http_request_headers()
├── Extract method, path, trace_id, agent_id
│
3. EvidenceFilter.on_http_response_headers()
├── infer_side_effect_class(method, path)
│ GET/HEAD/OPTIONS → Read
│ POST/PUT/PATCH/DELETE
│ path contains /network/ or /webhook → NetworkEgress
│ otherwise → MutateExternal
│ other methods → Unknown
│
├── Build RiskContext {
│ was_vetted: false,
│ has_consent_anomaly: false,
│ taint_chain_length: 0,
│ side_effect_class: <above>
│ }
│
├── compile_recording_policy(risk_ctx)
│ Priority chain (first match wins):
│ 1. was_vetted → Full "tool flagged by vetting"
│ 2. has_consent_anomaly → Full "consent anomaly recorded"
│ 3. tainted + non-read → Full "tainted input reaching state-changing call"
│ 4. Unknown class → Full "unknown side-effect class"
│ 5. MutateExternal → Full "external mutation"
│ 6. NetworkEgress → Full "external mutation"
│ 7. MutateLocal → Delta "local mutation, low risk"
│ 8. Read → Validation "read-only, no anomaly"
│
├── build_evidence(action_id, tool_name, risk_ctx, timestamp, digest)
│ → ActionEvidence { recording_mode, state_changing, ... }
│
└── set response header x-aep-recording-mode: "<RecordingMode>"
aep-core::signing wraps evidence records in an Ed25519 DSSE-style envelope:
- Canonicalize — serialize the
AepRecordto JSON (including thesignature: nullfield, which is then excluded during verification). - Hash — SHA-256 of the canonical JSON bytes.
- Sign — Ed25519 signature over the hash using
ed25519_dalek. - Attach —
AepSignature { alg: "ed25519", key_id, sig: "<hex>" }is set on the record.
Verification reverses the process: clone the record, strip the signature field, re-canonicalize, hash, and verify against the Ed25519 public key.
The signing key is injected at deploy time (see deployment.md) and never hard-coded in the Wasm module.
wasmagent-proxy implements the network-boundary layer of the wasmagent
evidence model. The process-internal layer lives in
@wasmagent/mcp-firewall.
| Layer | Repo | What it observes |
|---|---|---|
| Gateway (network boundary) | wasmagent-proxy (this repo) |
HTTP ingress/egress, side-effect classification, response headers |
| Process-internal | wasmagent-js / @wasmagent/mcp-firewall |
MCP tool calls, agent decisions, capability enforcement |
Both layers share the same trace_id (carried in the x-b3-traceid header) and
the same AEP record schema. Combining their evidence produces a full causal graph
from gateway ingress through to individual agent tool calls, consumed by
open-agent-audit.