Shared secrets
One value across several projects — without copies that drift apart.
Why
Repository credentials, a payment gateway key, mail settings are the same in every project. If you create them separately in each one, a month later you have five "identical" values, two of which have already drifted — and which one is correct will come out on the day the password changes.
A shared secret lives in a single instance. Change it in one place — it changes for everyone.
How it works
A project environment can additionally read the keys of another project's environment within the same organization. This is a reference, not a copy: the value stays at the source and is physically moved nowhere.
billing/prod ← common/prod
DB_PASSWORD SMTP_PASSWORD
(свой) GIT_TOKEN
The billing project in the prod environment serves three keys: its own
DB_PASSWORD and two arriving from common. The SDK and the CLI receive
them identically — an entry arriving via a reference is indistinguishable
from a local one.
What happens on read
Values are encrypted under their own environment's data key. The server decrypts the shared secret with the source key and re-encrypts it with the key of the environment it arrives into. Therefore:
- the response still carries one data key — the consumer's;
- your SDK needs no update: the response format has not changed;
- detaching requires no re-encryption — nothing was ever written into the consumer's environment.
How to connect
Create a project for shared values — say common — and put keys into it
like into a regular project. Then, in the Environments section of the
consumer project, in the "Shared secrets" block, click "Connect" and
choose:
| Field | What it means |
|---|---|
| This project's environment | where the external keys will arrive |
| Source project | where the shared values live |
| Source environment | which exact source environment to read |
A connection is made per environment, separately. This is not extra
work — it is the point of the model: your service's prod reads
common/prod, and dev reads common/dev, with different values for the
same keys.
Environments can be linked by the organization owner. The reason is not hierarchy but the fact that a connection is a two-sided operation. Allowing only the recipient would mean: the admin of their own project attaches to someone else's environment and reads it. Allowing only the source — a person with no role in the project silently changes its configuration. The owner has rights on both sides by definition.
The same operation over the API is one request (the source is specified by slug):
curl -s $BASE/projects/$PROJECT/links \
-H "authorization: Bearer $TOKEN" -H 'content-type: application/json' \
-d '{"environment":"production","fromProject":"common","fromEnvironment":"production"}'
Detaching is DELETE /projects/:id/links/:linkId; listing connections is
GET /projects/:id/links.
Local always beats shared
If an environment has its own key with the same name, it wins. The shared one is not served at all at that point — neither to the SDK nor in the table.
This is how you make an exception: attach the shared SMTP everywhere, and override it with a local one in a single project. Nothing needs to be detached.
The override must match the source in type and visibility. A secret cannot be overridden by a variable: otherwise a value the source declared a secret would become plaintext for everyone reading this environment.
Editing a shared key in a consumer project does not change it at the source — it creates a local override. The dashboard warns about this before saving, but it is worth knowing in advance: "changed the password for everyone" and "changed the password for myself" look identical here.
Two sources with the same key
Rejected at connection time. No winner is chosen, no "last connected" is taken — the operation simply does not go through, with an indication of which project already brings this key.
Silent priority by connection order is a configuration you cannot debug at 2 a.m. when a service reads the wrong value.
Detaching
Removes the reference. From the next request the keys stop arriving; there is nothing to re-encrypt.
Detaching does not pull values out of the memory of already running processes — exactly like deleting an entry or revoking an identity. Revocation stops issuance; it does not erase the past. If a secret is compromised, it must be changed, not detached — and that is the main win of the model: it will have to be changed once.
Webhooks
A change to a shared secret reaches the connected projects too: the
entry.updated event is delivered to every consumer's webhooks, not just
the source's, with a fromProjectId field in the metadata.
If a key is overridden by a local value, the consumer is not notified — for them nothing has changed.
Migrating what is already spread across projects
The Projects section shows the organization owner which keys hold the same value in different projects. A match is found by value fingerprint, without decryption: identical values have identical HMACs.
Only the owner sees the report, for the same reason only they can link environments. The fingerprint is not salted — otherwise the report would be impossible — which means the phrase "the same value here as there" answers a question about a secret the person asking may not have access to. For the owner this adds nothing: they grant themselves any role anyway. Building the report is recorded in the log.
Migration can be done on a running service, and the order matters:
- Create the key in the shared project with the same value.
- Connect the shared project to the consumer environments. The effective configuration does not change: the copies are still in place and override the reference.
- Delete the local copies. The value switches to the shared one — the same value, the same fingerprint; consumers see no difference.
Keys with the same name but different values are marked separately by the report. That is not a duplicate but a divergence: which value is correct is not something a machine can decide.
Limitations
- References are not transitive: if A reads B and B reads C, A does not get C's keys. A ring of references is rejected at creation — not because it would loop the server, but because it would imply expecting transitivity that does not exist.
- References do not cross the organization boundary.
- Deleting a project or an environment removes all references pointing into it.