Request access

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.

KeyTypeDefaultPurpose
namestring(none)Human-readable profile name. Echoed back on export.
descriptionstring(none)Documentation for your own use.
base_profilestring (baseline / strict / custom / none)customBase profile for the import. Accepted on import only.
pii_redaction_modestring (tag / fake / mask / hash)(none, inherits tenant default)Global redaction mode for PII checks.
pii_entitieslist of strings(none, inherits base)Built-in entity names scanned for PII.
checksobject — check_id → CheckConfig(required)Map of every Shield check you want to configure.

Each value inside checks is a CheckConfig:

FieldTypeDefaultPurpose
modestring — off / log_only / enforceenforceScan mode.
thresholdfloat0.85Score threshold above which the check fires.
fail_behaviorstring — fail_open / fail_closedfail_openBehaviour when the detector is broken or unreachable.
redaction_modestring or nullnullPII checks only. One of tag / fake / mask / hash.
extraobject{}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:

  1. Parses the body into Pydantic CheckConfig objects for every key in checks.
  2. Composes them into a SecurityProfileConfig object.
  3. Returns {"valid": true, "profile": <resolved dict>} on success.
  4. Returns HTTP 400 with 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 included enabled: true instead of mode.
  • Invalid enum valuemode is not one of off / log_only / enforce, or fail_behavior is not one of fail_open / fail_closed.
  • Wrong typethreshold is a string, or extra is a list.
  • checks is not an object — the top-level must be a mapping of check_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

  1. One of your security engineers edits shield/baseline.yaml (or shield/custom/<key-name>.yaml) in your infra repo.
  2. They raise a pull request.
  3. 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.
  4. 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.
  5. 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 (highassurancestrict).
  • 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.