Finding secrets
Walk the path tree one level at a time with list, or jump straight to the projects, environments and namespaces a key can reach.
Commands on this page: list · projects · envs · namespaces
list takes any prefix of a path, so whatever you copy out of the console explorer works exactly as
typed. Each call shows what sits directly under that prefix — directories with a trailing /, keys
without:
pulsvault list # projects you can address
pulsvault list acme-web # environments
pulsvault list acme-web/prod # namespaces
pulsvault list acme-web/prod/product/web # folders in that namespace
pulsvault list acme-web/prod/product/web/postgres # the keys it holdsWalking down that ladder looks like this:
$ pulsvault list acme-web/prod
infra/ (namespace)
product/
$ pulsvault list acme-web/prod/product/web
db/
postgres/
$ pulsvault list acme-web/prod/product/web/postgres
pass v1 11B
url v2 47B
url2 v1 30BThe two columns beside each key are its current version and the size of that version's value. No
value is read to produce them, so this is the cheapest way to see what every key in a folder is on —
url is on v2, the other two are still on their first write. Reading a specific one is
get, which names the version it returns.
A directory marked (namespace) is a namespace in its own right,
rather than a path segment on the way to one. You cannot tell those apart by counting segments — a
namespace path may be up to five — so the CLI reads it from the server instead of guessing.
The explorer's copy-path button produces {project}/{env}/{namespace}/{folder}. Paste it straight into
list, get --field, export or run — no trimming, no rewriting. See
Browsing secrets & folders.
The whole subtree at once#
-R (--recursive) prints every secret at or below the prefix as a full canonical path — output you
can pipe straight into get:
$ pulsvault list acme-web/prod/product/web -R
acme-web/prod/product/web/db/postgres/username v1 11B
acme-web/prod/product/web/postgres/pass v1 11B
acme-web/prod/product/web/postgres/url v2 47BThe tree shows folders and keys; -R shows secrets. So a folder holding no secrets appears in
the first and not the second — both correct, and together easily read as a bug.
On a terminal, -R says so:
acme-web/prod/product/web/db/postgres/db1/url v2 32B
acme-web/prod/product/web/db/postgres/db2/ (empty)An empty folder is a real thing: a folder can be created on its own, whereas everywhere else a folder simply is the shape of the secret paths beneath it.
That (empty) line goes to stderr, and only when the output is a terminal. Piped or under
-o json, stdout is byte-for-byte what it has always been — the same reason get masks a value on a
terminal and stays raw in a pipe. -q silences it too.
The server caps a recursive listing (5000 secrets by default). When it cuts one, -R still prints what it
received and warns on stderr — also under -o json, so a script reading the JSON on stdout is never
handed a partial list that looks whole — that the list is incomplete. List a narrower path to see the rest.
export and run go further and refuse to continue.
It works from any level, which is the difference between a quick check and an audit:
pulsvault list acme-web/prod/product/web/db -R # one folder
pulsvault list acme-web/prod -R # a whole environment
pulsvault list acme-web -R # every environment in the projectA namespace or folder is a single request. Anything shallower has no one namespace to enumerate, so the CLI walks down to find them first and then lists each — one request per namespace. A namespace the key cannot read is reported on stderr and skipped, so one tightened grant does not fail the whole walk.
The old --all flag is a deprecated alias for -R. It behaves the same at an environment and warns
once on stderr; -R works at every level.
Scripting a listing#
Under -o json (or -o yaml) list emits the whole listing object, so nothing has to be parsed out of
aligned columns:
$ pulsvault list acme-web/prod/product/web/postgres -o json
{
"path": "acme-web/prod/product/web/postgres",
"level": "FOLDER",
"directories": [],
"secrets": [
{ "name": "url",
"fullPath": "acme-web/prod/product/web/postgres/url",
"currentVersion": 2, "valueSize": 47,
"contentType": "text/plain", "updatedAt": "2026-07-21T09:14:02" }
]
}level is one of PROJECTS, ENVIRONMENTS, NAMESPACE_SEGMENTS, NAMESPACE, FOLDER — so a script
knows which rung it is on. Every row carries a fullPath that can be used directly, with no segment
joining.
Discovery shortcuts#
When you know which rung you want, three commands skip the walk:
pulsvault projects # project slugs this key may address
pulsvault envs # environment codes this key may address
pulsvault namespaces acme-web/prod # namespaces you can list or read (alias: ns)These list what this credential may address, not everything that exists in the space. A key scoped
to one environment is never told which others are there. So if something you expect is missing, the key
is missing a permission — pulsvault whoami shows which ones it has.
They answer "may I address this", not "is it populated", so a correctly provisioned but still-empty environment is listed. That is deliberate: filtering by content would make a new environment vanish from the picker on its first day.
When a path does not resolve#
| What you see | What it means |
|---|---|
nothing under <path> |
The path is real and you may list it, but it is empty. |
Nothing at this path (404) |
No such path — check for a typo. |
Access denied by policy (403) |
The path may or may not exist; your key cannot list it either way. |
The 403 is deliberately uninformative about existence. A refusal that distinguished "exists but denied" from "does not exist" would let a wordlist map a tree the key cannot read.
Related#
- Browsing secrets & folders — the same tree in the console
- Reading secrets — once you have found one
- Command reference — exact flags