Everything you need to use Dbrij Secrets from code: the CLI, the Node, Python and Go SDKs, machine tokens, logging in from a pipeline, the machine API, AI assistants, plans and errors.
Dbrij Secrets keeps your app keys: database addresses, payment keys, tokens. Each project holds environments (development, staging, production, or your own), and each environment holds named values in folders.
Values are encrypted on your device, in your browser or by the CLI and SDKs, before they reach Dbrij. An environment is sealed unless an admin chooses otherwise: Dbrij stores only encrypted values and cannot open them. An admin may make an environment readable so Dbrij can deploy it to Hosting, rotate a key, or sync it to an outside service; each time Dbrij opens it, the reason is written to the project’s activity.
Key names, folders, tags and the history of changes are stored as plain text, which is what makes comparing, searching and the activity trail work. Never put a secret in a key name. The security page says exactly what Dbrij can and cannot see.
dbrij login and unlock their vault key with its passphrase.Install the CLI, sign in, and start your app with its secrets. Nothing is written to disk unless you ask for it.
npm install -g dbrijdbrij login
dbrij secrets link --project my-app --env developmentdbrij secrets run -- npm startlink writes .dbrij/secrets.toml in the folder, so later commands know the project and environment. You can also pass --project and --env every time, or set DBRIJ_SECRETS_PROJECT and DBRIJ_SECRETS_ENV.
Make a machine in the project, give it the environments it may read, and copy its token once. Then:
DBRIJ_SECRETS_TOKEN=dbs_… npx dbrij secrets run --env production -- node server.jsA machine is something that reads secrets without a person: a server, a pipeline job, a container. Make one in a project’s Machines tab, choose the environments it may read or write, and optionally the addresses it may come from. Its token is shown once.
dbs_<id>_<auth>_<key>_<check>dbs_<id>_<auth> is ever sent to Dbrij, to log in.key part opens the machine’s private key on the machine itself, and never reaches Dbrij.check part catches a token that was cut short when it was copied.A login gives an access token that ends on its own (after 60 minutes unless the machine is set otherwise, and never after the token’s own end date). The CLI and SDKs log in again for you.
A pipeline that issues identity tokens can log in without a stored login secret. Give the machine a GitHub login in Dbrij Secrets, save the key it shows (dbsk_…) as DBRIJ_SECRETS_KEY, and let the job ask for an identity token. The token is asked for with the audience https://secrets.dbrij.com.
permissions:
id-token: write
contents: read
steps:
- uses: actions/checkout@v4
- run: npx dbrij secrets run --env production -- npm run deploy
env:
DBRIJ_SECRETS_KEY: ${{ secrets.DBRIJ_SECRETS_KEY }}A job of a Source repository can log in the same way. Give the machine a Source CI login naming the repository (and the branch, which a protected environment needs), then save the key it shows in the repository’s CI secrets as DBRIJ_SECRETS_KEY. Each job is given an address and a request token for its own identity token, signed by Source CI and good for ten minutes; the CLI and the SDKs ask for it and log in. The login is bound to the repository itself, so a repository made later under the same name is not trusted.
Worth knowing: CI secrets are kept sealed by Dbrij, so with the key half there Dbrij holds both halves for this machine. For values Dbrij must never be able to open, run the job somewhere that keeps the key itself.
jobs:
deploy:
steps:
- npm ci
- npx dbrij secrets run --env production -- npm run deployA machine running on a cloud can log in as the identity the cloud already gave it, so no login secret is stored anywhere. Give the machine a login of that kind in Dbrij Secrets, naming the one identity it trusts, and give the machine the key it shows as DBRIJ_SECRETS_KEY. The key still opens the values on the machine itself; the cloud identity only proves who is asking.
WHAT EACH LOGIN TRUSTS, AND WHERE THE PROOF COMES FROM
| AWS | IAM role or user ARN | A GetCallerIdentity request signed with the machine’s AWS credentials (environment, ECS, EKS web identity, or EC2 instance metadata). Dbrij sends it to AWS STS, and AWS answers who signed it. The header x-dbrij-secrets-server is signed into it, so it cannot be used anywhere else. Any session of the role is accepted. |
| Google Cloud | service account email | An identity token from the metadata server, asked for with the audience https://secrets.dbrij.com. |
| Azure | tenant, managed identity object ID, app ID URI | A managed identity token, asked for an app you register in your own tenant for Dbrij Secrets, so the token opens nothing else of yours. On App Service and Functions it comes from their own token service (IDENTITY_ENDPOINT); on virtual machines, scale sets and AKS from the instance metadata service. With more than one identity, set AZURE_CLIENT_ID. |
| Kubernetes | cluster issuer, namespace, service account | A projected service account token with the audience https://secrets.dbrij.com, read from /var/run/secrets/dbrij/token (or DBRIJ_SECRETS_KUBERNETES_TOKEN_FILE). The cluster’s issuer must publish its keys at an https address, as managed clusters do. |
containers:
- name: app
env:
- name: DBRIJ_SECRETS_KEY
valueFrom: { secretKeyRef: { name: dbrij-secrets, key: key } }
volumeMounts:
- { name: dbrij-token, mountPath: /var/run/secrets/dbrij }
volumes:
- name: dbrij-token
projected:
sources:
- serviceAccountToken: { audience: https://secrets.dbrij.com, expirationSeconds: 3600, path: token }The CLI and the SDKs read the kind of login from Dbrij and fetch the proof themselves; there is nothing else to set.
| DBRIJ_SECRETS_TOKEN | string | A machine token. Read by the CLI and every SDK. |
| DBRIJ_SECRETS_KEY | string | A login key (dbsk_…) for GitHub Actions, Source CI, AWS, Google Cloud, Azure or Kubernetes, used when there is no token. |
| DBRIJ_SECRETS_ENV | string | The environment to read. Optional when the machine reads only one. |
| DBRIJ_SECRETS_PROJECT | string | The project, for people using the CLI. A machine already belongs to one project. |
| DBRIJ_API_URL | string | The API address. Defaults to https://api.dbrij.com/api/v1. |
Every command reads the project and environment from flags, from .dbrij/secrets.toml, or from the environment variables above.
dbrij secrets run -- npm start # start a command with the secrets in its environment
dbrij secrets run --watch -- npm start # restart it when the secrets change
dbrij secrets export --format env # or json, yaml, docker, shell; --out FILE
dbrij secrets get DATABASE_URL # print one valuedbrij secrets set STRIPE_KEY=sk_live_… # one or more; set NAME alone reads the value from stdin
dbrij secrets set --template 'DATABASE_URL=postgres://${DB_USER}@${/shared.DB_HOST}/app'
dbrij secrets delete OLD_KEY
dbrij secrets diff # compare with a local .env (--file, --show-values)
dbrij secrets pull # write them to a local .env (--file, --force)
dbrij secrets push # save a local file into the environment (--file, --prune)push also takes a file dotenvx encrypted: it is opened on your machine with your dotenvx private key (DOTENV_PRIVATE_KEY, or DOTENV_PRIVATE_KEY_PRODUCTION for .env.production, from the environment or .env.keys beside the file), and every value is sealed again before it leaves. The key never does.
run, export, get and pull stop when a template cannot be filled in, naming the reference; --allow-missing goes on without that secret. It is never handed out empty.
dbrij secrets link # remember the project and environment for this folder
dbrij secrets status # who is reading, and from where
dbrij secrets lock # forget the unlocked vault key and local copies on this devicedbrij secrets lease postgres-readonly -- psql # a short lived user in DATABASE_URL, revoked when psql ends
dbrij secrets lease postgres-readonly --ttl 1h # print one (--var NAME, --format json)
dbrij secrets lease revoke ID # end one earlydbrij secrets scan # secrets written into this folder’s files
dbrij secrets scan --staged # only what is staged for the next commit
dbrij secrets scan --history # every commit (--range main..HEAD for some)
dbrij secrets scan --install-hook # run the staged scan before every commit (--force replaces a hook)--path /api reads one folder; repeat it to merge several in order, a later folder winning. --recursive includes the folders inside.
A change to a protected environment is refused from the CLI: it needs approval, which is asked for in Dbrij Secrets.
@dbrij/secrets loads an environment with a machine token and opens it on your own server. Node 20 or newer, no dependencies.
npm install @dbrij/secretsimport { DbrijSecrets } from '@dbrij/secrets';
// Reads DBRIJ_SECRETS_TOKEN and DBRIJ_SECRETS_ENV from the environment.
const secrets = new DbrijSecrets({ environment: 'production' });
await secrets.load();
const databaseUrl = secrets.get('DATABASE_URL');
secrets.injectEnv(); // or put every value into process.envrefresh() asks with the last ETag, so an environment that has not changed costs a small 304.failSoftMs (one hour). A refusal, such as a revoked token, is never hidden.watch(onChange) follows the environment’s version stream and calls you when values change. It returns a function that stops watching.set(values) and delete(names). Values are sealed before they leave, and a save is refused if the environment changed since the copy it started from.OPTIONS
| token | string | Defaults to DBRIJ_SECRETS_TOKEN, then DBRIJ_SECRETS_KEY. |
| environment | string | Defaults to DBRIJ_SECRETS_ENV, or the only environment the machine reads. |
| paths | string[] | Folders to merge in order, a later one winning. Defaults to ['/']. |
| recursive | boolean | Include the folders inside. Defaults to false. |
| baseUrl | string | Defaults to DBRIJ_API_URL, then https://api.dbrij.com/api/v1. |
| failSoftMs | number | How long the last good copy is served while Dbrij cannot be reached. One hour. |
| timeoutMs | number | Per request. Ten seconds. |
dbrij-secrets does the same in Python 3.9 or newer, with the cryptography package.
pip install dbrij-secretsfrom dbrij_secrets import DbrijSecrets
# Reads DBRIJ_SECRETS_TOKEN and DBRIJ_SECRETS_ENV from the environment.
secrets = DbrijSecrets(environment="production")
secrets.load()
database_url = secrets.get("DATABASE_URL")
secrets.inject_env() # or put every value into os.environrefresh() asks with the last ETag, so an unchanged environment costs one small request.fail_soft_seconds (one hour). A refusal is never hidden.watch(on_change, every_seconds=30) refreshes in a background thread and calls you when values change. It returns an event; set it to stop.set(values) and delete(names); values are sealed first.The options match the Node SDK, in Python’s spelling: token, environment, paths, recursive, base_url, fail_soft_seconds, timeout.
The Go SDK does the same in Go 1.24 or newer, with the standard library only. A client is safe for use by several goroutines.
go get github.com/SAW-TECHNOLOGIES-LIMITED/Dbrij/packages/secrets-goimport dbrijsecrets "github.com/SAW-TECHNOLOGIES-LIMITED/Dbrij/packages/secrets-go"
// Reads DBRIJ_SECRETS_TOKEN and DBRIJ_SECRETS_ENV from the environment.
secrets, err := dbrijsecrets.New(dbrijsecrets.Options{Environment: "production"})
if err != nil {
log.Fatal(err)
}
if _, err := secrets.Load(ctx); err != nil {
log.Fatal(err)
}
databaseURL, _ := secrets.Get("DATABASE_URL")
secrets.InjectEnv(true) // or put every value into the process environmentRefresh asks with the last ETag, so an unchanged environment costs one small request.FailSoft (one hour). A refusal is never hidden.Watch(ctx, onChange, onError) follows the environment’s version stream and calls you when values change, until ctx is done.Set and Delete; values are sealed first.*dbrijsecrets.Error, with the HTTP Status (0 when Dbrij could not be reached) and Dbrij’s Code.The options match the Node SDK, in Go’s spelling: Token, Environment, Paths, Recursive, BaseURL, FailSoft, Timeout, HTTPClient.
dbrij mcp lets an assistant in your editor work with Secrets without ever seeing a value. Names come through the API; values are only handled on your machine.
TOOLS
| list_secret_projects | read | The projects in a space, with their environments and how many secrets each holds. |
| list_secret_names | read | The names in one environment. Never values. |
| compare_secret_names | read | Which environments have each name and which lack it, such as a key set in staging but forgotten in production. |
| set_secret | write | Saves a value, sealed on your machine before it leaves. |
| run_with_secrets | run | Runs a command with the secrets in its environment and hands back the output with every value hidden. |
| send_request_with_secrets | run | Sends an https request with placeholders like {{secret:STRIPE_KEY}} filled in on your machine, and hands back the answer with every value hidden. A secret goes only to the hosts a person listed on it. |
| run_with_database_lease | run | Leases a short lived database user, runs a command with its address, then revokes it. The assistant never sees the password or the address. |
On a secret in Dbrij Secrets, list the hosts an assistant may send it to (its send-to list, such as api.stripe.com). The assistant writes the request with {{secret:STRIPE_KEY}} where the key goes (or {{secret:NAME|base64}}, {{secret:NAME|url}}), and it is filled in on your machine and sent. A request to any other host, over plain http, or with a placeholder where the host belongs is refused before anything is sent. Redirects are not followed. An assistant cannot change the list: its writes never carry it.
Opening a project needs a key: a machine token in DBRIJ_SECRETS_TOKEN, or your vault key unlocked in a terminal by any dbrij secrets command. An assistant can never be asked for your passphrase.
A template is a value built from other secrets, filled in where it is read. Values are encrypted on your device, so filling in happens there too: in the CLI, the SDKs and your browser. Only a value saved as a template is filled in; one that merely holds ${...}, like a shell snippet, stays as written.
WRITING A REFERENCE
| ${KEY} | same folder | KEY in the same folder. |
| ${/api.KEY} | another folder | KEY in the folder /api of the same environment. ${/.KEY} is the top level. |
| ${production.KEY} | another environment | KEY at the top of the environment called production. |
| ${production./api.KEY} | both | KEY in /api of production. |
dbrij secrets set --template 'DATABASE_URL=postgres://${DB_USER}:${/shared.DB_PASS}@${production.DB_HOST}/app'${ offers the names you can use, and a value with references can be saved as a template.A folder can import other folders of the same environment, so /api gets everything in /shared without copying it. The folder’s own values win, then a later import over an earlier one, then what the imported folders import. A folder that another imports cannot be deleted until the import is removed. Change imports above the list of secrets in a folder.
A lease is a database user made for one person or machine, for a short time, and removed when it ends. Nobody keeps a standing password for a quick look at production, and every lease is in the project’s activity.
An admin adds a lease source in a project’s Connections: a Dbrij Hosting Postgres or MySQL database, whether its users may read or also write, and how long a lease may last (up to 24 hours, renewable up to 7 days in all). Whoever may read the source’s environment may take a read lease; a write lease needs a role that may change it. A machine leases with its own token.
dbrij secrets lease postgres-readonly -- psqlA database outside Dbrij can still have its password rotated: in Connections, a rotation that holds two Postgres or MySQL users switches between them, so the app always has one that works. On MySQL each of the two users must exist for one host only, and the admin user needs the CREATE USER privilege and to read mysql.user.
A person or a machine can be given some folders of an environment instead of all of it: a contractor gets /frontend in Development, a billing worker gets /billing in Production. Choose the folders when you give access, in People or in Machines.
/ on its own is the top level only.What it is not: whoever reads an environment holds its key, as the encryption needs. Folder access limits what Dbrij sends and accepts, not what that key could open if somebody already had the encrypted values. For a hard wall, put the values in an environment of their own and give access to that.
Secrets end up in code by accident. Dbrij looks for them in two places: on your own machine before they are committed, and in Dbrij Source when they are pushed.
dbrij secrets scan
dbrij secrets scan --install-hookThe scan knows the shapes of common keys (cloud, payment, chat and source hosting keys, private keys, database addresses with a password, and every Dbrij key). Values that read like examples are skipped. Nothing leaves your machine. It exits with 1 when it finds something, so it can stop a pipeline too, and --json prints the findings for a tool to read. A line meant to be there, such as a test fixture, can carry the comment dbrij-scan: ignore.
Every push is scanned. A repository’s settings choose what happens when a secret is found: warn (the default: the push goes through, and the pusher and the repository’s owner are told), block (the push is refused, naming the file and line), or off. Findings are listed under Secrets found, with the value never shown, and are settled as replaced or as not a secret.
When a found key is a live Dbrij key, the finding says so, so it can be revoked first.
Every project keeps an activity trail: who read, changed, approved or opened what, and from where. Values are never in it. Admins can take it out of Dbrij in two ways.
From the project’s Activity, download a date range as CSV or JSON, up to 50,000 events at a time. On every plan, within the history your plan keeps.
On Business, an admin can send the trail to an https address of your own, such as a log collector or your security tools. New events are delivered in order, in batches, within about a minute.
{ "id": "evt_…", "type": "secrets.activity", "createdAt": "…", "data": { "project": { "id": "…", "slug": "my-app", "name": "My app" }, "events": [ { "id": "…", "action": "secret_updated", "detail": "…", "environment": { "id": "…", "name": "Production" }, "keys": ["DATABASE_URL"], "actor": { "kind": "person", "id": "…", "name": "Ada" }, "ip": "203.0.113.5", "userAgent": "…", "createdAt": "…" } ] } }Each delivery is signed with the stream’s secret, shown once when you save it. Check X-Dbrij-Signature-V2 (a time and a signature of the time and the body) before trusting a delivery.
import { createHmac, timingSafeEqual } from 'node:crypto';
// header: the X-Dbrij-Signature-V2 value, body: the raw request body
function verified(header, body, secret) {
const parts = Object.fromEntries(header.split(',').map((p) => p.split('=')));
if (Math.abs(Date.now() / 1000 - Number(parts.t)) > 300) return false;
const want = createHmac('sha256', secret).update(`${parts.t}.${body}`).digest('hex');
return timingSafeEqual(Buffer.from(want), Buffer.from(parts.sha256));
}A decoy is a key that looks real and opens nothing. Plant one in an environment, under a name an intruder would reach for. Nobody with a reason uses it, so the moment somebody does, the project’s admins are told, with the address it came from.
Decoys are real Dbrij key shapes (an API key or a Storage key), so they are recognised wherever they are tried against Dbrij. Every use is refused, exactly like a wrong key. Add them in a project’s Decoys, up to 20.
A decoy tells you a key was used, not that it was read. Reading an environment is in the activity trail already.
The SDKs and the CLI are the easy way in. This is what they call, for a language without an SDK. Every answer carries encrypted values and wrapped keys; the machine opens them with the key part of its token, which never reaches Dbrij. The opening is the same as the Node SDK’s, which is open source and has no dependencies to read through.
Answers come inside the usual envelope: { "success": true, "data": … }.
Exchange the first two parts of a machine token for an access token that ends on its own.
curl -X POST https://api.dbrij.com/api/v1/secrets/machine/login \
-H "Content-Type: application/json" \
-d '{ "token": "dbs_<id>_<auth>" }'{ "accessToken": "…", "expiresAt": "2026-10-30T13:00:00.000Z", "machine": { "id": "…", "name": "api server", "projectId": "…" }, "credentialId": "…" }Log in from GitHub Actions with its identity token: { "credential": "<id from the dbsk_ key>", "token": "<identity token>" }. The answer is the same as above.
Log in from AWS, Google Cloud, Azure or Kubernetes: { "credential": "<id>", "token": "<platform token>" }, or for AWS { "credential": "<id>", "aws": { "url", "method": "POST", "headers", "body" } }, a signed GetCallerIdentity request. GET /secrets/machine/login/hint?credential=<id> says which kind a credential is and which audience to ask for.
A database lease for the machine: { "source": "postgres-readonly", "ttlMinutes": 60 }. The answer holds the user, its password and a ready address. POST /secrets/machine/leases/:id/renew and /revoke extend or end it.
The machine, its project, and the environments it may read, with its role in each.
Everything one read needs, in one round trip. Send the last ETag back as If-None-Match for a 304 when nothing moved.
QUERY PARAMETERS
| env | string | The environment short name. Optional when the machine reads only one. |
| path | string | A folder like /api. Repeat it to read several. |
| recursive | "true" | Include the folders inside. |
curl "https://api.dbrij.com/api/v1/secrets/machine/bundle?env=production&path=/" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H 'If-None-Match: "…"'{ "project": { "slug": "my-app", … }, "environment": { "slug": "production", "version": 42, "keyVersion": 3, "protected": true }, "access": "read", "holder": { "kind": "machine", "encryptedPrivateKey": "…", "keySalt": "…", "algo": "…" }, "wraps": [ … ], "folders": [ … ], "items": [ { "key": "DATABASE_URL", "folderPath": "/", "version": 7, "keyVersion": 3, "ciphertext": "…", "iv": "…" } ], "etag": "…" }Save sealed values, for a machine with write access. Send If-Match: "v<version>" with the environment version the change started from; the save is refused if anything moved since. Each change carries an idempotencyKey, so a retry never applies twice.
A stream (server sent events) of the environment version. Fetch the bundle when it moves. A machine revoked while it listens is cut off within a heartbeat.
People are billed; machines, pipelines and servers are free on every plan. A year is billed as ten months. Every safety feature (roles, temporary access, approvals, history, the activity trail and its export, rotation, leases, decoys, share links) is on every plan. Prices are before VAT.
| Free | Team | Business |
|---|---|---|
| Free | ₦2,000 a person a month | ₦4,000 a person a month |
| Up to 3 people | Any number of people | Any number of people |
| 3 projects, 3 environments in each | 25 projects, 10 environments in each | 500 projects, 20 environments in each |
| 2 syncs | 20 syncs | 200 syncs |
| 30 days of history | 1 year of history | 3 years of history |
| Activity kept 90 days | Activity kept 1 year | Activity kept 3 years |
| Up to 1 approval | Up to 3 approvals | Up to 5 approvals |
| Built in roles | Built in roles | Roles of your own |
| Activity export | Activity export | Activity stream |
A limit only refuses something new: a project, a person, an environment, a sync. Reading values, deploys and syncs already running never stop. After moving to a smaller plan, older history is hidden and the newest syncs past the limit pause; nothing is deleted, and they come back on moving up. The activity trail is the one thing with an end: an entry past the plan’s window is hidden at once and deleted 90 days later, so IP addresses are not kept for ever and an upgrade soon after a downgrade still brings it back.
| 401 | login | The token or access token is wrong, revoked or past its end date. Log in again, or make a new token. |
| 402 | plan limit | Code plan_limit_exceeded. The limit field names which one (projects, environments, people, syncs, approvals, custom_roles, history_days), how much is used, and where to move up. |
| 403 | access | The machine may not read or write that environment, or is calling from an address it is not allowed. |
| 409 | conflict | environment_changed or secret_changed: something moved since your copy; reload and save again. change_request_required: the environment is protected and changes need approval. |
| 429 | rate | Too many requests. Wait for Retry-After. |
Dbrij is Nigeria’s company operating system: the first Nigerian built platform to put team chat, video meetings, email, HR, tax compliant payroll, customer support, marketing, analytics and cloud hosting in one product on one subscription.