DOCS · REFERENCE

Security and invariants

The nine properties specd enforces in code rather than by convention, and where each one is enforced.

On this page

Each of these is enforced in code, and each has a test. A property that depends on everyone remembering it is not a property.

The invariants

Only a named human can approve
The state machine refuses approved without an actor, and a database CHECK constraint rejects an approved row with no approver — a direct write cannot record an unattributed approval.
The gate cannot be routed around
specd spec pull is refused server-side for anything unapproved, and CLI tokens are audience-scoped and rejected on every route that authors or approves.
Approval is append-only
approved → draft is refused. v2 supersedes v1 while v1 keeps its stamp exactly as recorded.
A citation means someone can check it
Citations are validated against what was actually retrieved; invented paths are demoted to UNVERIFIED, because a citation that cannot be followed is worse than none.
The loop closes
The last task of every spec files the as-built copy. If the model omits it, specd appends it.
Spend cannot run away
Caps are checked before a run starts. Money is integer EUR cents — floats never touch it.
Agents never push
Editing tools only, the spec's own branch only, never a default branch. The build agent has no shell.
Webhooks cannot be impersonated
GitHub: HMAC over raw bytes, constant time, before parsing. GitLab: token echo, constant time. Both fail closed on an unset secret, dedupe by delivery id, and act only for a registered repository.
Leaving is free
Git holds the knowledge; the platform holds a derived index. Delete a project and nothing you would miss is gone.

Credentials

  • Every connection secret is held with envelope encryption under a project-bound key (VAULT_MASTER_KEY), and never logged.
  • Credentials are verified against the provider before being stored, so a bad one fails in front of you rather than inside an agent run.
  • A subscription credential is never seen, stored or proxied — specd shells out to a CLI that is already signed in. That is also why a hosted specd could not offer that mode.
  • A runner token lives in a separate keychain slot from a person's own CLI token, so pairing cannot clobber a session. Revoking a runner takes effect on its very next request, with no grace period.

Tokens are audience-scoped

A CLI token, a runner token and a web session are different audiences, and the API checks which one is presenting before it checks what is being asked. This is what makes "the MCP server is read-only" a structural statement rather than a promise about the MCP server's code.

The audit trail

Every webhook delivery is recorded with what specd decided and why — including the ones it ignored. "The webhook arrived and specd chose not to act" and "the webhook never arrived" are different problems, and the delivery log is what tells you which one you have. Rows are retained for 30 days by default.