Snitchproxy — Assertion DSL Specification¶
Version: 0.1.0-draft Status: Design phase Config file:
snitchproxy.yaml
Overview¶
Snitchproxy inspects outbound HTTP traffic and evaluates it against a set of assertions. Each assertion answers one question: "Is this request safe to leave the network?"
The DSL is designed around three concepts:
- Match — which requests does this assertion apply to?
- Allow / Deny — what condition triggers a pass or violation?
- Presets — curated rule packs for common leak patterns.
Top-level structure¶
# snitchproxy.yaml
presets:
- <preset-name>
- <preset-name>
assertions:
- name: <unique-id>
description: "<human-readable explanation>"
severity: critical | high | warning | info
match: # optional — omit to apply to all traffic
...
deny: # "if this is true, it's a violation"
...
# OR
allow: # "if this is NOT true, it's a violation"
...
An assertion must have exactly one of deny or allow, never both.
Match block¶
The match block scopes an assertion to specific traffic. If omitted, the assertion applies to all proxied requests. All fields within a match are AND'd together. Multiple values within a single field are OR'd.
match:
host: "<glob-pattern>" # single host
host: # OR — matches any
- "*.analytics.google.com"
- "*.segment.io"
path: "/v1/charges" # exact path
path: "/webhooks/**" # glob: ** matches nested segments
method: POST # single method
method: [POST, PUT, PATCH] # any of these methods
header: # match on request header values
Content-Type: "multipart/form-data"
X-Request-Source: "batch-*" # glob supported in values
Pattern syntax¶
| Pattern | Meaning | Example |
|---|---|---|
* | Matches one segment / any characters | *.stripe.com → api.stripe.com |
** | Matches zero or more path segments | /api/** → /api/v1/users/123 |
| Exact string | Literal match | api.stripe.com |
Matching semantics¶
| Field | Multiple values | Combination with other fields |
|---|---|---|
host | OR | AND with path, method, header |
path | OR | AND with host, method, header |
method | OR | AND with host, path, header |
header | AND (all headers must match) | AND with everything else |
Allow / Deny blocks¶
deny — violation if the condition IS true¶
Use deny when you want to reject requests that exhibit a specific trait.
# "If the Authorization header is present, that's a violation"
deny:
header: Authorization
condition: present
allow — violation if the condition is NOT true¶
Use allow when you want to require requests to have a specific trait.
# "If the Idempotency-Key header is missing, that's a violation"
allow:
header: Idempotency-Key
condition: present
Condition reference¶
Header conditions¶
# Header exists
deny:
header: X-Internal-Token
condition: present
# Header has exact value
allow:
header: Content-Type
condition: equals
value: "application/json"
# Header matches regex
deny:
header: Cookie
condition: matches
pattern: "session_id=|_internal_auth="
# Header does not match regex
allow:
header: Authorization
condition: not-matches
pattern: "^Basic "
Body conditions¶
# Body matches regex pattern
deny:
on: body
condition: matches
pattern: "\\b\\d{3}-\\d{2}-\\d{4}\\b" # SSN pattern
# Body contains exact string
deny:
on: body
condition: contains
value: "BEGIN RSA PRIVATE KEY"
Query parameter conditions¶
# Query param exists
deny:
on: query
param: api_key
condition: present
# Query param matches pattern
deny:
on: query
param: token
condition: matches
pattern: "^sk_live_"
TLS conditions¶
# Minimum TLS version
allow:
on: tls
condition: version-gte
value: "1.2"
# Client certificate present (for mTLS)
allow:
on: tls
condition: client-cert-present
Source IP conditions¶
# Source IP in CIDR range
allow:
on: source-ip
condition: in-cidr
value: "10.0.0.0/8"
# Multiple CIDR ranges
allow:
on: source-ip
condition: in-cidr
value:
- "10.0.0.0/8"
- "172.16.0.0/12"
Compound conditions (all)¶
When an assertion requires multiple conditions to be true simultaneously, use the all block. Every condition inside must pass — short-circuits on first failure.
- name: payment-endpoint-hardening
description: "Payment requests must use TLS 1.2+, JSON, and carry idempotency key"
severity: critical
match:
host: "api.stripe.com"
method: POST
allow:
all:
- header: Content-Type
condition: equals
value: "application/json"
- header: Idempotency-Key
condition: present
- on: tls
condition: version-gte
value: "1.2"
v2 consideration: An
anyblock (OR semantics) may be added in a future version for cases like "must authenticate via Bearer OR mTLS."
Presets¶
Presets are curated rule packs that expand into a set of assertions. They provide sensible defaults for common compliance and security scenarios.
Available presets¶
| Preset | What it checks |
|---|---|
pci-dss | Credit card PANs (Luhn-validated), track data, CVVs in body and query |
aws-keys | AKIA* access key IDs, secret key patterns, STS tokens |
common-auth | Authorization, Cookie, X-API-Key, Bearer tokens, Set-Cookie |
pii | SSN, email addresses, phone numbers (international), dates of birth |
gcp-keys | GCP API keys, service account JSON fragments |
private-net | Private IPs (RFC 1918) leaked in X-Forwarded-For, X-Real-IP, Host |
Overriding preset rules¶
Every preset rule has a name in the format <preset>/<rule-name>. You can override its severity or scope it to specific traffic:
presets:
- pci-dss
assertions:
# Downgrade credit card check severity in staging
- name: pci-dss/credit-card-in-body
severity: warning
match:
host: "*.staging.internal"
# Disable a preset rule entirely
- name: pci-dss/track-data-in-body
enabled: false
Severity levels¶
| Level | Meaning | Default CI behavior |
|---|---|---|
critical | Data breach risk — immediate action required | Fail the build |
high | Significant leak risk — should block in production | Fail the build |
warning | Potential issue — review recommended | Pass with warning |
info | Informational finding — no action required | Pass silently |
Severity thresholds are configurable at the top level:
Complete example¶
# snitchproxy.yaml — E-commerce API egress policy
presets:
- pci-dss
- common-auth
- pii
fail-on: high
assertions:
# Global: never leak internal session tokens
- name: no-internal-session
description: "Internal session tokens must never leave the network"
severity: critical
deny:
header: X-Internal-Session
condition: present
# Global: never send AWS keys anywhere
- name: no-aws-keys-in-body
description: "AWS credentials must not appear in outbound request bodies"
severity: critical
deny:
on: body
condition: matches
pattern: "AKIA[0-9A-Z]{16}"
# Scoped: don't send auth headers to analytics
- name: no-auth-to-analytics
description: "Credentials must not leak to analytics providers"
severity: critical
match:
host:
- "*.analytics.google.com"
- "*.segment.io"
- "*.mixpanel.com"
deny:
header: Authorization
condition: present
# Scoped: Stripe payments must be hardened
- name: stripe-payment-hardening
description: "Stripe charge requests must use JSON over TLS 1.2+"
severity: critical
match:
host: "api.stripe.com"
path: "/v1/charges"
method: POST
allow:
all:
- header: Content-Type
condition: equals
value: "application/json"
- header: Idempotency-Key
condition: present
- on: tls
condition: version-gte
value: "1.2"
# Scoped: webhook calls must be clean
- name: no-credentials-to-webhooks
description: "Outbound webhook calls must not carry our credentials"
severity: high
match:
path: "/webhooks/**"
deny:
header: Authorization
condition: present
# Scoped: only multipart uploads should carry file data
- name: no-binary-to-json-endpoints
description: "Binary data should only go to multipart endpoints"
severity: warning
match:
header:
Content-Type: "application/json"
deny:
on: body
condition: matches
pattern: "^(?:[A-Za-z0-9+/]{4}){100,}" # long base64 blocks
# Override: relax PCI check for staging
- name: pci-dss/credit-card-in-body
severity: info
match:
host: "*.staging.internal"
Design principles¶
- Deny by default, document by exception. If you're not sure,
denyit. - Assertions are independent. Each assertion evaluates on its own. No ordering, no dependencies.
- Match narrows, allow/deny asserts. These are separate concerns and should stay separated.
- Presets are just assertions. They follow the exact same structure and can be overridden like any other rule.
- Flat over nested. Prefer multiple simple assertions over deeply nested compound blocks.