Secrets
Give your sessions a private registry token or a test API key without putting it in the session. A secret belongs to your organization; a session holds only a placeholder for it, and the value is attached outside the session, on the way out, to requests to the hosts the secret is bound to.
Session secrets need the installation to keep a secret store. In
eu1this is not turned on yet:ciresk dev secrets setandciresk dev secrets rmanswersecrets_unavailable, andciresk dev secrets listanswers what is stored, which is nothing. Until it is on, no session gets a secret, and a private dotfiles token cannot be stored either.
How it works
The value never enters the session: not in the environment, not in a file, not in a checkpoint or a diagnostics bundle.
Create or replace a secret
ciresk dev secrets set npm-token --host registry.acme.com --repo '*' < token.txtThe value is read from standard input only, never from an argument or an environment variable. One trailing newline is dropped, so echo and files both work. When standard input is a terminal the command refuses, so the value is never echoed or kept in your shell history. No command shows the value again.
| Flag | Default | What it sets |
|---|---|---|
<name> | required | the secret's name: lower-case letters, digits, - and _, at most 63 characters, starting with a letter or digit, not starting with model_ or ciresk_ (names Ciresk keeps for itself) |
--host <HOST> | required, repeatable | a host the value is attached for, exact: registry.acme.com or registry.acme.com:8443 (port 443 when you give none); 1 to 16 hosts |
--header <NAME> | authorization | the request header the value goes in, for example x-api-key or private-token |
--repo <*|REMOTE> | required, repeatable | * for every repository, or one repository as a remote URL or host/owner/name; 1 to 64 entries |
--template <NAME> | any template | only sessions started from this template get the secret; repeatable, up to 64 |
--agent-sessions | off | sessions started by an agent, a service or an API key get the secret too |
--purpose <proxy|git> | proxy | proxy attaches the value to the session's requests to its hosts; git makes it the credential ciresk dev push uses (see git.md) |
--keep-value | off | send no value and keep the stored one; only for a pure narrowing |
Setting a name that exists replaces its settings and raises its version. The answer prints the version, the scope and the variable sessions send.
What a secret accepts:
- Hosts are exact, lower-case names. A wildcard is refused, because it also covers names a third party hosts under your domain. An IP address is refused. A host of a model or tool vendor is refused (
model_or_tool_vendorin the detail). - Header is a lower-case header name of up to 64 characters. Headers the proxy owns or removes are refused:
connection,keep-alive,te,trailer,trailers,transfer-encoding,upgrade,host,cookie,forwarded,content-length,expect,x-original-url,x-rewrite-url,x-http-method-override,x-http-method,x-method-override, and everyx-forwarded-*andproxy-*header. - Value is 16 to 8192 bytes of UTF-8 with no control character.
When the value is needed again
You must send the value again whenever a change could point the stored value somewhere new: a host added, a different header, a different purpose, more repositories or templates, or agent sessions turned on. Without it the change is refused with value_required. A pure narrowing (fewer hosts, repositories or templates, agent sessions turned off) may use --keep-value:
ciresk dev secrets set npm-token --host registry.acme.com --repo github.com/acme/app --keep-valueList secrets
ciresk dev secrets list
ciresk dev secrets list --jsonOne row per secret, up to 500: its name, purpose, header, hosts, repositories, templates, whether agent sessions get it, and when and by whom it was last set. Never a value. --json returns {secrets: [...]}, each entry with name, purpose, header, hosts, repos, templates, agent_sessions, version, updated_by and updated_at. Personal dotfiles tokens are not in this list (see Private dotfiles tokens).
Remove a secret
ciresk dev secrets rm npm-tokenThe secret and its value are deleted. Running sessions lose it within 5 minutes. To cut a session off at once, stop it with the kill switch (see admin.md).
Which sessions get a secret
A session gets a secret when all of these hold when it starts:
- the secret's repositories include
*or the session's repository; - the secret names no template, or names the session's template;
- the session was started by a person, or the secret allows agent sessions. A session started by an agent identity, a service or an API key is not a person's. An agent working under a person's own sign-in, on their laptop, is that person.
Repositories, templates and agent sessions choose which sessions get a secret. They are not an access boundary: any member who may start a session in your organization can start one that the secret selects, and use the secret through the proxy against its hosts. The boundary is your organization.
When changes reach a running session
The worker holding a session fetches its secrets' values when the session starts and again every 5 minutes. A session's placeholders and the certificate authority it trusts are fixed when it starts:
| Change | A running session |
|---|---|
| Secret removed, or narrowed so it no longer selects the session | loses it within 5 minutes |
| New value | uses it within 5 minutes |
| Host removed | stops attaching for it within 5 minutes |
| Host added | not attached until a new session; run ciresk dev up --recreate |
| New secret, or one widened to select the session | not in the session until a new session; run ciresk dev up --recreate |
A session that is granted secrets never runs without them. If the worker cannot fetch the values when the session starts, or cannot set up the session's certificate authority, the session does not start: ciresk dev up answers session_failed with the reason secrets_not_delivered. Run ciresk dev up --recreate to try again. A session whose granted secrets were all removed or narrowed away before it started starts without them.
Values are handed out only while the session's worker may keep running it: a stopping, killed or paused session gets none, and the proxy drops the ones it holds. After a worker restart, a session that keeps running gets its values again at the next fetch.
What a session sees
In every container of your template in a session with at least one secret, and in every ciresk dev exec:
| Variable or file | What it holds |
|---|---|
CIRESK_SECRET_<NAME> | the placeholder, one per secret of purpose proxy (CIRESK_SECRET_CIRESK_DOTFILES for your dotfiles token) |
/etc/ciresk/extra-ca.pem, NODE_EXTRA_CA_CERTS | the session's own certificate authority (and, when the session has models, the installation's) |
/etc/ssl/certs/ca-certificates.crt, SSL_CERT_FILE, REQUESTS_CA_BUNDLE | the system roots followed by those authorities |
The session's certificate authority is minted for that session only and is name-constrained to the secrets' hosts, so it vouches for nothing else. Its key stays on the worker. A session without secrets gets no authority, no variables and no interception.
CIRESK_SECRET_* and REQUESTS_CA_BUNDLE are reserved: a template that sets one is refused with reserved_env, and ciresk dev exec refuses them with env_not_allowed.
A secret of purpose git gets no placeholder: nothing in the session stands for it.
Where the value is attached
The proxy attaches the value only in the secret's header, and only when that header holds exactly the placeholder, <scheme> <placeholder> (Bearer ...), or, for authorization, Basic credentials whose user or password is exactly the placeholder (git, pip and docker send Basic). The request line and the body are never touched.
A request that is refused is answered 403, nothing is sent to the host, and the decision log records the secret rule (see egress.md):
- a placeholder anywhere else: another header, a header name, another form;
- a placeholder that does not resolve: not granted to this session, bound to another host or header, or a wrong nonce;
- a placeholder in a plain
http://request: a value never crosses the network in clear.
A secret's hosts never widen egress. The destination must still pass the session's egress policy first: the organization's ceilings, the template's egress rows, and the installation's egress.yaml rows for the session product dev-session. Rows the installation keeps for Ciresk's own control plane (dev) or for the worker's push (dev-push) never let a session out, so a bound host needs a dev-session row too.
On a connection to a bound host:
- The client must send the host as its TLS server name, and speak HTTP/1.1. Each connection carries one request and is answered with
Connection: close. - The
Hostheader must name the connection's host, or the answer is421and nothing is attached. TRACE,TRACKandCONNECTare refused with405, an upgrade (WebSocket) with501. Ambiguous framing (bothContent-LengthandTransfer-Encoding, twoContent-Lengthheaders, a bare LF) is refused with400.- Forwarding headers (
Forwarded,X-Forwarded-*,X-Original-URL,X-Rewrite-URL) are removed. - In response headers, each of the session's values is replaced by its placeholder. Trailers are dropped.
What does not work today:
- A host behind a private certificate authority: the proxy verifies hosts against the public roots only.
- A host on a private address, which the egress policy refuses.
- A client that pins certificates, or ignores both the system bundle and the three variables: its TLS to a bound host fails. Other hosts are not affected.
What a bound host returns enters the session. A host that echoes a request header back in a response body, or that mints a new credential from the value (a registry's token endpoint, a token API), hands the session what it returns. Bind only hosts and tokens scoped so that does not matter, such as a read-only registry token.
Do not bind a proxy secret to your git forge's host: the session's own requests to it would carry the value, so ciresk dev push refuses with push_credential_proxied while one does. Store the push credential with --purpose git instead (see git.md).
Private dotfiles tokens
A private dotfiles repository's token is a secret of its own, stored by you for your sessions, not by an admin for the organization:
ciresk dev dotfiles set --repo https://github.com/me/dotfiles --token-stdin < token.txtIt travels the same way as an organization secret of purpose proxy: kept in the installation's secret store, never shown again, delivered only to the worker running the session, and attached by the session's egress proxy while the shell clones the repository. The session holds the placeholder CIRESK_SECRET_CIRESK_DOTFILES, never the token. It differs in these ways:
| Organization secret | Dotfiles token | |
|---|---|---|
| Who stores it | an owner or admin, ciresk dev secrets set (dev:secrets.write) | you, for yourself, ciresk dev dotfiles set --token-stdin (dev:sessions.write); never an agent |
| Where it is attached | the secret's hosts and header | the dotfiles repository's host on port 443, in Authorization |
| Which sessions get it | every session it selects, by repository, template and agent sessions | only sessions you start yourself; a session an agent identity, a service or a key starts skips your dotfiles with the reason agent_session |
| Listed by | ciresk dev secrets list | ciresk dev dotfiles show (credential stored), never the token |
| Removed by | ciresk dev secrets rm | ciresk dev dotfiles rm, which removes the repository and its token; setting a repository on another host also drops the token |
The token is 16 to 8192 bytes with no control character; a token off that rule is refused with dotfiles_invalid, and a token without a dotfiles repository with dotfiles_unset. Any process of yours in the session can use the token against the repository's host, so use a read-only token scoped to that one repository. The host must pass the session's egress like any other destination. See sessions.md for dotfiles themselves.
Images from your own registry
A template's service can pull a pinned image from a private registry with an organization secret: pull: {secret: <name>, user: <registry user>} (see templates.md). The worker uses the value for the pull only. ciresk dev up refuses a template whose pull secret the session is not granted with pull_secret_not_granted, naming the secret.
Who may do what
| Operation | Command | Scope | Who may call it |
|---|---|---|---|
| List secrets | ciresk dev secrets list | dev:secrets.read | people, agents, services |
| Create, replace or remove a secret | ciresk dev secrets set, ciresk dev secrets rm | dev:secrets.write | people only |
| Store your dotfiles token | ciresk dev dotfiles set --token-stdin | dev:sessions.write | people only, for themselves |
Owners and admins hold both secrets scopes; members and viewers hold dev:secrets.read. No one can read a value back: no command, API call or tool returns one, and it is never logged, never in an error and never in an audit record.
The audit log records dev.secrets.set (the name, the fields that changed, and whether the value changed), dev.secrets.delete, dev.dotfiles.credential.set (the host, never the token), and dev.session.secrets each time a worker is given a session's secrets, naming them.
Values are delivered to the worker running the session. When your organization runs sessions on its own hosts, those hosts hold your secrets' values while they run your sessions.
Agents
An agent can see which secrets exist, never a value, and can never set or remove one, nor store a dotfiles token: those calls are refused with principal_not_allowed.
- With the MCP server,
dev_secrets_listanswers whatciresk dev secrets list --jsondoes. - An agent uses a secret by putting
${CIRESK_SECRET_<NAME>}where the token belongs in the client's configuration. When a secret is missing, it asks a person to runciresk dev secrets set. - A session an agent identity starts gets only the secrets set with
--agent-sessions, and never its owner's dotfiles token.
Errors
| Code | When | What to do |
|---|---|---|
secret_invalid | a name, host, header, repository, template or value breaks its rule | ciresk dev secrets set --help |
value_required | a new secret, or a widening change, without a value | send the value on standard input, without --keep-value |
secret_not_found | rm names no secret | ciresk dev secrets list |
secrets_unavailable | the installation keeps no secret store | ask the installation's operator |
pull_secret_not_granted | a template's pull secret does not select the session | ciresk dev secrets set <name> --host <registry> --repo <this repository> |
session_failed with secrets_not_delivered | the worker could not deliver the session's secrets at start | ciresk dev up --recreate |
principal_not_allowed | an agent or service called set or rm | ask a person |
The API is in the API reference: GET /v1/dev/secrets, PUT /v1/dev/secrets/{name} and DELETE /v1/dev/secrets/{name}; the dotfiles token is PUT /v1/dev/dotfiles/credential.