result.blocked = true.-, and _ (required).X-API-Key) and the Secret Value.Authorization and the Secret Value to Bearer <token value>.result.blocked (required).true or BLOCK (required).result.sanitized_text), and the gateway rewrites the output. When the request is allowed, the gateway takes the complete value of this field and replaces the original detected text with it (for example, masked text). If this feature is disabled, the original text is passed through unchanged. Content rewriting takes effect only in the output direction (response review) and must be used together with the Input + Output detection direction. Content rewriting is not supported in the input direction. If the external service returns rewritten content during the input phase, or returns rewritten content when content rewriting is not enabled in the policy, the gateway ignores the replacement field and continues to determine the verdict based on the hit field.Role | Responsibilities |
AI gateway (caller) | Encapsulate a unified review envelope, initiate review requests, parse review conclusions, and perform release / blocking / rewriting based on policies. |
External security service (review service) | Receive review requests, complete content determination based on its own detection capabilities, and return review conclusions in JSON format. |
Term | Definition |
Audit Request | A detection call initiated by the gateway to the audit service |
Audit Unit | Detection object corresponding to an audit request, uniquely identified by audit_id. It can be a complete request/response Body, an SSE Event Batch, or the complete content of an oversized Body transferred in segments. |
Decision | Decision returned by the audit service for an audit unit: allow, block, or replace |
Inline Envelope | Audit request form in which the Body is directly embedded in content.body, as opposed to "segmented transfer" |
Event Batch | An audit unit composed of one or more complete SSE Events in streaming response scenarios |
Transfer | A complete transfer process consisting of the begin, chunk, and end phases in oversized Body scenarios |
protocol field. Based on this, the review service can select a dedicated parser or directly perform generic detection on content.body.Item | Convention |
Request Method | POST |
Request Content-Type | For inline envelope audit requests and the Begin and End phases of an oversized Body, the Content-Type is application/json; for the Chunk phase of an oversized Body, it is application/octet-stream. |
Response Content-Type | The response of the audit service must be application/json. |
Protocol version request header | X-CNGW-Audit-Schema-Version: 2.0, which the gateway carries in all audit requests. |
Authentication | Both parties agree on a fixed Header. The Header name and value are configured on the gateway side, corresponding to API Key (Header) in the console. |
Status code semantics | For normal audit conclusions (including block), HTTP 2xx must be returned. A non-2xx return indicates audit service call failure (rather than content violation), and the gateway handles it according to the configured detection failure policy. |
decision = "block". In custom field mapping mode, the hit verdict field takes the hit value.{"schema_version": "2.0","audit_id": "7c96777a-5f8f-4e58-9b16-2fdc0de3a7ba","direction": "request","protocol": {"category": "mcp","name": "mcp-streamable-http","version": "2025-03-26"},"http": {"method": "POST","path": "/mcp","headers": {"content-type": ["application/json"],"accept": ["application/json, text/event-stream"],"mcp-protocol-version": ["2025-03-26"]}},"streaming": {"mode": "possible","format": "sse","unit": "full_body"},"content": {"media_type": "application/json","encoding": "json","body": {"jsonrpc": "2.0","id": 17,"method": "tools/call","params": {"name": "knowledge_search","arguments": { "query": "Please introduce this product" }}}},"gateway_context": {"request_id": "req-123","gateway_id": "gateway-abc123","route_id": "route-456","service_id": "service-789"}}
Field | Type | Constraint | Description |
schema_version | string | Required | Fixed to 2.0 |
audit_id | string | Required | Unique ID of the current audit unit. In standard response mode, it must be echoed back as is. In custom field mapping mode, echoing back is not required. |
direction | string | Required | request (request for review) or response (response for review) |
protocol | object | Required | Identification result of the original service protocol. For details, see the original service protocol identifier. |
http | object | Required | Metadata of the original HTTP request or response |
streaming | object | Required | Streaming intent in the request direction, or actual streaming review status in the response direction |
content | object | Required | Original protocol Body (inline envelope) or transfer description (segmented transfer) |
gateway_context | object | Optional | The gateway automatically supplements this field based on the current request context, and it is not filled in by the caller. |
Field | Type | Description |
method | string | Original request method, carried only in request for review |
path | string | Original request path, excluding Query String, carried only in request for review |
status_code | int | Original response status code, carried only in response for review |
headers | object | Header names are uniformly converted to lowercase, and values are uniformly string arrays. |
query | string | Original Query String, not carried by default, appears only after the gateway explicitly enables Query passthrough |
method, path, and headers. Regular response review submissions carry status_code and headers.content-type, accept, mcp-protocol-version, and a2a-version are passed through. Other headers are determined by the gateway allowlist configuration.authorization, cookie, x-api-key, and mcp-session-id are not sent with review requests.content.body according to content.encoding and SHOULD NOT assume that the Body is always a JSON object.Field | Type | Description |
media_type | string | Original media type. For JSON, the type is the original Content-Type with parameters removed and converted to lowercase. |
encoding | string | Body encoding method: json / text / base64 |
body | mixed | Content encoded by encoding; not present during segmented transfer (Begin phase) |
transfer | object | Transfer description, only present in the Begin phase of segmented transfer for oversized Body |
Original Body | encoding | body Value |
JSON | json | A complete JSON value, which can be an object, array, or scalar |
Text | text | Original string (original text/* type) |
Binary | base64 | Base64 string (media type remains unchanged) |
Empty Body | base64 | Empty string ""; SSE empty termination unit uses text encoding, see Stream End Semantics. |
GET /sse in MCP Legacy SSE is used only to establish the response stream. The request phase is skipped directly, and streaming response review can still proceed afterward.Field | Type | Constraint | Description |
request_id | string | Optional | Request ID, which may be inherited from the value passed in by the client. |
trace_id | string | Optional | Trace ID, which may be inherited from the value passed in by the client. |
gateway_id | string | Optional | Gateway instance ID |
route_id | string | Optional | Matched route ID |
service_id | string | Optional | Matched service ID |
workspace_id | string | Optional | Workspace ID |
consumer_id | string | Optional | Consumer ID, populated after successful authentication. |
tenant_id | string | Optional | Reserved field, not output when no stable data source is available. |
gateway_id, route_id, service_id, workspace_id, and consumer_id obtained after successful authentication come from the gateway runtime context and can serve as stable governance dimensions.request_id and trace_id may be inherited from client-provided values and can be used for log correlation. They SHOULD NOT be used alone as a basis for trusted identity.mode | Applicable Direction | Description |
none | Request/Response | The original request does not express streaming return intent, or the response is non-streaming. |
requested | Request | The original request explicitly requires streaming return. |
possible | Request | The original protocol may return SSE, such as MCP Streamable HTTP. |
active | Response time | Streaming response review is in progress and only occurs in Event Batch review. |
mode is requested or possible, the review unit is unit = "full_body". When mode = "none", format and unit may be omitted.mode = "none". For field rules of streaming responses, see Streaming Response Review.protocol field declares which service protocol semantics content.body should be parsed according to.Field | Type | Constraint | Description |
category | string | Required | Protocol category: llm / dify / a2a / mcp / agent |
name | string | Required | Protocol name. See the table below. |
version | string | Optional | Protocol version. See the notes below for its source. |
operation | string | Optional | Operation identifier. Currently output only for LLM protocols. |
Scenario | protocol.category | protocol.name | Identification Basis |
OpenAI Chat Completions | llm | openai-chat-completions | Path /v1/chat/completions |
OpenAI Responses | llm | openai-responses | Path /v1/responses |
Anthropic Messages | llm | anthropic-messages | Path /v1/messages |
A2A JSON-RPC | a2a | a2a-jsonrpc | JSON-RPC call method |
A2A HTTP+JSON | a2a | a2a-http-json | application/a2a+json, :send, or :stream |
MCP Streamable HTTP | mcp | mcp-streamable-http | MCP Streamable HTTP |
protocol.name, or it can directly perform general detection on the complete content.body.protocol.version is v1, and protocol.operation is chat.completions, responses, and messages, respectively.protocol.version is taken from the A2A-Version Header in the request. If the request does not carry this Header, this field may be omitted.protocol.version is taken from the MCP-Protocol-Version Header in the request. If the initialize request has not completed version negotiation, this field is omitted.direction = "request";http.method, http.path, and http.headers.content.body represents the complete service protocol Body at the time of review execution according to content.encoding, including modifications made to the request body by higher-priority plugins. JSON is a complete JSON value, and byte-level format consistency is not guaranteed.direction = "response" and http carrying status_code. The following is an example of A2A JSON-RPC response review:{"schema_version": "2.0","audit_id": "b238ce7f-8616-4914-b21f-a65f144cf90f","direction": "response","protocol": {"category": "a2a","name": "a2a-jsonrpc","version": "1.0"},"http": {"status_code": 200,"headers": { "content-type": ["application/json"] }},"streaming": { "mode": "none" },"content": {"media_type": "application/json","encoding": "json","body": {"jsonrpc": "2.0","id": "task-001","result": {"id": "task-001","status": { "state": "completed" },"artifacts": [{ "parts": [ { "kind": "text", "text": "Content returned by the A2A Agent" } ] }]}}},"gateway_context": { "request_id": "req-123" }}
{"schema_version": "2.0","audit_id": "audit-stream-0003","direction": "response","protocol": { "category": "mcp", "name": "mcp-streamable-http", "version": "2025-03-26" },"http": {"status_code": 200,"headers": { "content-type": ["text/event-stream"] }},"streaming": {"mode": "active","format": "sse","unit": "event_batch","stream_id": "req-123:response","sequence": 3,"final": false,"context_before": {"media_type": "text/event-stream","encoding": "text","body": "...previous approved context tail..."}},"content": {"media_type": "text/event-stream","encoding": "text","body": "data: {\\"jsonrpc\\":\\"2.0\\",\\"id\\":17,\\"result\\":{\\"content\\":[{\\"type\\":\\"text\\",\\"text\\":\\"Content returned for the current batch\\"}]}}\\n\\n"},"gateway_context": {"request_id": "req-123","gateway_id": "gateway-abc123","route_id": "route-456","service_id": "service-789"}}
Field | Type | Description |
mode | string | Fixed to active during streaming response review. |
format | string | Currently fixed to sse. |
unit | string | Fixed to event_batch, indicating that the current audit unit consists of one or more complete SSE Events. |
stream_id | string | Remains unchanged within the same logical response stream for correlation and observation; global uniqueness is not guaranteed, and it must not be used as an isolation key for security identity or persistent state. |
sequence | int | Sequence number of the actual review unit within the current logical response stream, strictly increasing from 1. |
final | bool | Whether the current Batch is the last review unit of this logical response stream. |
context_before | object | Optional released history tail, used only to identify cross-Batch risks and not part of the current rewritable content. |
audit_id is generated for each Event Batch. In standard response mode, the audit_id of the current Batch must be returned. The audit_id is not used for cross-Batch correlation, and stream_id should be used instead.content and context_before, and should not rely solely on stream_id to maintain cross-Batch state on the server side over the long term.content.body is the complete raw text of the SSE Event currently pending review, and may contain one or more Events.content.media_type is fixed to text/event-stream, and content.encoding is fixed to text.context_before.body may only be the tail of the preceding text and is not guaranteed to form a complete SSE Event by itself. The review service can use it for auxiliary judgment, but must not incorporate it into the replacement result.context_before has been delivered to the client, and subsequent Batches must not modify or retract it.schema_version and audit_id of the current Batch must be returned. The handling behavior for each review conclusion is as follows:Review Conclusion | Gateway Behavior |
allow | Forward the current Batch to the client as is. |
block | Terminate the response stream. Because the HTTP 200 response header is usually already sent in SSE scenarios, the client receives event: error instead of a new HTTP 403. |
replace | Allowed to return only when stream response rewriting is enabled. content must be the complete replacement value for the current entire Event Batch (not a Patch) and consist of complete SSE Events. After replacement, continue processing subsequent Batches. If the replacement content contains a termination Event recognizable by the protocol, end the response stream. |
replace response:{"schema_version": "2.0","audit_id": "audit-stream-0003","decision": "replace","reason_code": "CONTENT_REDACTED","content": {"media_type": "text/event-stream","encoding": "text","body": "data: {\\"jsonrpc\\":\\"2.0\\",\\"id\\":17,\\"result\\":{\\"content\\":[{\\"type\\":\\"text\\",\\"text\\":\\"Replaced safe content\\"}]}}\\n\\n"}}
final = true based on their respective protocol termination Events.final = true. If review units have been generated, final = true has not yet appeared, and review bypass has not been entered, an empty termination unit with final = true and content.body = "" is sent, whose encoding is text. In the event of an abnormal interruption, receipt of a termination unit is not guaranteed.allow or block, and is not allowed to return replace.final = true, no subsequent Event Batches are generated for the same stream_id.Review Conclusion | Console Configuration Item | Final Gateway Behavior |
allow | | Pass through the current moderation unit as is. |
block | Action taken = Block | Block the request or terminate the response stream, and return the interception message. |
replace | Action taken = Block and Content rewriting enabled (output direction only; replacement is not performed when the action taken is Observe). | Replace the current audit unit entirely with the content returned by the audit service; replacement takes precedence over blocking. |
schema_version, audit_id, or decision to be returned.{"schema_version": "2.0","audit_id": "7c96777a-5f8f-4e58-9b16-2fdc0de3a7ba","decision": "allow"}
{"schema_version": "2.0","audit_id": "7c96777a-5f8f-4e58-9b16-2fdc0de3a7ba","decision": "block","reason_code": "CONTENT_RISK","message": "The content does not comply with the security policy."}
reason_code and message are optional fields. Whether message is used as a client prompt is determined by the gateway policy. It takes effect when the Interception Prompt Message in the console is configured as "Read from Response", and falls back to the default message when the read result is empty.tools/call (direction = "response"):{"schema_version": "2.0","audit_id": "audit-response-001","decision": "replace","reason_code": "CONTENT_REDACTED","content": {"media_type": "application/json","encoding": "json","body": {"jsonrpc": "2.0","id": 17,"result": {"content": [ { "type": "text", "text": "Replaced safe content" } ],"isError": false}}}}
replace is returned, the following constraints must be met:content object;content.media_type and content.encoding must be exactly the same as those in the submitted request.content.body represents the entire current review unit after replacement, and is a complete replacement value rather than a Patch.replace is returned during the input phase or when content rewriting is not enabled, the gateway **converts it to block** and then processes it according to the observation/interception policy.Item | Convention |
Trigger Condition | Determined by the gateway configuration. By default, segmentation is triggered when the size exceeds 1 MiB. The access party should not hardcode this threshold. |
Storage Capacity | By default, a maximum of 32 MiB is received. Requests exceeding this limit do not enter the review process. The access party should not hardcode this threshold. |
Chunk Size | Determined by the content.transfer.chunk_bytes declared in the Begin phase. The gateway dynamically adjusts it based on the Body size. |
Timeout | begin, all chunks, and end share the same total transfer timeout, subject to the configuration actually delivered by the gateway. When a policy is created in the console, it corresponds to total detection timeout (default: 500 ms, range: 50 to 5000 ms). |
Streaming Intent | The gateway does not fully parse the Body again before Begin. When the streaming intent cannot be clearly determined from the path or Header, streaming.mode is conservatively marked as possible, and this value does not indicate that the response is necessarily streaming. |
Rewrite Capability | The current segmentation protocol does not support replace and only returns allow or block. |
Content-Type: application/jsonX-CNGW-Audit-Schema-Version: 2.0X-CNGW-Audit-Id: <audit_id>X-CNGW-Audit-Transfer-Phase: beginX-CNGW-Audit-Transfer-Id: <transfer_id>
protocol, http, streaming, and gateway_context are the same as those in the inline envelope. content no longer contains body and instead carries a transfer description:{"content": {"media_type": "application/json","encoding": "json","transfer": {"transfer_id": "transfer-123","total_bytes": 5242880,"sha256": "<SHA-256 hexadecimal value of the complete original Body>","chunk_bytes": 262144}}}
{"schema_version": "2.0","audit_id": "<audit_id>","transfer_id": "transfer-123","status": "continue","next_sequence": 1}
Content-Type: application/octet-streamX-CNGW-Audit-Schema-Version: 2.0X-CNGW-Audit-Id: <audit_id>X-CNGW-Audit-Transfer-Phase: chunkX-CNGW-Audit-Transfer-Id: transfer-123X-CNGW-Audit-Transfer-Sequence: 1X-CNGW-Audit-Transfer-Offset: 0
sequence = N is received, the review service returns:{"schema_version": "2.0","audit_id": "<audit_id>","transfer_id": "transfer-123","status": "continue","next_sequence": 2}
Content-Type: application/jsonX-CNGW-Audit-Schema-Version: 2.0X-CNGW-Audit-Id: <audit_id>X-CNGW-Audit-Transfer-Phase: endX-CNGW-Audit-Transfer-Id: transfer-123
{"schema_version": "2.0","audit_id": "<audit_id>","transfer_id": "transfer-123","phase": "end","chunks": 20,"total_bytes": 5242880,"sha256": "<SHA-256 hexadecimal value of the complete original Body>"}
sequence, offset, total_bytes, and sha256.media_type: JSON can be parsed as a JSON value, text is read using the corresponding character set, and binary remains raw bytes. The content.encoding in the Begin phase describes how the content is represented in the inline envelope and is not the transfer encoding of the chunks. Even if its value is base64, you must not perform Base64 decoding again on the reassembled chunks.allow or block and must also echo back schema_version, audit_id, and transfer_id as-is:{"schema_version": "2.0","audit_id": "<audit_id>","transfer_id": "transfer-123","decision": "allow"}
フィードバック