{"id":23391432,"url":"https://github.com/ahrefs/passage","last_synced_at":"2026-01-20T19:03:41.232Z","repository":{"id":247412501,"uuid":"687224075","full_name":"ahrefs/passage","owner":"ahrefs","description":"store and manage access to shared secrets","archived":false,"fork":false,"pushed_at":"2026-01-14T16:41:46.000Z","size":404,"stargazers_count":7,"open_issues_count":1,"forks_count":2,"subscribers_count":2,"default_branch":"master","last_synced_at":"2026-01-14T19:58:51.350Z","etag":null,"topics":["age","ocaml","secrets-management"],"latest_commit_sha":null,"homepage":"","language":"OCaml","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ahrefs.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGES.md","contributing":null,"funding":null,"license":"LICENSE.md","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2023-09-04T23:15:52.000Z","updated_at":"2026-01-14T16:41:51.000Z","dependencies_parsed_at":"2025-04-11T09:59:49.442Z","dependency_job_id":"c8b1e2a7-b4b5-493e-bca7-e56ef2f85748","html_url":"https://github.com/ahrefs/passage","commit_stats":null,"previous_names":["ahrefs/passage"],"tags_count":12,"template":false,"template_full_name":null,"purl":"pkg:github/ahrefs/passage","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ahrefs%2Fpassage","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ahrefs%2Fpassage/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ahrefs%2Fpassage/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ahrefs%2Fpassage/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ahrefs","download_url":"https://codeload.github.com/ahrefs/passage/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ahrefs%2Fpassage/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28609596,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-20T18:56:40.769Z","status":"ssl_error","status_checked_at":"2026-01-20T18:54:26.653Z","response_time":117,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["age","ocaml","secrets-management"],"created_at":"2024-12-22T04:17:27.614Z","updated_at":"2026-01-20T19:03:41.175Z","avatar_url":"https://github.com/ahrefs.png","language":"OCaml","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Passage\n\n`passage` - store and manage access to shared secrets\n\n## Installation\n\n```sh\napt install age\nopam install . --deps-only --with-dev-setup\n```\n\n## Development\n\nBuilding the project\n```\nmake build\n```\n\nRunning tests\n```\nmake test\n # or\nmake promote\n```\n\nRunning linting\n```\nmake fmt\n```\n\n\n## Secret format\n\nMulti-line secrets with comments:\n```\n\u003cempty line\u003e\npossibly several lines of comments\nwithout empty lines\n\u003cempty line\u003e\nsecret until end of file\n```\n\nMulti-line secrets without comments:\n```\n\u003cempty line\u003e\n\u003cempty line\u003e\nsecret until end of file\n```\n\nSingle-line secrets with comments:\n```\nsingle-line secret\n\u003cempty line\u003e\ncomments until end of file\n```\n\nSingle-line secrets without comments:\n```\nsingle-line secret\n```\n\nThe rationale for why we have 2 distinct secret formats for multi-line and single-line secrets\n(and not just multi-line secrets) is mainly for backward compatibility reasons since most of\nthe existing secrets are of the \"single-line secret\" format.\n\n## Commands\n\n### Reading secrets\n\n`passage get [-c, --clip] [-l, --line=LINE] [-q, --qrcode] [-s, --singleline] SECRET_NAME`\n- Outputs the content of the text of the secret in `SECRET_NAME`, excluding comments\n\n`passage secret [-c, --clip] [-l, --line=LINE] [-q, --qrcode] [-s, --singleline] SECRET_NAME`\n- An alias of `passage get`\n\n`passage cat [-c, --clip] [-l, --line=LINE] [-q, --qrcode] SECRET_NAME`\n- Outputs the whole content of the secret in `SECRET_NAME`, including comments\n\n`passage show SECRET_NAME`\n- Outputs the whole content of the secret in `SECRET_NAME`, including comments. Behaves differently when used with PATHs, please check below.\n\n### Templating with secrets\n\n`passage template TEMPLATE_FILE [TARGET_FILE]`\n- Generates `TARGET_FILE` by substituting all secrets in `TEMPLATE_FILE`\n- Secrets in `TEMPLATE_FILE` are denoted with the following format `{{{subdir/secret_name}}}`. In particular, note the following:\n  - Three opening and closing braces\n  - No leading and trailing whitespaces before and after `secret_name`\n  - `secret_name` must start with an alphanumeric character (either lowercase or uppercase), followed by 0 or more alphanumeric characters, underscores, hyphens, slashes, or dots (reference: [template_lexer.ml](lib/template_lexer.ml))\n\n- Example when substituting a single-line secret:\n  ```sh\n  $ cat template_file\n  {\n    \"non_secret_config1\": \"hello\",\n    \"sendgrid_api_key\": \"{{{sendgrid_api_key}}}\",\n    \"non_secret_config2\": \"bye\",\n  }\n\n  $ passage get sendgrid_api_key\n  thesupersecretkey\n  the above is the sendgrid api key!\n\n  $ passage template template_file\n  {\n    \"non_secret_config1\": \"hello\",\n    \"sendgrid_api_key\": \"thesupersecretkey\",\n    \"non_secret_config2\": \"bye\",\n  }\n  ```\n\n- Example when substituting a multi-line secret:\n  ```sh\n  $ cat template_file\n  foo{{{multiline_secret}}}bar\n\n  $ passage get multiline_secret\n\n  comment_line 1\n  comment_line 2\n\n  secret_line 1\n  secret_line 2\n\n  $ passage template template_file target_file\n\n  $ cat target_file\n  foosecret_line 1\n  secret_line 2bar\n  ```\n\n`passage subst [TEMPLATE_ARG]`\n- similar to passage template, but you pass in a string template and the result is output to stdout\n  ```bash\n  $ passage secret test/secret\n  unbelievable stuff\n\n  $ passage subst \"This secret is {{{test/secret}}}\"\n  This secret is unbelievable stuff\n  ```\n\n`passage template-secrets [TEMPLATE_FILE]`\n- returns a list of sorted secrets identified in that template file per the parse format\n- secrets are not checked for existence\n\n### Specifying recipients\n\nSecrets' recipients are specified in the `.keys` file in the immediately containing folder.  The first time a folder is used, passage will create this file. If no recipients are specified, it falls back to the caller as the sole recipient based on the file referenced by `$PASSAGE_IDENTITY`.\n\nRecipients are not inherited from containing (parent) folders. Recipients in a folder can only be increased when added by the existing recipients.\n\nAll secrets in a given folder must share the same set of recipients.\n\n`passage edit-who SECRET_NAME`\n- edit the recipients for the specified secret (and path).\n\n### Creating or updating secrets\n\n`passage new SECRET_NAME`\n- Interactive secret creation using `$EDITOR` and prompts.\n- Can only be used in interactive shell\n\n`passage create SECRET_NAME`\n- Creates a secret using contents from standard input. Use Ctrl+d twice to signal end of input.\n- Can pipe from another command into `passage create`. E.g.:\n```bash\n$ echo \"secret\" | passage create secret_folder/secret\n```\n\n`passage edit SECRET_NAME`\n- Interactive editing of `SECRET_NAME` using `$EDITOR`\n- Can only be used in interactive shell\n\n`passage replace SECRET_NAME`\n- Replaces `SECRET_NAME`'s secret with the user input and keeps the comments. Use Ctrl+d twice to signal end of input.\n- If you use `replace` on a secret that doesn't exist, it creates a new secret without comments (only in folders where the user is already a recipient)\n\n`passage rm [--force] [--verbose] SECRET_NAME / passage delete [--force] [--verbose] SECRET_NAME`\n- Deletes `SECRET_NAME` path\n- If `SECRET_NAME` is the only secret in that folder, passage deletes the whole folder\n\n### Managing secrets\n\n`passage list [PATH]` / `passage ls [PATH]`\n- Recursively list all secrets in `PATH`\n\n`passage search PATTERN [PATH]`\n- List all secrets in `PATH` containing contents matching `PATTERN`\n\n`passage show [PATH]`\n- Recursively list all secrets in `PATH` in a tree-like format\n- Will work the same way as `cat` when used with secret names instead of a PATH. Doesn't take any arguments or flags\n\n`passage refresh [PATH]`\n- Re-encrypts all secrets in `PATH` per the recipients in the corresponding .keys file\n\n`passage who [PATH]`\n- List all recipients of secrets in `PATH`\n\n`passage what RECIPIENT_NAME`\n- List all secrets that `RECIPIENT_NAME` has access to\n\n## Environment Variables\n\n`PASSAGE_DIR`\n- Overrides the default `passage` directory.\n\n`PASSAGE_KEYS`\n- Overrides the default `passage` keys directory.\n\n`PASSAGE_SECRETS`\n- Overrides the default `passage` secrets directory.\n\n`PASSAGE_IDENTITY`\n- Overrides the default identity `.key` file that will be used by `passage`\n\n`PASSAGE_X_SELECTION`\n- Overrides the default X selection to use when clipping to clipboard.\n  Allowed values are `primary`, `secondary`, or `clipboard` (default).\n\n`PASSAGE_CLIP_TIME`\n- Overrides the default clip time. Specified in seconds.\n\n## Utilities\n\n`passage healthcheck`\n- checks for issues with secrets, and for directories without `.keys` file\n\n`passage realpath [--verbose] [PATH]`\n- show the full filesystem path to secrets/folders\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fahrefs%2Fpassage","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fahrefs%2Fpassage","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fahrefs%2Fpassage/lists"}