6 min read
Security Architecture
How StateAnchor handles access, data, and trust.
GitHub App permissions
StateAnchor requests the minimum permissions to operate: read access to contents (to read stateanchor.yaml on push), read access to metadata (to verify repo identity), and webhook events (to trigger syncs on push). No write access. No access to issues, pull requests, or source code.
The app is revocable at any time from GitHub Settings → Integrations. Revocation takes effect immediately -- no subsequent sync will fire against a repo whose installation has been removed, and no token can be regenerated from a revoked installation.
OIDC token validation
Every sync is triggered by a GitHub Actions OIDC token, not a stored API key. StateAnchor validates four properties on every incoming request: the token audience matches stateanchor-sync-action, the repository claim matches the connected project, the token has not been replayed (the JTI is recorded on first use and rejected on every subsequent submission), and the token is within its validity window.
Tokens are never stored. They are validated against GitHub’s JWKS endpoint on each request and discarded after the sync run completes. This eliminates the long-lived-credential attack surface that personal access tokens and OAuth refresh tokens expose.
Row-level security
Every database query is scoped to the authenticated user’sscope_id via Supabase row-level security policies enforced at the Postgres layer. There is no admin query path in user-facing code that bypasses RLS. Users cannot read, modify, or reference data belonging to other accounts, even with a valid session.
The gate engine writes through the service role, which has access to all tables -- this is the only code path that bypasses RLS, it is audited in the Merkle log, and the write path contains no user-controlled input that could leak data across accounts.
Artifact isolation
Generated artifacts (TypeScript, Python, Go SDKs; MCP servers; OpenAPI schemas; docs) are stored in content-addressed object storage keyed by the SHA-256 of the artifact content. Two projects that generate an identical artifact share the same stored blob -- this is deduplication, not cross-account access.
Project references (the mutable pointer to the latest artifact for a given project / language pair) are protected by scope_id. An authenticated user can only resolve a reference for a project they own. Reading the content-addressed blob directly requires knowing its SHA-256, which is never exposed outside the owning account.
Secret handling
StateAnchor never has access to source code (only stateanchor.yamlis read), secrets or environment variables, private keys, or payment instruments (Stripe handles all payment card storage). The spec file should never contain secrets -- it is a structural contract declaration, not a configuration file.
API keys and webhook signing secrets used by StateAnchor itself are stored in Supabase Vault (Postgres pgsodium-backed encryption) and accessed only by the service-role paths that need them. No API key is ever logged, returned in a response, or rendered in the UI.
Trust model
The Merkle log publishes an hourly root hash at stateanchor-hq/stateanchor-merkle-logon GitHub. Any gate decision can be independently verified by downloading the log, recomputing the tree, and checking the inclusion proof via GET /api/v1/sync-runs/[id]/merkle-proof.
This is the mechanism that makes StateAnchor trustworthy without trusting StateAnchor: a customer, a security auditor, or a regulator can confirm that a specific gate decision for a specific commit was made, at the recorded time, and has not been altered. The corpus is open, the classification rules are public, and the log is verifiable.
Outage behavior
If StateAnchor is unavailable -- network error, service outage, timeout -- the Action behavior is controlled by the outage-policy input. The default is fail-closed: the push is blocked until StateAnchor recovers. Teams that prefer to keep deploying during outages can opt in via outage-policy: fail-open, which causes the Action to pass CI with a soft-pass result.