Security hardening
Security controls built into OrkStage, a hardening checklist for hosting it yourself, the known gaps and how to report a vulnerability.
Controls built into OrkStage
| Area | Control |
|---|---|
| Sessions | An opaque random token of 32 bytes in an httpOnly, SameSite=Lax cookie (Secure in production). Only its SHA-256 hash is stored. Sessions expire after 12 hours and are revoked on logout. |
| CSRF | Every request with a session that changes state must send x-orkstage-csrf: 1. The API allows no CORS from other origins, so a browser on another site cannot send this header. Requests with a bearer API key are exempt. |
| OIDC | Authorization code grant with PKCE (S256). state and nonce are verified. The flow state lives in a signed, short lived cookie. returnTo accepts relative paths only. Provider tokens are discarded. |
| Account linking | Only through an email address the provider has verified (email_verified, or xms_edov for Entra). OrkStage never trusts preferred_username, which prevents takeover in the style of "nOAuth". Unknown users get no account unless a tenant has put their domain on its allowlist. |
| Dev login | Off unless AUTH_DEV_LOGIN_ENABLED=true. Startup fails if it is on while NODE_ENV=production. |
| API keys | Only the SHA-256 hash of the token is stored. Scopes narrow a role and never exceed it. A key cannot manage keys. Keys support expiry, revocation and a last used time. Every action is audited with the key's name and prefix. |
| Authorization | CASL abilities for each request. Queries for tenant data are scoped to the caller's memberships, and each record is checked again after loading. Records of other tenants return 404, not 403. |
| Secrets | The API rejects target configuration with keys that look like secrets. Credentials are env:// or aws-sm:// references. Services receive secret references, not values. |
| Cloud credentials | AssumeRole with an external id. The credentials are short lived, stay in memory and reach only the runner container that needs them. |
| Webhooks | HMAC-SHA256 signatures with a timestamp, and https only. SSRF protection blocks private, loopback, link local and multicast targets, checked at registration and again before each delivery. No redirects are followed, the timeout is 10 seconds, and signing secrets are encrypted at rest. |
| Logging | Pino redacts tokens, secrets, cookies and authorization headers. Errors return a code and a correlation id, never a stack trace. |
| Rate limits | 600 requests per minute per client overall. Stricter limits apply to login, deploy, destroy, target validation, template publishing, API key creation, imports and webhook tests. |
| Audit | Append only. A Postgres trigger rejects UPDATE and DELETE on the audit table. |
| Headers | Helmet with a restrictive Content Security Policy. |
| Supply chain | Only pnpm build scripts on an allowlist run. Telemetry during install is denied. Very recent releases are avoided (minimum release age). Container images are pinned by digest where it matters (MiniStack, OpenTofu). |
Hardening checklist
-
NODE_ENV=production, dev login off and an OIDC provider configured. -
SESSION_SECRETandENCRYPTION_KEYgenerated at random and injected from a secret store. - TLS in front of the console and the API, with
WEB_PUBLIC_URLandAPI_PUBLIC_URLonhttps://. - Postgres and Redis on private networks, with authentication and encryption in transit.
- The worker on a dedicated host (it holds the Docker socket), with an IAM identity limited to
sts:AssumeRoleon yourorkstage-deployroles andsecretsmanager:GetSecretValueon the referenced secrets. - One AWS account per environment, with deployment roles created from the provided CloudFormation template and a unique external id for each.
- State buckets versioned, encrypted and private.
-
WEBHOOK_ALLOW_PRIVATE=false(enforced in production). - One API key per integration, with the smallest preset and an expiry.
- Postgres backups tested by restoring them (see Backups and recovery).
Known gaps
- MFA policy, and a console page that lists and revokes sessions.
- Container image scanning in CI (the dependency audit already blocks merges on high severity advisories).
- Postgres row level security as an extra isolation layer.
- An OpenTofu runner that does not need the Docker socket (ECS RunTask or a Kubernetes Job).
Reporting a vulnerability
Email security@orkstage.io with the details and, if you can, a proof of concept. Please do not open public issues for security problems. We acknowledge each report and coordinate disclosure with you.
OpenTofu runner and state
How OrkStage runs OpenTofu in throwaway containers, keeps one S3 state per resource without secrets, plans before apply and scans for drift.
Upgrades and migrations
Upgrade OrkStage on your own infrastructure. Rebuild images, apply Prisma migrations, update the OpenTofu runner and move instances to new template versions.