Security
What the system actually enforces, and where the deliberate limits are.
Tenancy
Every workflow, credential, execution and dead letter belongs to an
organization. Server-side queries are scoped by organizationId, not by
user, which is what lets teammates share a credential and what stops one org
reading another's data.
Membership is checked per request. A user in two organizations sees each one's data only while that org is active, and holding the editor open in two orgs at once does not cross the streams.
Authentication
Email and password, plus GitHub and Google. Sessions are signed with
BETTER_AUTH_SECRET, and better-auth refuses to boot in production without one.
Password reset emails a single-use link valid for one hour. Requesting a reset returns the same response whether or not the address has an account — whether someone has registered is not something an unauthenticated form should reveal.
Email verification is available but off by default. Turning it on is a deployment decision: it protects against someone registering an address they don't control, at the cost of locking everyone out if mail is misconfigured.
With verification off, an address belongs to whoever registers it first. If your
deployment is public before you sign up, someone could claim an address listed in
ORCHKA_ADMIN_EMAILS. Sign up immediately after deploying, or use GitHub/Google
sign-in, which supplies a verified address.
Rate limiting
/api/auth/* is the only unauthenticated surface. It is rate limited — 100
requests per 10 seconds globally, 3 per 10 seconds on sign-in and sign-up — with
state held in Redis, so the limit is shared across replicas rather than
per-instance.
Credentials
Encrypted at rest with AES-256-GCM and decrypted only in the worker, at execution time. No endpoint returns a secret in plaintext. Access is checked by organization, and every resolution writes an audit row. See Credentials.
AI provider keys are deliberately not read from environment variables: a process-wide key would be shared by every organization on the deployment and would bypass both the encryption and the scoping above.
The agent code tool
The AI Agent's evaluate_expression tool runs model-generated JavaScript inside
QuickJS compiled to WebAssembly, not in the host process.
A QuickJS context starts with no host bindings at all — no fetch, no
process, no require, no timers — so an expression cannot read environment
variables, reach the network, or touch the filesystem. It is capped at 1 MB of
heap and interrupted after 1 second.
This matters because the expression is reachable by prompt injection from any untrusted node input, and the worker's environment holds the database URL and the credential encryption key.
Outbound requests
Workflow automation means fetching arbitrary URLs, but "arbitrary" stops at the private network. The HTTP Request node and the agent's HTTP tools reject:
- loopback (
127.0.0.0/8,::1) and0.0.0.0 - link-local (
169.254.0.0/16,fe80::/10) — which includes the cloud metadata address - private ranges (
10/8,172.16/12,192.168/16,fc00::/7) .internal,.localand.localhosthostnames- any scheme that isn't
http/https
Set ORCHKA_ALLOW_PRIVATE_NETWORK=1 to opt out, if workflows genuinely need to
call internal services.
This is a hostname and literal-IP check. A public hostname that resolves to a private address (DNS rebinding) still gets through. A stricter boundary would resolve the address and re-check it, or route egress through a proxy.
Admin surface
/settings/engine and the queue statistics are restricted to addresses in
ORCHKA_ADMIN_EMAILS, which denies everyone when unset. The restriction matters
because job scheduler ids embed workflow ids, so the stats would otherwise let
any signed-in user enumerate every organization's workflows and schedules.
The link is hidden from the sidebar for non-admins and the page redirects them away. The control that actually enforces this is server-side on the data itself: the statistics procedure returns not found regardless of how the page is reached.
Known limits
Stated plainly rather than left implied:
- No DNS-rebinding protection on outbound URL checks, as above.
- No key rotation.
CREDENTIAL_ENCRYPTION_KEYcannot be rotated without re-encrypting every credential by hand, and losing it is unrecoverable. - Email verification off by default, with the squatting window described above.
- No secret scanning on workflow output. Node output is stored and shown in the execution viewer; if a workflow prints a secret, it is visible to anyone in that organization.