Security

The tenancy and credential model, written for a security reviewer: where data lives, who holds which credential, how trust is derived and what Ciresk never holds.

Topology in three lines

An organization is pinned to exactly one regional cell by its jurisdiction; the only cell today is eu1 in AWS eu-central-1. Everything with content that Ciresk CI stores (source deltas, logs, artifacts, caches, test results) stays in the cell, also when your jobs run on your own workers. Identity (WorkOS), API key records and routing are the shared Ciresk control plane and hold no build content.

Crosses regionsNever crosses
Identity, organization placement, API key records, monthly usage totalsSource, diffs, logs, artifacts, caches, test results, worker metadata with paths

Tenancy in the cell

ControlDesign
DatabaseEvery table with organization data carries the organization id and the region, with row-level security enabled; requests run under a role without BYPASSRLS and without DELETE outside a few short-lived tables (leases, idempotency keys, webhook deliveries, worker credentials); every request transaction opens with the organization set, so a query without it fails rather than leaks.
Object storeKeys are tenants/{organization}/... (for example tenants/{organization}/cas/{sha256}); the bucket is encrypted with one customer-managed KMS key per cell; in the cell's AWS account only the server's role is granted access to tenants/, while the worker and release roles reach release/ only; old object versions expire after 30 days.
Presigned URLsMinted for one object key; an upload URL lives at most 15 minutes and never outlives the lease, a download URL at most 60 minutes.
WorkersBelong to exactly one organization and lease only that organization's tasks.
Region guardA credential minted for another region is refused with 421 wrong_region before any SQL.
ExistenceNot-found and forbidden are the same 404 across organizations.

Credentials: all short-lived, none shared

WhoCredentialObtainedLifetime
A person in the CLIAccess tokenDevice flow in ciresk ci auth login, stored in the OS keychainRefreshed in the keychain
A person's MCP clientOAuth token for the CI MCP endpointOAuth 2.1 from the clientThe same
A machine, an agent sandboxcsk_live_eu_... keyplatform.ciresk.com under API tokens; introspected by fingerprint, cached 60 s, never stored in the cellUntil revoked (effective within 60 s)
GitHub Actions, Azure DevOps, an agent sandboxCell token (cix_) from POST /v1/ci/auth/exchangeAn OIDC token whose issuer, audience and subject match a trust your organization registered, which names the scopes and the trust tier1 hour
A workerWorker token (ciw_) scoped to one worker id and the ci:workers.* scopesjoin with a one-time enrolment secret an admin issued24 hours, renewed by the worker; revoked on deregister
A taskPer-task presigned URLs and pass_env valuesMinted by the cell at lease time; pass_env resolved on the workerThe lease

Scopes are ci:<resource>.<verb> (ci:jobs.run, ci:jobs.run_full, ci:repos.write, ci:repos.admin). The MCP endpoint forwards your token to the same API, so every surface (REST, CLI, MCP) checks the same scope for the same capability.

Trust tiers

Trust is derived server-side from principal kind, ref and your organization's policy, stored on the job and carried in the task's cache token. The client never supplies it.

TierWhoWorkspaceIsolationCache
trusted_interactiveYour developers and their local agents, on your own branchesWarm, persistent, on a worker you ownUnprivileged user, cgroup caps, egress proxyReads its own tier and trusted_ci; writes its namespace
trusted_ciProtected branches, CI systems through OIDC exchangeA fresh clone from the worker's mirror per taskUnprivileged user, cgroup caps, egress proxyReads and writes; the only writer other tiers read
untrustedFork PRs, policy-marked refs, unknown principals, agent branches when policy saysA fresh clone that keeps no link to the shared mirrorIts own network namespace whose only exit is the proxy, always in block mode; leased only by workers labelled isolation=vm or disposableReads trusted_ci entries; writes to a namespace of its own job

Cache keys are write-once (a second write with different content is refused and audited), entries record their writer's tier, and reads filter by the reader's allowed set. Because an untrusted job writes only to its own namespace, it cannot write a key a trusted build reads, which rules out the cache-poisoning class of attack by construction. Cache archives are checked before restore: no absolute paths, no .., no link that escapes the root.

Isolation on a worker

ControlMechanism
UserTasks run as an unprivileged account (ciresk-run by default), dropped to with runuser; the worker itself runs as root.
CPU and memorysystemd-run --scope with CPUQuota, MemoryMax, TasksMax; the whole scope is killed on timeout, cancel or lease loss.
EgressA per-task proxy with your organization's allowlist (default: crates.io, github.com, registry.npmjs.org). Audit mode records every unlisted destination on the job and lets it through; block mode refuses it. For trusted_ci and trusted_interactive the proxy is set through proxy environment variables, so a program that ignores them is not stopped; untrusted runs in its own network namespace, where the proxy is the only exit.
Instance metadata169.254.169.254 and private address ranges are refused by the proxy; on Ciresk's AWS workers an iptables rule rejects metadata traffic from the task user, IMDSv2 has hop limit 1, and the instance role reaches release/ only, not organization objects.
InboundNone; the health endpoint binds 127.0.0.1:9471 by default.
Static hostsA static host (an office box, a PC under WSL2) that is not labelled disposable leases trusted_ci and trusted_interactive work only.

Ciresk-run workers

Workers that Ciresk runs follow the same rule as yours: each instance is enrolled into one organization and leases only that organization's tasks. The eu1 pool is a fixed set of EC2 instances that are started on queue depth and stopped when idle, plus burst instances launched past them under an hourly cost ceiling and terminated when idle; while an instance runs it is reused between jobs of the same organization rather than replaced.

Egress

Per-organization allowlist checked by the worker's proxy. Audit mode is the default today: every unlisted host is recorded on the job and shown in the log as a [ciresk] egress audit: line, and the connection goes through. Block mode is set by your organization's policy; the untrusted tier is always block mode.

Audit

Every job records its principal, actor (agent id and session), client (CLI, MCP, API, webhook) and region, and every task the worker that ran it. Job runs and cancellations, policy and budget refusals, leases, policy changes, OIDC exchanges, cache write conflicts, worker enrolment, registration and deregistration, repository creation and deletion and webhook changes write an audit_log row with the principal, actor, client, region and X-Request-Id. One shared function writes the table, and the request role can insert into and read the audit log and the job journal but can neither update nor delete them. Rows are readable through GET /v1/ci/audit with the ci:audit.read scope. Audit export and a configurable retention period are not available yet; rows are kept.

Operator access

Ciresk staff have no product path to an organization's source, logs or artifacts: there is no administrative capability over organization content, and every request is fenced by row-level security. Infrastructure paths exist (the owner database credential, the server's storage role, a shell into the server task); they are not yet behind a break-glass gate, and access through them is not recorded in your organization's audit log.

What Ciresk never holds

Never held by the control plane or the cellWhere it lives instead
Your deploy secretsThe worker's own secrets backend (environment, file:, AWS Secrets Manager, GCP Secret Manager, Azure Key Vault); pass_env values go from there into the task process.
Git credentialsNever on a worker or in a task: the worker's mirror fetches without one and writes no credential into its config; a private repository runs from the tree the cli uploads.
pass_env valuesOnly their names reach the cell; the worker masks the values in the log before it leaves the worker.
Trust chosen by the clientDerived in the cell.
An agent's prompts, transcripts or tokensNot sent; the job carries an actor id and a session id only.
Source, logs, artifacts, caches outside your cellThe cell's object store under your organization's prefix, encrypted with the cell's key.
Long-lived tokens in agent environmentsThe CLI keeps a person's token in the OS keychain; sandboxes get one-hour OIDC-exchanged tokens or revocable csk_ keys.

Identity is hosted by WorkOS, which stores identity data in the US; identity holds no source or build content.

Supply chain and compliance

How releases are signed and what is attested today is in Compliance; where every kind of data lives is in Data residency.