Enterprise — identity, tenants, audit, telemetry and supply chain¶
What a security review of Verel's pilot service will ask for, and what exists today. Where something does not exist yet, this page says so.
Identity and roles¶
verel serve-pilot runs the pilot workflow over HTTP. Authentication is always on, even on loopback.
A routable bind additionally requires TLS (the same transport layer as the other services).
| Role | Can do |
|---|---|
viewer |
read evaluations, verify logs |
operator |
+ record decisions (shadow or advisory), add labels |
admin |
+ request enforce, archive logs, (phase 5) promote graders |
The role-to-permission table is fixed in code. Nothing in a config file or a token claim can create a permission that the code does not define.
Bearer tokens. verel auth add --file tokens.json --id alice --role operator --tenant acme prints a
random 256-bit token once. Only its SHA-256 is stored. Comparison is constant-time across all entries.
Tokens can expire. verel auth revoke takes effect on the next request. A corrupt or group-writable token
file denies everyone (fail closed).
SSO (OIDC). pip install "verel[sso]", then pass --oidc-issuer/--oidc-audience and a JWKS file or
https URL. Verel verifies signed JWTs with an asymmetric-only algorithm allowlist (none and HS* are never
accepted), requires exp, iss, aud and sub, and takes the role and tenant from claims
(verel_role, verel_tenant). A missing or unknown role or tenant denies; it never defaults. Group
mapping and refresh-token flows are out of scope: put your IdP in front and mint short-lived access tokens.
Tenant isolation¶
The tenant comes only from the authenticated principal. No request field, header or path can name
another tenant. Each tenant's decision log, labels and archives live under <data>/tenants/<id>/, and tenant
ids are validated to a strict charset. This is logical isolation inside one process and one filesystem. If
you need isolation against a compromised process or host, run one service per tenant.
Audit trail¶
Every mutation and every denial by an authenticated principal is appended to that tenant's keyed, hash-chained
audit log: who, tenant, action, outcome. Tokens and claims are never written. Reads are not audited, and
unauthenticated attempts are only counted in memory, so neither a read-only token nor anonymous traffic can fill the
log or slow other tenants. If an action cannot be audited it is refused. Any of the pilot, label or audit logs can be
archived (POST /v1/pilot/archive with "log"), which is what frees a full one. verel auth verify-audit --log audit.jsonl.
Evidence integrity and retention¶
Decision logs, label ledgers and audit logs share one implementation: each record carries an HMAC under a
per-installation secret (VEREL_LOG_KEY, else a persisted key file), and a keyed head ledger pins the latest
head and count. Edits, deletions, reordering, forged records, rebuilt chains, truncation and deleting the
log are detected. Not protected: an attacker who holds the key, or who deletes the log and its ledger.
Keep an external copy of head for that case.
Retention. verel pilot archive --log pilot.jsonl --keep-last N moves older decisions into a verifiable
archive segment and re-anchors the live log. That is an authorized deletion: delete a segment when your
retention period ends and the live log still verifies. verel pilot verify --segment checks a segment. The segment is created next to the log (so an interrupted
archive can be finished by verel pilot repair); move it to cold storage afterwards, and put it back before repairing.
Telemetry¶
pip install "verel[otel]". Verel emits one span and one counter per pilot decision through the
OpenTelemetry API. It never configures an exporter or endpoint: until your application installs an SDK,
the API is a no-op and nothing leaves the process. Artifact ids are exported as a short hash unless you opt in.
Supply chain¶
- A CycloneDX SBOM of the resolved install is generated per release (
tools/release_sbom.sh). - The
release-assetsworkflow attaches a Sigstore-signed build-provenance attestation for the wheel, sdist and SBOM. Verify withgh attestation verify <file> --repo amitpatole/verel. This workflow runs on version tags; it has not yet run on a published tag, so treat verification as available from the first tag that executes it. - PyPI uploads currently use an API token, not Trusted Publishing, so PyPI itself does not yet show a publish attestation for Verel.
- Dependencies are tracked by Renovate; CodeQL runs weekly.
Data flow and residency¶
Verel is self-hosted. The base install makes no outbound network calls. Pilot data stays on the host you choose. The only features that reach the network are ones you turn on and point somewhere: an LLM backend, an OIDC JWKS URL (https, SSRF-guarded), and OpenTelemetry export. Choose the region by choosing where to run it.
Control mapping (supports, not certified)¶
Verel is not SOC 2, ISO 27001 or similar certified. This table shows which controls the product helps you evidence. Your auditor decides what satisfies them.
| Control area | What Verel provides | What you still own |
|---|---|---|
| Logical access (SOC 2 CC6) | roles, hashed tokens, SSO, tenant scoping, revocation | IdP policy, MFA, joiner/mover/leaver process |
| Monitoring (CC7) | tamper-evident decision and audit logs, OpenTelemetry | alerting, retention policy, log shipping |
| Change management (CC8) | gated releases, signed receipts, SBOM, build attestation | approvals, deploy process |
| Availability (A1) | health endpoint, bounded concurrency | HA, backups, capacity |
| Confidentiality (C1) | TLS on routable binds, no default secrets, no egress by default | disk encryption, key management |
Reading evidence¶
verify and eval take a short shared lock only to copy the log and its ledger, then verify the copy, so a slow verification of a big
log never blocks a writer. If the directory is read-only (an auditor, a WORM copy), they read without a lock and look twice before
calling a mismatch corruption. The ledger keeps at most 200 archive entries (older ones are folded into one), so it cannot outgrow
its read limit however often you archive. The archived-id index is keyed per line and counted against the archived total: forged
lines are ignored, a deleted or truncated index is reported.
Resource limits¶
Denials are audited with a per-principal budget (20 a minute, and a separate 200 a minute for privileged actions such as archive
and promote, so cheap noise cannot hide an attempt on a sensitive route). Per tenant: at most 8 concurrent write requests (the rest get 429), 500 labels per request, 5,000 pending jobs. Waiting for a
log's lock is bounded (10 s, then 503 with Retry-After). A write that fails midway (a full disk) is rolled back, a multi-record write announces itself in the keyed ledger first (so a crash is recovered, and a
half-persisted batch after a power loss is fixed explicitly with verel pilot repair, never silently; add --kind labels|policy|audit for the tenant's other logs), and a
torn final line is dropped on the next write, so a log is never left unusable.
Known gaps¶
- No built-in rate limiting of failed authentications beyond connection caps. Tokens are 256-bit, so guessing is infeasible, but put a WAF or gateway in front for abuse control.
- A single process serves all tenants of one service.
- No SCIM provisioning and no UI for token management (CLI only).
- Unique appends re-verify the whole log (O(n)) whenever another writer changed it since this process last wrote, which is fine for pilot volumes and not for millions of records.