Security model
How we keep your workspace, your secrets and the internet safe from the code that runs here, yours included.
Accounts and access
Who can sign in, how long a session lasts, and what an API key can reach.
- Sign-in
Multi-factor authentication (TOTP) is required for every member, with bot and fraud detection at sign-up and sign-in. The sign-in callback is checked against a state cookie.
- Sessions
A
__Host-cookie,HttpOnly,SecureandSameSite=Lax, holding a random token. We store only its SHA-256, and the identity provider's refresh token encrypted. Sessions end after 30 days, or 7 days idle. - Role changes
Every 5 minutes a session re-reads the member's role. If the identity provider is unreachable, a session lasts 15 more minutes, then fails closed. Removing a member revokes their sessions; changing a role takes effect on their next request.
- Cross-site requests
Dashboard writes must come from
https://app.shiftlabs.org; anything else is refused. - API keys
256 random bits, one workspace and one role each, expiring after 1–365 days. We store the SHA-256 and show the key once. A key's role cannot exceed its creator's.
- Roles
viewer<developer<admin, checked on every route. See what each role can do. - Isolation
Every API query is scoped to the caller's workspace, which comes from the credential and never from the path or body. Another workspace's trigger or run answers 404.
- Failed sign-ins
Failed API authentication counts against a per-IP limit: after 10 a minute, further attempts get
429.
Members · ridgeline-ops
3| Member | Role | Sign-in |
|---|---|---|
| dana@ridgeline.example | admin | MFA · TOTP |
| li@ridgeline.example | developer | MFA · TOTP |
| sam@ridgeline.example | viewer | MFA · TOTP |
Removing a member revokes their sessions.
Webhooks
A trigger URL starts a run. Signatures, rate limits and a size cap decide which deliveries get that far.
- Trigger URLs
Trigger ids are 128 random bits, so URLs are unguessable. Developers can rotate or revoke them at any time, and viewers see them masked.
- Signatures
Optional per trigger:
shiftlabs(a secret we generate),stripeorgithub. Timestamped signatures must be within 5 minutes, and replayed deliveries are dropped. - Signing secrets
Encrypted with AES-256-GCM and bound to their trigger. A generated secret is returned once; a secret you supply is never returned.
- Rate limits
Per edge location: 100 requests per 10 seconds per trigger and 600 requests a minute per IP across the API and webhooks. At the edge, an IP sending more than 150 requests in 10 seconds to the webhook host is blocked.
- Size
A webhook body is capped at 256 KiB.
Deliveries to hooks.shiftlabs.org/t/kQ7v…
signed: stripeA replayed delivery starts nothing; an unsigned one never reaches the queue.
Secrets and connections
Secrets are write-only: no endpoint returns a value. Your code reaches an upstream with a secret only through a connection, which an admin owns and which names the exact hosts it applies to.
- Encryption
Each workspace's values are encrypted with its own data key (AES-256-GCM, bound to the workspace, the secret's name and its version). The data key is itself encrypted under a platform key.
- Stand-ins
Your code holds a stand-in, never the value. The gateway swaps the value in only for the secret's own hosts (exact names, no wildcards or IP addresses, https only) and only for a stand-in made in the same run; to any other host the request gets a
403. Text that only looks like a stand-in is left alone. - Connections
For admin-fixed placement, the workflow must declare the connection and the host must be on its exact list. The gateway applies the template to every request to its hosts and overwrites whatever the code set. Redirects are not followed for any request that carries a secret.
- Scrubbing
If an upstream echoes a secret, or a reversible encoding of it such as base64 or Basic auth, it is replaced with
[redacted]before your code sees the response. Readable secrets are scrubbed from logs. - Readable and sign
Only secrets marked
readableare returned byreveal(). A secret markedhmaccan only produce signatures throughsign(); the value never enters your code. - Audit
Every use or refusal of a secret is written to the audit log, which admins read at
/v1/audit.
Audit log · ridgeline-ops
admins only| Time (UTC) | Action | Subject | Host | Detail |
|---|---|---|---|---|
| 21:41:10.470 | inject | stripe | api.stripe.com | ["STRIPE_KEY"] |
| 21:36:02.881 | blocked | billing | api.stripe.com | connection billing is not declared for workflow refund-check |
| 21:30:15.303 | inject | stripe | api.stripe.com | ["STRIPE_KEY"] |
| 21:05:40.112 | connection_put | stripe | ||
| 21:05:12.640 | secret_put | STRIPE_KEY | version 2 |
Outbound requests
Every request your code makes with fetch goes through our egress gateway. Nothing else leaves: raw TCP is refused at deploy and has no path out.
Outbound requests · last hour
5| Request | Status | Time |
|---|---|---|
| GET api.stripe.comvia connection stripe | 200 | 182 ms |
| POST hooks.slack.comno connection | 200 | 95 ms |
| GET 203.0.113.7no connection | blockedrequests to IP addresses are not allowed; use a hostname | 2 ms |
| GET api.stripe.com:8443no connection | blockedport 8443 is not allowed; use 80 or 443 | 2 ms |
| POST hooks.shiftlabs.orgno connection | blockedrequests to shiftlabs.org are not allowed from workflows | 2 ms |
- Refused
IP-address hosts, ports other than 80 and 443, and any
*.shiftlabs.orghost, so a workflow cannot call its own webhook in a loop. - Attributed
Every request carries
X-Shiftlabs-Tenant: t_…, an opaque reference rather than your workspace id, andX-Abuse-Contact: abuse@shiftlabs.org. Connection templates cannot set either header. - Logged
Every request is logged with its host, method, status, connection and outcome, and you can read the log for each run.
- Capped
Outbound requests are capped per workspace and per host; see Limits.
Limits
Every workspace starts on these free-tier limits. We can raise them for a workspace, up to platform ceilings.
Usage · last 24 hours
- Runs
- 630 of 1,000
- Outbound requests
- 1,236 of 3,000
- Concurrent runs
- 4 of 10
- Workflows
- 4 of 10
| Limit | Free | When you reach it |
|---|---|---|
| Runs per 24 hours · concurrent active runs | 1,000 · 10 | The webhook answers 429. |
| Workflows · active triggers · secrets · connections | 10 · 20 · 25 · 10 | Creating one more answers 403. |
| Engine dispatches per run · run lifetime | 50 · 168 h | The run fails before your code loads again. |
| CPU · subrequests per invocation | 2,000 ms · 50 | The invocation fails. |
| Outbound requests | 3,000 per 24 h · 30/min per host | The gateway answers 429. |
| Deploys · new versions per 24 hours | 20/min · 100 | The API answers 429. |
| Webhook body · workflow source · API JSON body | 256 KiB · 512 KiB · 16 KiB | The request is refused with 413. |
| Logs per invocation | 500 lines · 4,000 chars/line | Further lines are not stored; long lines are cut. |
Abuse
Report abuse to abuse@shiftlabs.org with the X-Shiftlabs-Tenant value from the request.
- Deploy scan
Every deploy is scanned. WebAssembly, crypto-mining code and raw sockets are blocked. URLs with IP addresses, unusual ports or
shiftlabs.orghosts, and hard-coded credentials, come back to you as warnings. Other patterns are flagged for review by our operators. - Hourly checks
Every hour we look at usage signals across workspaces, such as traffic concentrated on one host, failing or scanned targets, blocked requests and CPU use. New workspaces, and workspaces whose current code is flagged, are held to stricter thresholds. Flags go to an operator.
- Suspension
A suspended workspace has its active runs stopped, and its webhooks, runs and API writes are refused. You can still read and revoke triggers, API keys and members.
- Reporting
Send reports to abuse@shiftlabs.org with the
X-Shiftlabs-Tenantvalue from the request. It identifies the workspace to us without telling anyone else which customer it is.
Platform
Where runs execute, and what this site and the API send back.
- Region
Runs execute in US regions only.
- Transport
HTTPS only, TLS 1.2 or later, with HSTS. Plaintext HTTP is refused with
403, and the app, API and webhooks answer on their own hostnames only. - API responses
API responses are never cached and cannot be framed or content-sniffed.
- Telemetry
Run telemetry uses separate credentials to write and to read: the component that writes cannot read it, and the API that reads cannot write it.
- This site
This site loads no third-party scripts, fonts or trackers, under a strict Content Security Policy.