RFC: Companion Example: CID Resolution + Host-Managed HTTP for Hook Plugins
Status: Draft example for review Last updated: 2026-04-08 Related RFC: rfc-hook-plugin-system.md
1. Purpose
Section titled “1. Purpose”This document is illustrative only. It shows how a plugin could:
- resolve
X-Viper-State-CIDthrough the host-mediatedResolveCIDcapability - read the full request body through
ReadRequestBody - call an external system such as VirusTotal through host-managed
DoHTTPRequest - interpret
VerifiedByHostdifferently inrequest.startvsproxy.pre - rely on verified signer identity exposed by the host instead of reparsing RFC 9421 headers
- observe
SizeBytesseparately from truncation when CID data is returned
The example assumes:
- the plugin is granted
cid.resolve - the plugin is granted
request.body.read - the plugin is granted
http.request - the gateway has stored the referenced state attestation blob and can report
whether
X-Viper-State-CIDis request-referenced or verified by host logic - the plugin is enabled on both
request.startandproxy.pre - outbound
HTTP(S)policy allows the configured VirusTotal destination and injects required auth from secret references at dispatch time
2. Example request
Section titled “2. Example request”Incoming request headers:
POST /v1/openai_api/v1/chat/completions HTTP/1.1X-Viper-State-CID: bafybei-example-state-cidX-Viper-Nonce: 0f3f5c89X-Viper-Timestamp: 1774966562000Content-Type: application/jsonSignature: sig1=:...:Content-Digest: sha-256=:...:At request.start, the plugin sees the raw inbound headers in HookEventEnvelope.RequestHeaders plus resolved route metadata for supported proxy routes. Signer identity has not been established yet, so verified identity fields such as AgentID and SignerKeyID are still absent.
Illustrative envelope snippet:
{ "event_name": "request.start", "correlation_id": "req_123", "provider": "openai", "operation": "chat_completions", "gateway_path": "/v1/openai_api/v1/chat/completions", "upstream_path": "/v1/chat/completions", "request_headers": { "X-Viper-State-CID": ["bafybei-example-state-cid"], "X-Viper-Nonce": ["0f3f5c89"], "X-Viper-Timestamp": ["1774966562000"] }}Illustrative request body:
{ "model": "gpt-4.1", "messages": [ { "role": "user", "content": "Summarize the contents of https://example-malware.test/dropper.zip" } ]}3. Example native plugin flow
Section titled “3. Example native plugin flow”This is an illustrative native Go shape, not a final SDK contract:
func Handle(ctx context.Context, env HookEventEnvelope, caps HostCapabilities) (HookDecision, error) { cid := firstHeader(env.RequestHeaders, "X-Viper-State-CID") if cid == "" && env.EventName != HookRequestStart { return HookDecision{Allow: true}, nil }
bodyRead, err := caps.ReadRequestBody(ctx, ReadRequestBodyOptions{MaxBytes: 128 * 1024}) if err != nil { return HookDecision{Allow: true, Tags: map[string]string{"request_body": "unavailable"}}, nil }
extractedURLs := extractCandidateURLs(bodyRead.Bytes) if len(extractedURLs) == 0 { return HookDecision{Allow: true}, nil }
var stateRes CIDResolution if cid != "" { stateRes, err = caps.ResolveCID(ctx, cid, ResolveCIDOptions{MaxBytes: 64 * 1024}) if err != nil { return HookDecision{Allow: true, Tags: map[string]string{"state_cid_lookup": "error"}}, nil } }
vtReqBody := buildVirusTotalIntentPayload(extractedURLs) vtResp, err := caps.DoHTTPRequest(ctx, OutboundHTTPRequest{ URL: "https://www.virustotal.example/api/v3/intents/scan", Method: "POST", Headers: map[string][]string{ "Content-Type": {"application/json"}, }, Body: vtReqBody, }) if err != nil { return HookDecision{ Allow: true, Tags: map[string]string{ "intent_scan": "error", }, }, nil }
scan := parseVirusTotalIntentResponse(vtResp.Body) return applyIntentPolicy(env, stateRes, bodyRead, scan), nil}4. What happens at request.start
Section titled “4. What happens at request.start”At request.start, the plugin may use all three capabilities if granted:
ReadRequestBodyto inspect the full inbound request bodyResolveCIDto inspectX-Viper-State-CIDDoHTTPRequestto call an external system such as VirusTotal
Illustrative ReadRequestBody result:
RequestBodyRead{ Bytes: []byte(`{"model":"gpt-4.1","messages":[...]}`), Truncated: false,}Illustrative ResolveCID result at request.start:
CIDResolution{ CID: "bafybei-example-state-cid", Found: true, Bytes: []byte("{...canonical state attestation json...}"), SizeBytes: 35812, Truncated: false, Source: "blob_store", RequestReferenced: true, VerifiedByHost: false,}Illustrative outbound request:
OutboundHTTPRequest{ URL: "https://www.virustotal.example/api/v3/intents/scan", Method: "POST", Headers: map[string][]string{ "Content-Type": {"application/json"}, }, Body: []byte(`{"urls":["https://example-malware.test/dropper.zip"]}`),}Illustrative outbound response:
OutboundHTTPResponse{ StatusCode: 200, Headers: map[string][]string{ "Content-Type": {"application/json"}, }, Body: []byte(`{"malicious":true,"confidence":"high","indicator_count":1}`), Truncated: false,}Interpretation at request.start:
- The CID is allowed to be resolved because it is referenced by the current request.
- The request body is available only because
request.body.readwas explicitly granted. - The outbound VirusTotal call is allowed only because
http.requestpolicy permitted that destination/method. SizeBytesreports the underlying blob size when known, independently of whether the returned payload was truncated toMaxBytes.- The blob is still untrusted at this stage.
VerifiedByHost=falsemeans the plugin must not treat the resolved state as authenticated agent state yet.- VirusTotal results are still external system input and should be handled according to plugin policy and confidence thresholds.
Reasonable request.start behavior:
- scan extracted URLs or indicators against an external system
- emit observability tags
- perform lightweight pre-verify heuristics
- deny only on policies that are explicitly allowed to act on untrusted request-derived data
Not reasonable at request.start:
- enforcing policy that assumes the blob is already authenticated
- treating blob contents as verified agent identity or verified state attestation material
5. What changes at proxy.pre
Section titled “5. What changes at proxy.pre”After core verification completes, the same plugin can call the same capabilities again during proxy.pre.
The envelope may now also include verified identity fields such as:
{ "agent_id": "agent:demo", "signer_key_id": "did:key:z6Mkt2HssyBGaThf7ofBwQvVcPLXnBWvgZp7s3K4DkCyLLjz"}The host may now return:
CIDResolution{ CID: "bafybei-example-state-cid", Found: true, Bytes: []byte("{...canonical state attestation json...}"), SizeBytes: 35812, Truncated: false, Source: "blob_store", RequestReferenced: true, VerifiedByHost: true,}Interpretation:
- The CID is still marked
RequestReferenced=truebecause it came from the request context. - The resolved blob is still the same content.
SignerKeyIDis available directly from the envelope, so plugins do not need to parse RFC 9421 headers to recover the verified signer DID/key identifier.VerifiedByHost=truemeans the host has already verified or traversed this CID as part of request verification/attestation handling.- The plugin may now combine parsed state fields with outbound scan results in enforcement policy that requires verified state.
- If the resolved blob contains references to additional CIDs, the plugin may resolve them with the same
cid.resolvegrant when those blobs are present in the gateway blob store. Their ownRequestReferencedmetadata may be false.
6. Concrete policy example
Section titled “6. Concrete policy example”Suppose the plugin enforces:
“deny requests that contain malicious external URLs when the verified agent state says outbound retrieval is enabled for this route.”
At request.start:
- the plugin reads the full request body
- the plugin extracts candidate URLs
- the plugin calls VirusTotal through
DoHTTPRequest - the plugin may resolve
X-Viper-State-CID - the plugin may parse the blob
- the plugin may tag scan findings and candidate state fields
- the plugin may deny immediately only if policy explicitly allows acting on untrusted request-derived indicators before verification
At proxy.pre:
- the plugin may resolve the same CID again
- the plugin may re-run or reuse the external lookup if policy requires a fresh result
- now
VerifiedByHost=true - the plugin parses the same blob and checks whether verified state enables outbound retrieval
- the plugin may deny if verified state permits the risky action and the scan result is malicious
Illustrative enforcement snippet:
func enforceIntentScan(ctx context.Context, env HookEventEnvelope, caps HostCapabilities) (HookDecision, error) { cid := firstHeader(env.RequestHeaders, "X-Viper-State-CID") bodyRead, err := caps.ReadRequestBody(ctx, ReadRequestBodyOptions{MaxBytes: 128 * 1024}) if err != nil { return HookDecision{Allow: true}, nil }
urls := extractCandidateURLs(bodyRead.Bytes) if len(urls) == 0 { return HookDecision{Allow: true}, nil }
vtResp, err := caps.DoHTTPRequest(ctx, OutboundHTTPRequest{ URL: "https://www.virustotal.example/api/v3/intents/scan", Method: "POST", Headers: map[string][]string{ "Content-Type": {"application/json"}, }, Body: buildVirusTotalIntentPayload(urls), }) if err != nil { return HookDecision{Allow: true}, nil }
scan := parseVirusTotalIntentResponse(vtResp.Body)
if env.EventName == HookRequestStart { return HookDecision{ Allow: true, Tags: map[string]string{ "intent_scan_malicious": strconv.FormatBool(scan.Malicious), "state_verified": "false", }, }, nil }
if cid == "" { return HookDecision{Allow: true}, nil }
stateRes, err := caps.ResolveCID(ctx, cid, ResolveCIDOptions{MaxBytes: 64 * 1024}) if err != nil || !stateRes.Found { return HookDecision{Allow: true}, nil }
state, err := parseStateAttestationData(stateRes.Bytes) if err != nil { return HookDecision{ Allow: true, Tags: map[string]string{ "intent_scan_malicious": strconv.FormatBool(scan.Malicious), "state_verified": strconv.FormatBool(stateRes.VerifiedByHost), }, }, nil }
if env.EventName == HookProxyPre && stateRes.VerifiedByHost && state.RoutePolicy.AllowOutboundRetrieval && scan.Malicious { return HookDecision{ Allow: false, StatusCode: 403, Code: "malicious_intent_detected", Message: "verified agent state and external intent scan indicate malicious retrieval intent", }, nil }
return HookDecision{ Allow: true, Tags: map[string]string{ "intent_scan_malicious": strconv.FormatBool(scan.Malicious), "state_verified": strconv.FormatBool(stateRes.VerifiedByHost), }, }, nil}7. What this example clarifies
Section titled “7. What this example clarifies”HookEventEnvelope.RequestHeaderscan includeX-Viper-State-CID.- Plugins do not fetch CID data or external scan data with raw runtime network access.
- CID resolution is host-mediated, grant-gated, and backed by the gateway blob store in V1.
- Full request body access is separate and opt-in.
- External
HTTP(S)calls are host-managed and policy-gated. SignerKeyIDlets plugins consume the verified signer identity directly instead of reparsing signature headers.SizeByteslets plugins distinguish total blob size from truncation of the returned payload.VerifiedByHostis the trust boundary signal for whether a plugin may treat resolved state as verified input.- The same CID can be resolved in multiple lifecycle stages, but its trust posture can change as the request moves through verification.