Organizing secrets per project
grantpin has one vault. Separation between projects comes from two things you set up yourself: how you name secrets, and a policy file in each repository. This page shows a convention that works well.
Namespaces
Section titled “Namespaces”A secret name may contain /, which you can use as a namespace. Names use the characters A-Z a-z 0-9 _ / . -.
For a shop, a blog and a Sentry project they share, you might store:
grantpin set shop/STRIPE_KEYgrantpin set shop/DATABASE_URLgrantpin set blog/DATABASE_URLgrantpin set shared/SENTRY_DSNshared/ is just a naming convention for secrets more than one project uses. It has no special meaning to grantpin. Neither does shop/. A namespace is only part of the name.
grantpin list shows them all. You can add tags (--tags shop,prod) and filter with grantpin list --tag shop. Tags are labels for you. They do not restrict anything.
Environment variable names
Section titled “Environment variable names”Inside the command, each secret becomes an environment variable named after the last path segment, uppercased (. and - become _):
| Secret name | Environment variable |
|---|---|
shop/STRIPE_KEY |
STRIPE_KEY |
shop/DATABASE_URL |
DATABASE_URL |
shared/SENTRY_DSN |
SENTRY_DSN |
If two secrets in the same run would get the same variable name, all of the colliding secrets use their full name instead. Asking for shop/DATABASE_URL and blog/DATABASE_URL together gives SHOP_DATABASE_URL and BLOG_DATABASE_URL. Secrets that do not collide keep their short name.
--env-prefix puts a prefix in front of every name, verbatim:
grantpin run --secrets shop/STRIPE_KEY --env-prefix APP_ -- printenv APP_STRIPE_KEYThe result must still be a valid uppercase variable name, so APP_ works and app_ is an error.
A policy per project
Section titled “A policy per project”Put a .grantpin.yml at the root of ~/projects/shop and commit it:
version: 1secrets: - shop/** - shared/SENTRY_DSNcommands: - [npm, run, migrate] - [./scripts/deploy.sh]max_grant_ttl: 2hNow a request from inside ~/projects/shop (or any subfolder) can only name secrets under shop/ or shared/SENTRY_DSN, and only run those two commands. A request for blog/DATABASE_URL is refused with policy_refused before any dialog appears. The same agent working in ~/projects/blog is held to that project’s own policy.
Two details worth knowing:
- A literal secret name matches a policy entry exactly or through a glob (
shop/**coversshop/STRIPE_KEY). A requested glob such asshop/*is only allowed if that exact text is in the policy. - The nearest
.grantpin.ymlwins and policies are never merged. In a monorepo, a policy in a subfolder replaces the one at the root. See Policy files.
Grants follow the same lines
Section titled “Grants follow the same lines”A grant (what “Allow for 1h” creates) is scoped by:
- the directory where the request runs, and everything beneath it,
- the secrets that were requested,
- the command prefix (taken from the policy when there is one),
- and the caller name the agent reported.
An hour-long grant made in ~/projects/shop does not cover ~/projects/blog. More in Grants and approvals.
Moving an existing .env file in
Section titled “Moving an existing .env file in”grantpin import reads KEY=value lines from a .env file and stores each key as a secret. With --namespace, each key is stored under that namespace:
grantpin import ~/projects/shop/.env --namespace shop --dry-rungrantpin import ~/projects/shop/.env --namespace shopSTRIPE_KEY in the file becomes the secret shop/STRIPE_KEY. --dry-run prints the final names without writing anything, and --on-conflict (skip, overwrite or error, the default) is checked against those final names, so importing the same file under two namespaces never conflicts. The namespace must be a valid secret name: only A-Z a-z 0-9 _ / . -, not starting or ending with /, with no //, no .. and no . segment. An invalid namespace is refused before the file is read.
Without --namespace, keys keep their bare names. An import is not atomic: if the daemon fails midway, some keys may already be stored.
Delete the .env file once you have checked the import. That is the point.