Skip to content
Solo ProTeamBusinessEnterprise

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:

bash
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 holds

Walking down that ladder looks like this:

text
$ 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    30B

The 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 path in the console is the path in the CLI

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:

bash
$ 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    47B
A one-level listing and `-R` answer different questions

The 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:

text
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.

A very large listing is cut, and `-R` says so

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:

bash
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 project

A 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.

`--all` still works

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:

bash
$ 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:

bash
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)
Discovery is filtered by what your key holds

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.

Was this page helpful?