| title | Security model |
|---|
Security and privacy are first-class concerns; this page is the contract.
- Humans: GitHub identity through direct OAuth or the SaaS OIDC broker. Machines: argon2id-hashed API keys bound to roles. One permission catalog for web, CLI, MCP, and agents; deny by default; every route declares its permission and the startup assertion refuses undeclared routes.
- Custom roles are named permission sets — no side-channel authority.
- Stored secrets — provider API keys and integration signing secrets are sealed (libsodium) with a master key from your secret manager/KMS, decrypted only in the service that needs them. Provider credentials are never returned; integration signing secrets appear only in create/rotate responses and never in read responses.
- Service credentials — the GitHub App, upstream identity, and OAuth signing credentials are supplied to the services as environment variables from your secret manager; the platform reads them at boot and does not persist them in its database.
- Sandboxes receive no provider secrets — only run-scoped virtual keys and short-lived repo tokens fetched after boot.
- Facility-signed inbound events bind timestamp, delivery id, event type, and exact body bytes into HMAC-SHA256. The receiver enforces a five-minute replay window and integration-scoped delivery deduplication.
- Outbound webhooks require HTTPS outside insecure development, reject URL credentials and private/reserved IPv4/IPv6 answers, pin the validated address for the connection, disable redirects, and use a ten-second deadline. The durable outbox gives at-least-once delivery with bounded retry and visible dead letters.
- Remote MCP validates
Hostand browserOrigin, bounds JSON bodies, and asks the control plane to authenticate each uncached Bearer credential before MCP protocol admission. Invalid credentials cannot enumerate the catalog; validation outages fail closed with503andRetry-After. Accepted caller credentials are forwarded to the API, and MCP has no privileged service identity.
- Preview origins bind only to Docker loopback or an AWS private address; Facility rejects public, credential-bearing, and non-HTTP origins.
- Production preview creation, listing, viewing, and deletion fail closed until interactive GitHub/OIDC login is configured. Machine keys can request a preview, but cannot view or delete it.
- Preview HTML and JavaScript are served only from
FACILITY_PREVIEW_URL, a browser site isolated at the registered-site boundary that routes to the existing API tasks. The AWS module creates a dedicated AWS-assigned CloudFront origin by default; custom deployments must provide a separately registered site. The preview host denies every control-plane route, and the control-plane hosts deny preview content routes. - Opening a preview exchanges the Facility session for a single-use,
60-second handoff and then a short-lived HttpOnly cookie bound to one user,
organization, and preview. Every request rechecks active membership and
runs:read; the proxy strips cookies and authorization before forwarding only browser-safeGETandHEADrequests. - Do not configure a custom preview hostname as a sibling of the app/API
hostnames, and never widen Facility cookies with a parent
Domainattribute. The AWS-assigned origin or a separate registered site prevents untrusted preview JavaScript from tossing cookies into the control-plane site. - No provider or production secrets are injected. Projects publish an immutable review image whose command prepares only non-production data.
- Preview application containers currently have outbound network access. Place production preview workers in a dedicated subnet/network whose policy blocks metadata endpoints and internal services; treat the review image as untrusted.
- Readiness is a project-defined HTTP path. PR close and retention expiry queue sandbox destruction, and each lifecycle transition enters the audit log.
Issue, PR, review, and comment text is data, never instructions — the rule holds in webhook handlers (no interpolation into shell/prompts/SQL), in operating contracts, and in the rendered workflows (jq event parsing, start-of-line slash commands, bot-refusal, message-hash canary authorization). The fifteen production hardening notes ship encoded in templates, handlers, and guards — not as advice.
- Append-only, hash-chained audit log; tamper evidence is a query.
- Store-everything default (envelopes, transcripts). Expiry is by the object
store's lifecycle policy — configured by our AWS Terraform, and operator-
configured for other S3-compatible stores; there is no app-level enforcement yet
(a per-org
retention_dayssetting is recorded; app-enforced per-org expiry is a follow-up). Access is gated by permission + project scope — the full transcript needsaudit:read. - Receipts and analytics are metrics-only: no prompts, no code, hashed actors. Self-hosted telemetry to the vendor: none.
Agents never approve, never merge, never push to protected branches. Every outward action carries a named principal. Every merge carries a human decision.
Report vulnerabilities per SECURITY.md.