sable network
Buy on Index
Documentation · all sections

Guardrails

A guardrail rule set is a named set of detectors that run on your prompts and your completions inside the request frame — at the same point the outbound scrub runs, after the payload is unsealed and before anything crosses the network. Each detector is configured per direction with an action, and every evaluation is stamped on the signed receipt: which rule set, its hash, what it saw, and what it decided.

Read this part first

These are deterministic pattern detectors, not a safety classifier. There is no model here. The consequences are worth stating plainly, because a guardrail you misunderstand is worse than none:

  • They produce false positives. A support ticket quoting a customer's email address trips the PII detector. That is working as designed, and it is why the default action is allow (record only) and why enforcement is off by default.
  • They produce false negatives. A card number written in words, a secret split across two lines, an address in an image — none of those are caught.
  • The prompt-injection heuristics catch known phrasings and are trivially evaded. They match strings like "ignore previous instructions". Anyone who rewords gets through. They are useful against untargeted, copy-pasted injection in retrieved content; they are not a defence against someone attacking you specifically.
  • A guardrail is a control you can prove ran — not a guarantee. The receipt proves which rules governed a request and what they found. It does not, and cannot, prove that nothing bad got through.
  • Sable cannot show you the text that matched. This is not a missing feature. Under the privacy contract, the prompt and the completion exist only inside the request frame; nothing is written to disk or to a log. So there is no snippet to retrieve afterwards, no "show me the example" button, and no support request that can produce one. A finding is a rule, a direction, a category, a count, a severity, an action, and a decision. That is the whole record, by construction.

The detectors

RuleCategories it reportsWhat it looks for
piiCategories it reportsemail, phone, national_id, credit_card, evm_address, btc_addressWhat it looks forEmail shapes; phone numbers that carry a leading + or grouping punctuation (a bare ten-digit number is not treated as a phone number); NNN-NN-NNNN national-id shapes; 13–19 digit runs that pass a Luhn check; 0x-prefixed 40-hex addresses; bech32 and base58 Bitcoin addresses.
secretsCategories it reportsapi_key, jwt, private_key, high_entropy_hex, high_entropy_blobWhat it looks forKnown key prefixes (sk-, AKIA, ghp_, github_pat_, xoxb-, AIza, glpat-, and more), Bearer tokens, three-segment JWTs, PEM private key blocks (a certificate is not a secret), 32+ character hex runs, and 40+ character base64-ish blobs with mixed case and digits.
prompt_injectionCategories it reportsinstruction_override, role_override, exfiltration, tool_abuseWhat it looks forA short, deliberately narrow list of known phrasings. Kept narrow on purpose: a broad list flags ordinary conversations about prompts, which is how a guardrail gets switched off.
blocklistCategories it reportstermWhat it looks forYour own literal terms, matched case-insensitively. A term prefixed re: uses a tiny pattern language — literal text plus . (any character) and * (zero or more of the preceding). No alternation, classes, or anchors.

The Luhn check is the reason pii can look for card shapes at all: 4111 1111 1111 1111 is reported, and 1234 5678 9012 3456 — same shape, same length — is not, because it fails the checksum. Most order ids and invoice numbers fail it too.

Directions and actions

Each detector takes a direction — input (the prompt), output (the completion), or both — and an action:

  • allow — record the finding, change nothing. The observe mode, and the default.
  • redact — replace each match in-frame with a typed marker such as [redacted:email], so the model can still reason about the structure of the text. On the input side this happens before the upstream call, so the vendor never sees the original.
  • block — refuse. On the input side the request is refused with 422 before any upstream call, so a refused prompt never leaves the process, and you are not billed for inference that did not happen.

What guardrails do not see

Guardrails evaluate prompts and completions on the inference path, and nothing else. They do not inspect a sandbox's submitted code or its env, the arguments of an MCP tools/call, a Sable Service payload, or an image or video prompt — even where the same key policy's allowed_tools genuinely does bind the MCP path. A key carrying a rule set is not a key whose every surface is filtered, and the receipt reflects that: the guardrails block appears on inference receipts, not on mcp_call, sandbox, service, image or video ones.

This is a real scope limit rather than a roadmap note. Where you need a bound on those surfaces today, the mechanisms that do apply are the key policy (allowed_tools, deny_sandbox_network), the node-level egress policy, and spend caps — all of which bind in the request path.

Streaming: what a guardrail can and cannot do

Output-direction rules behave differently on a stream, and the difference is real rather than cosmetic. Once a token has been flushed to your client, it is gone; nothing at the gateway can recall it.

  • Detection always runs. At the end of a stream the completed text is evaluated and the findings land on the receipt exactly as they would for a buffered response.
  • Blocking only works inside a buffered window. The operator setting SABLE_GUARDRAIL_STREAM_WINDOW (bytes, 0 by default) holds back the start of the completion until that many bytes have accumulated, evaluates, and then either flushes what it held or discards it and emits a content-free event: sable.guardrail instead. With the default of 0 nothing is held back, so an output block on a stream is recorded and not applied. Anything produced after the window has already left, whatever the window is set to.
  • Output redaction is never applied to a stream. Rewriting only the tail would be a worse lie than not rewriting at all.

If you need output-side enforcement to be reliable, do not stream that request.

Creating a rule set

curl https://api.buildsable.com/v1/guardrails \
  -H "Authorization: Bearer $SABLE_SESSION_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "support-agent",
    "rules": {
      "secrets":          { "direction": "both",   "action": "block" },
      "pii":              { "direction": "output", "action": "redact" },
      "prompt_injection": { "direction": "input",  "action": "allow" },
      "blocklist":        { "direction": "both", "action": "block",
                            "terms": ["Project Chimera", "re:acme-.*-secret"] }
    }
  }'

Attaching one

Guardrails ride the existing policy attachment rather than adding a second one, so a key has exactly one place that says what governs it. Put the rule set id in a policy, then mint a key with that policy_id:

# 1. a policy that carries the guardrail set
curl https://api.buildsable.com/v1/policies \
  -H "Authorization: Bearer $SABLE_SESSION_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"name":"support","rules":{"guardrail_ruleset_id":"gr_…"}}'

# 2. a key bound to that policy
curl https://api.buildsable.com/v1/keys \
  -H "Authorization: Bearer $SABLE_SESSION_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"name":"support-agent","policy_id":"pol_…"}'

Observe before you refuse

SABLE_GUARDRAILS_ENFORCED defaults to false, the same way billing enforcement did. With it off, everything runs and everything is recorded — a block decision is reported honestly on the receipt as blocked: false, enforced: false — and nothing is refused. Watch the findings accumulate on real traffic, tune the rule set, and only then ask your operator to switch enforcement on. GET /v1/guardrails reports the deployment's current mode, and the portal shows it at the top of the Guardrails page.

What lands on the receipt

{
  "guardrails": {
    "ruleset_id": "gr_9f1c…",
    "sha256": "6d2a…",
    "findings": [
      { "rule": "pii", "direction": "input", "category": "email",
        "count": 2, "severity": "medium", "action": "redact" },
      { "rule": "secrets", "direction": "input", "category": "api_key",
        "count": 1, "severity": "high", "action": "block" }
    ],
    "blocked": true,
    "enforced": true
  }
}

The block is additive: a receipt for a request that ran under no guardrails is byte-identical to one minted before this feature existed, so nothing about your existing receipts or their signatures changed.

sha256 is the hash of the exact rules that were enforced, so editing a rule set is visible in every receipt minted afterwards — you can prove not only that a control ran, but which version of it.

A refused request

{
  "error": {
    "message": "blocked by a guardrail rule set: secrets (api_key) on the input side. Sable records the rule, the category, and a count — never the matched text, which is not stored anywhere and cannot be shown to you.",
    "type": "guardrail_blocked",
    "code": "guardrail_blocked"
  },
  "guardrails": { "…": "the same stamp as above" }
}

The response still carries x-sable-receipt headers: a refusal is a recorded outcome with its own signed proof that the control ran, not a silent drop.

Webhooks

A block-action detector that matches fires guardrail_triggered — whether or not enforcement acted on it, because an operator deciding whether to switch enforcement on needs to see exactly those. The payload carries the rule set id, the rule, the category, the direction, the findings, the decision (blocked or observed), and nothing else. See Webhooks.

Limits

  • 100 rule sets per account; 256 blocklist terms per set; each term ≤ 200 characters.
  • Detection scans the first 256 KB of each message. Longer text is not scanned past that point, and the counts describe only what was scanned.
  • At most 32 distinct findings ride on one receipt (they are aggregated by rule, direction, and category first, so this is generous); over that, the highest-severity ones are kept.
Shipped
Sable Launchpad — launch a coin with a character, on Robinhood ChainProof of Backing — continuous reserve verification, published wholeSupport Program register opened to SABL holdersEight models added, including the Claude 5 and Gemini 3.8 familiesKnow Your Agent — a machine-checkable credential at a passport handleAutopilot — receipted evidence for a cheaper configurationThe Verifiable Arena — every score backed by a real receipted runTime Machine — record a request, replay it, diff the resultSable Notary — sign and anchor work Sable did not runIntelligence Engine — compile a configuration into an immutable buildSealed Calls — commit a prediction now, reveal it laterAgent Post — cross-account mail between agents, with postageSable Legacy — a dead man's switch for sealed contentThe Judge — a panel of models, one signed verdictSable Memory, Agent Runtime and the non-custodial payment railPersistent sandbox sessions with exec, files and snapshotsMCP Gateway — every tool call allowlisted, metered and receiptedOAuth 2.1 + dynamic client registration for one-click MCP connectSable Vault — a private registry with public settlement