Security
Policy-as-code
Export Shield profiles as YAML, commit them to git, validate on PR, and apply back through the admin API.
9 min read
Export your Shield profile as YAML, commit it to git, validate it in a pull request, and apply it back through the admin API.
TL;DR
- Your Shield profile is a plain dict. The gateway lets you export it through the admin API, edit it in git, and import it back with validation.
- Import only validates — it never persists by itself. The validated profile is attached to a virtual key via
PATCH /api/v1/keys/{key_ref}. - Round-trip is lossless: export → import → export produces the same payload.
- Schema versioning is the
receipt_version-style tag on the profile; migrations are handled for you.
Export
The gateway exposes every built-in profile as a structured dict:
GET /api/v1/security/policies/{profile_name}/export
Where profile_name is one of none, baseline, strict.
Response (real shape — the below is baseline with a small number of checks trimmed for readability):
{
"name": "baseline",
"description": "Verosek Shield built-in profile: baseline",
"pii_redaction_mode": "fake",
"pii_entities": [
"EMAIL_ADDRESS", "PHONE_NUMBER", "CREDIT_CARD", "IBAN_CODE",
"US_SSN", "IP_ADDRESS", "CRYPTO", "MEDICAL_LICENSE"
],
"checks": {
"CHK-013": {
"mode": "log_only",
"threshold": 0.9,
"fail_behavior": "fail_closed",
"redaction_mode": null,
"extra": {}
},
"CHK-014": {
"mode": "log_only",
"threshold": 0.9,
"fail_behavior": "fail_closed",
"redaction_mode": null,
"extra": {}
},
"CHK-015": {
"mode": "enforce",
"threshold": 0.5,
"fail_behavior": "fail_open",
"redaction_mode": "fake",
"extra": {
"entities": ["EMAIL_ADDRESS", "PHONE_NUMBER", "CREDIT_CARD", "IBAN_CODE",
"US_SSN", "IP_ADDRESS", "CRYPTO", "MEDICAL_LICENSE"]
}
},
"CHK-016": {
"mode": "enforce",
"threshold": 0.85,
"fail_behavior": "fail_open",
"redaction_mode": null,
"extra": {}
},
"CHK-022": {
"mode": "log_only",
"threshold": 0.85,
"fail_behavior": "fail_open",
"redaction_mode": null,
"extra": {
"pii_warn": 20, "pii_block": 50,
"urls_warn": 10, "urls_block": 30,
"bytes_warn": 5000000, "bytes_block": 20000000
}
},
"CHK-023": {
"mode": "log_only",
"threshold": 0.7,
"fail_behavior": "fail_open",
"redaction_mode": null,
"extra": {
"async": true,
"daily_token_budget": 100000,
"max_context_chars": 8000
}
},
"CHK-024": {
"mode": "log_only",
"threshold": 0.5,
"fail_behavior": "fail_open",
"redaction_mode": null,
"extra": {}
}
}
}
Convert to YAML in your pipeline with a tool of your choice — the server returns JSON, not YAML, to keep the endpoint usable from any HTTP client. Example with yq:
curl -s -H "Authorization: Bearer vsk_admin_..." \
https://<your-tenant>.verosek.com/api/v1/security/policies/baseline/export \
| yq -P > shield/baseline.yaml
YAML schema
Every top-level key in the export is optional except checks. Unknown keys are rejected on import.
| Key | Type | Default | Purpose |
|---|---|---|---|
name | string | (none) | Human-readable profile name. Echoed back on export. |
description | string | (none) | Documentation for your own use. |
base_profile | string (baseline / strict / custom / none) | custom | Base profile for the import. Accepted on import only. |
pii_redaction_mode | string (tag / fake / mask / hash) | (none, inherits tenant default) | Global redaction mode for PII checks. |
pii_entities | list of strings | (none, inherits base) | Built-in entity names scanned for PII. |
checks | object — check_id → CheckConfig | (required) | Map of every Shield check you want to configure. |
Each value inside checks is a CheckConfig:
| Field | Type | Default | Purpose |
|---|---|---|---|
mode | string — off / log_only / enforce | enforce | Scan mode. |
threshold | float | 0.85 | Score threshold above which the check fires. |
fail_behavior | string — fail_open / fail_closed | fail_open | Behaviour when the detector is broken or unreachable. |
redaction_mode | string or null | null | PII checks only. One of tag / fake / mask / hash. |
extra | object | {} | Check-specific parameters (token budgets, counter thresholds, etc.). |
A real example — baseline as YAML
Drop this file into shield/baseline.yaml in your repo and you have the baseline profile under version control. Every field below is pulled from the export payload of the same name.
name: baseline
description: "Verosek Shield built-in profile: baseline"
pii_redaction_mode: fake
pii_entities:
- EMAIL_ADDRESS
- PHONE_NUMBER
- CREDIT_CARD
- IBAN_CODE
- US_SSN
- IP_ADDRESS
- CRYPTO
- MEDICAL_LICENSE
checks:
CHK-013:
mode: log_only
threshold: 0.90
fail_behavior: fail_closed
CHK-014:
mode: log_only
threshold: 0.90
fail_behavior: fail_closed
CHK-015:
mode: enforce
threshold: 0.5
fail_behavior: fail_open
redaction_mode: fake
extra:
entities: [EMAIL_ADDRESS, PHONE_NUMBER, CREDIT_CARD, IBAN_CODE, US_SSN, IP_ADDRESS, CRYPTO, MEDICAL_LICENSE]
CHK-016:
mode: enforce
fail_behavior: fail_open
CHK-017:
mode: log_only
threshold: 0.90
fail_behavior: fail_open
CHK-018:
mode: log_only
threshold: 0.5
fail_behavior: fail_open
redaction_mode: fake
extra:
entities: [EMAIL_ADDRESS, PHONE_NUMBER, CREDIT_CARD, IBAN_CODE, US_SSN, IP_ADDRESS, CRYPTO, MEDICAL_LICENSE]
CHK-019:
mode: log_only
fail_behavior: fail_open
CHK-020:
mode: log_only
threshold: 0.90
fail_behavior: fail_open
CHK-021:
mode: log_only
threshold: 0.5
fail_behavior: fail_open
redaction_mode: fake
extra:
entities: [EMAIL_ADDRESS, PHONE_NUMBER, CREDIT_CARD, IBAN_CODE, US_SSN, IP_ADDRESS, CRYPTO, MEDICAL_LICENSE]
CHK-022:
mode: log_only
fail_behavior: fail_open
extra:
pii_warn: 20
pii_block: 50
urls_warn: 10
urls_block: 30
bytes_warn: 5000000
bytes_block: 20000000
CHK-023:
mode: log_only
threshold: 0.7
fail_behavior: fail_open
extra:
async: true
daily_token_budget: 100000
max_context_chars: 8000
CHK-024:
mode: log_only
threshold: 0.50
fail_behavior: fail_open
Import + validation
POST /api/v1/security/policies/import
Content-Type: application/json
<the exported dict, or the YAML converted to JSON>
What the endpoint does:
- Parses the body into Pydantic
CheckConfigobjects for every key inchecks. - Composes them into a
SecurityProfileConfigobject. - Returns
{"valid": true, "profile": <resolved dict>}on success. - Returns
HTTP 400with an explanation on any validation failure.
The endpoint does not persist the profile. It only validates. To put the profile into effect, attach it to a virtual key via PATCH /api/v1/keys/{key_ref}:
PATCH /api/v1/keys/vkr_abc123def456
{
"security_profile": {
"profile": "custom",
"base_profile": "baseline",
"pii_redaction_mode": "mask",
"overrides": {
"CHK-022": {
"extra": { "pii_warn": 10, "pii_block": 25 }
}
}
}
}
Validation errors
HTTP 400 is returned with a message of the form "invalid policy: <details>" for the following failure classes:
- Unknown field in
CheckConfig— e.g. you includedenabled: trueinstead ofmode. - Invalid enum value —
modeis not one ofoff/log_only/enforce, orfail_behavioris not one offail_open/fail_closed. - Wrong type —
thresholdis a string, orextrais a list. checksis not an object — the top-level must be a mapping ofcheck_id → CheckConfig.
Check-ID normalisation (CHK-013 vs chk_013) is applied when the profile is resolved onto a key, not at import time. Import expects the exact IDs the export produces.
Round-trip guarantee
Export → import → export produces the same dict, modulo ordering of object keys in the JSON response. A simple CI check to assert this invariant:
#!/usr/bin/env bash
set -euo pipefail
TENANT="https://<your-tenant>.verosek.com"
ADMIN="Authorization: Bearer vsk_admin_..."
# 1. Export current profile.
curl -s -H "$ADMIN" "$TENANT/api/v1/security/policies/baseline/export" > a.json
# 2. Run it back through the validator.
curl -s -X POST -H "$ADMIN" -H "Content-Type: application/json" \
--data @a.json "$TENANT/api/v1/security/policies/import" \
| jq '.profile' > b.json
# 3. Diff. Any diff means the validator mutated the policy; fail CI.
if ! diff -q <(jq -S . a.json) <(jq -S . b.json); then
echo "Round-trip failed — diff above."
exit 1
fi
echo "Round-trip OK"
The import endpoint is designed to be a structural validator, not a transformer — any diff is a bug in our validation logic and we want to know.
Git workflow
- One of your security engineers edits
shield/baseline.yaml(orshield/custom/<key-name>.yaml) in your infra repo. - They raise a pull request.
- CI runs the round-trip script above against your tenant to confirm the YAML validates. You can use a non-prod admin key for this.
- On approve / merge, your deploy tooling converts the YAML to JSON and calls
PATCH /api/v1/keys/{key_ref}against the keys the profile applies to. - The change takes effect on the next request (the gateway invalidates the key cache immediately on update).
Migration
Profile structure is stable. When we change it we preserve backwards compatibility by:
- Silently remapping legacy profile names (
highassurance→strict). - Keeping old field names as aliases where possible.
- Versioning the signable receipt payload (see Audit API — HMAC signature design).
On Verosek Cloud, schema migrations happen automatically on upgrade — your YAML will continue to import as long as it was valid against any previously-published schema. On on-prem deployments, schema migrations are coordinated with your team during the upgrade window.
Onboarding-only
Handled during onboarding — not public. The schema change log and any legacy-field retirement timelines are tracked in the internal customer bulletin, not on public docs.
What's next
If you have not already, read Shield configuration to understand the full set of checks the YAML is controlling, and Audit API to see the verdicts your profile produces at runtime.