Policy Guide
Write policy.json rules for the Gateway — schema, matching semantics, and examples.
The Gateway enforces a JSON policy file loaded at startup. When no
.oxvault/policy.json exists, a built-in default policy applies. Create the
default on disk to edit it:
oxvault-gw init # writes .oxvault/policy.json
Point the Gateway at a specific file with --policy <path>.
File format
{
"version": 1,
"default_action": "allow",
"rules": [
{
"id": "block-ssh-keys",
"enabled": true,
"description": "Block access to SSH private keys",
"match": {
"tool": "*",
"arguments_contain": ["/.ssh/id_"],
"arguments_match": ["regex pattern"],
"response_contain": ["substring"],
"response_match": ["regex pattern"],
"method": "tools/call",
"direction": "client_to_server"
},
"action": "block",
"alert": true
}
]
}
Top level
| Field | Type | Description |
|---|---|---|
version | int | Schema version (currently 1) |
default_action | string | Action applied when no rule matches: allow or block |
rules | array | Ordered list of rules |
Rule
| Field | Type | Description |
|---|---|---|
id | string | Rule identifier, surfaced in the audit log |
enabled | bool | Disabled rules are skipped entirely |
description | string | Human-readable explanation (shown in alerts/audit) |
match | object | Conditions under which the rule fires |
action | string | allow or block (redact is planned but not yet implemented) |
alert | bool | Emit an alert whenever the rule matches, even if the action is allow |
match conditions
| Field | Type | Fires when… |
|---|---|---|
tool | string (glob) | The tools/call tool name matches the glob (* matches all) |
arguments_contain | string[] | Any argument value contains one of these substrings |
arguments_match | string[] | Any argument value matches one of these regexes |
response_contain | string[] | The serialized response contains one of these substrings |
response_match | string[] | The serialized response matches one of these regexes |
method | string (glob) | The JSON-RPC method matches (for non-tool messages) |
direction | string | Limit to a direction: client_to_server, server_to_client, or "" (both) |
Matching semantics
- Rules are evaluated in order; the first match wins. Put your most specific rules first.
- Within a rule, all non-empty
matchfields must pass (AND). A rule with both atoolglob andarguments_containonly fires when the tool matches and an argument matches. - Within a list field, any one element is enough (OR).
arguments_contain: ["a", "b"]fires if an argument containsaorb. - Matching is case-sensitive. Substrings are literal;
*_matchfields are Go regular expressions, compiled once at startup (an invalid regex fails policy load). - Tool and method globs use
filepath.Matchsemantics, with*as a special-cased “match everything”. - When no rule matches,
default_actionis applied.
Default policy
If you never run oxvault-gw init, these five rules apply. They block the most common
credential-theft argument patterns and alert on credential-shaped values in responses:
| Rule ID | Action | Fires on |
|---|---|---|
block-ssh-keys | block + alert | Arguments containing /.ssh/id_, /.ssh/authorized_keys, or /.ssh/config |
block-aws-credentials | block + alert | Arguments containing /.aws/credentials or /.aws/config |
block-env-files | block + alert | Arguments containing .env |
block-mcp-config-access | block + alert | Arguments containing /mcp.json, /.cursor/mcp, or /claude_desktop_config |
alert-credential-in-response | allow + alert | A response matching an AWS key (AKIA…), OpenAI key (sk-…), GitHub PAT (ghp_…), or PEM private key header |
alert-credential-in-response is server_to_client only and its action is allow — responses are
never blocked, only surfaced, which matches the Gateway’s runtime inspection order.
How policy relates to --scanner-block-severity
The policy engine and the scanner run as separate stages (policy first, then the scanner). The
policy engine gives you exact, declarative control over specific tools, arguments, and responses.
--scanner-block-severity controls the broader, pattern-based scanner rules (injection, secrets,
poisoning) by severity. A message can be blocked by either:
- Policy
block→ immediate JSON-RPC error, regardless of scanner settings. - Scanner finding at or above
--scanner-block-severity→ blocked (arguments) or alerted (responses are alert-only).
Use the policy for precise allow/deny rules you can reason about, and the scanner threshold as a severity-based safety net. See the Gateway inspection order.
Worked examples
Deny a tool entirely:
{
"id": "deny-shell-tool",
"enabled": true,
"description": "This server's run_shell tool is never allowed",
"match": { "tool": "run_shell" },
"action": "block",
"alert": true
}
Block writes to a sensitive directory across all tools:
{
"id": "block-etc-writes",
"enabled": true,
"description": "Block any argument referencing /etc/",
"match": {
"tool": "*",
"arguments_match": ["/etc/(passwd|shadow|sudoers)"]
},
"action": "block",
"alert": true
}
Alert (but allow) when a response looks like it contains a JWT:
{
"id": "alert-jwt-in-response",
"enabled": true,
"description": "Surface JWTs returned by the server",
"match": {
"direction": "server_to_client",
"response_match": ["eyJ[A-Za-z0-9_-]+\\.eyJ[A-Za-z0-9_-]+\\."]
},
"action": "allow",
"alert": true
}
Default-deny posture — flip default_action to block and add explicit allow rules for the
tools you trust:
{
"version": 1,
"default_action": "block",
"rules": [
{ "id": "allow-read", "enabled": true, "description": "Allow read_file", "match": { "tool": "read_file" }, "action": "allow" },
{ "id": "allow-list", "enabled": true, "description": "Allow list_dir", "match": { "tool": "list_dir" }, "action": "allow" }
]
}
Every match and decision lands in the audit log with the matching rule’s
id, so you can tune rules from real traffic.