Skip to content
Solo ProTeamBusinessEnterprise

Validating credentials

Check from your terminal whether a path is validated and whether its credential still works — then gate a deploy on the same answer.

Commands on this page: validation-status · validate · validation-history

A secret being present is not the same as it being usable. A rotated database password, a revoked token, an expired certificate — all still read back perfectly and all break the deploy that uses them. Validation closes that gap by having the server actually try the credential.

Two questions, and you can ask either from your terminal:

Question Command Touches the credential
What is validated, and was it working? validation-status <env | namespace | path> no
Does the credential work right now? validate <path> yes — a real sign-in
You validate a path, and only one somebody configured

There is no "validate this value". A probe is attached to a path in the console — either a folder of keys that together form one credential (…/db, whose probe uses host, user and password together) or a single key that is the credential, like a token. Until someone attaches one, there is nothing for the CLI to run.

The CLI can inspect and trigger probes, never create them. A path with none configured exits 4, which means unchecked — not fine. See Validation overview for attaching one.

What is validated?#

validation-status answers without touching anything, and its scope follows the path you give it — the same way list works.

A whole environment#

The question you ask first: which of my credentials are being checked at all?

bash
pulsvault validation-status acme-web/prod
text
acme-web/prod/product/web
  db          postgres     enabled   SUCCESS  4m12s ago
  gh/token    github_pat   enabled   FAILURE  2m12s ago  ALERT:FIRING/HIGH
  legacy/key  http_bearer  disabled  -

acme-web/prod/payments/api
  stripe  http_bearer  enabled  SUCCESS  12m0s ago

2 namespaces · 4 validated paths · 1 failing · 1 disabled

Grouped by namespace, with paths shown relative to their heading. --status narrows it to one health state — the same five the console's tiles use:

bash
pulsvault validation-status acme-web/prod --status failing      # what is broken right now
pulsvault validation-status acme-web/prod --status disabled     # switched off, so checking nothing
pulsvault validation-status acme-web/prod --status never_tested # configured but never run
`disabled` is worth looking at on purpose

A disabled probe is not a path without validation — it is a path that looks validated and checks nothing. It keeps its config, its history and its badge, and runs no probes.

--status disabled finds two different things, and today the CLI cannot tell them apart: a config a person switched off, and one the system switched off itself because its path ran out of readable keys (the console shows the second case as a distinct ⚠ no keys badge, and it re-enables itself the moment a key is written back — no action needed). The machine-plane status response carries no field yet that distinguishes them, so treat disabled here as "checking nothing right now, for either reason" rather than assuming every row was a deliberate switch-off.

Large environments are paged behind the scenes — the command fetches every page, so what you see is the whole answer. --limit N stops early, and says so rather than letting a partial list look complete:

text
showing 50 of 213 — raise --limit to see the rest

This listing always exits 0 when it can answer, even with paths failing. It reports; it does not gate. validate is the gate.

One path#

Give it one path: does this credential have a probe, and was it working?

bash
pulsvault validation-status acme-web/prod/product/web/db
text
acme-web/prod/product/web/db
  probe        postgres
  validation   enabled
  last result  SUCCESS (4m12s ago)

Naming a key inside a validated folder answers about the folder, and says so — in full, so you can paste it into the next command:

text
acme-web/prod/product/web/db/password
  probe        postgres
  validation   enabled
  last result  SUCCESS (4m12s ago)
  configured on acme-web/prod/product/web/db

One namespace#

Between the two: every validated path in a single namespace.

bash
pulsvault validation-status acme-web/prod/product/web
pulsvault validation-status acme-web/prod/product/web -o json
text
db          postgres     enabled   SUCCESS   2026-07-21T09:14:02
gh/token    github_pat   enabled   FAILURE   2026-07-21T09:02:55   ALERT:FIRING/HIGH
legacy/key  http_bearer  disabled  -

-o json works at every scope and always returns flat rows, each carrying both path and canonicalPath — grouping is a display choice, not something a script should have to undo.

When nothing is configured#

A path with no probe says so, and names the nearest one that does have a probe — usually the folder above it, because a multi-key credential is validated as a whole:

text
No validation configured for acme-web/prod/product/web/db/postgres/host.
The nearest configured path is acme-web/prod/product/web/db.
Validation is opt-in: attach a probe to the path in the console to start checking it.
Run this first when something looks unvalidated

A path that has never been checked and a path whose check is failing look nothing alike here — the first shows - as its last result, the second shows FAILURE. Reaching for validate to find out means signing in to the provider to learn something already recorded.

Checking a credential now#

validate runs the configured probe immediately and reports what happened:

bash
pulsvault validate acme-web/prod/product/web/db/uri
text
SUCCESS acme-web/prod/product/web/db/uri (42 ms)

A failure names the category, so the output says what to fix:

text
FAILURE acme-web/prod/product/web/db/uri (38 ms) — AUTH_FAILED: authentication failed

This is a real sign-in with your credential, against your provider — worth reaching for when now is genuinely the question, and worth leaving alone when it is not.

After rotating a credential#

Writing a secret already re-checks its validated path — PulsVault runs the probe as soon as the new version commits, so a rotation is confirmed without you asking. validation-status shows the result, and costs nothing:

bash
pulsvault set acme-web/prod/product/web/db/password --from-file ./new-pw.txt
pulsvault validation-status acme-web/prod/product/web/db   # what the automatic check found

Running validate yourself is for when a script must block on the answer. A result from the last 30 seconds is reported rather than re-run — the line says cached — so a matrix build fanning twelve jobs at one path does not become twelve sign-ins against your provider. A write always forces a real probe, so set followed by validate is never answered from before the rotation, and --force insists on one in any case:

bash
pulsvault validate acme-web/prod/product/web/db          # blocks; fresh after the write above
pulsvault validate acme-web/prod/product/web/db --force  # never reported from an earlier run

Validating a key inside a validated folder#

Validation attaches to a path — usually a folder of keys that together make one credential. Naming a key inside it validates the whole connection, and the line says which path was actually checked:

text
SUCCESS acme-web/prod/product/web/db/password (41 ms, validated acme-web/prod/product/web/db)

A key nested deeper than one level does not resolve, and the error names the nearest configured path rather than leaving you to guess — in full, so you can paste it straight into the next command:

text
NONE    acme-web/prod/product/web/db/postgres/host
No validation configured for acme-web/prod/product/web/db/postgres/host.
The nearest configured path is acme-web/prod/product/web/db.
Validation is opt-in: attach a probe to the path in the console to start checking it.

What the exit code means#

Both validate and validation-status exit with a code that names the outcome, so you never have to parse output to know what happened:

Code Meaning What to do
0 the credential works continue
1 it genuinely failed stop — a credential is revoked, expired or wrong
2 usage error fix the command
3 the check could not be completed — a timeout, a probe error, or rate-limited retry; nothing here says the credential is bad
4 nothing is configured for that path it was never checked
5 could not reach or authenticate to PulsVault fix the key, the host or the network

&& chains are unaffected — every non-zero still short-circuits. The codes matter when something inspects $?, and the one worth wiring up is 3. A validator timeout is not a broken credential, and failing a deploy over a network blip is what teaches a team to add || true — after which the gate protects nothing at all.

Exit 4 is the one to notice

A path with nothing configured is not a passing check; it is no check. If a script treats "4" as success, it is trusting a probe nobody ever set up.

Gating a deploy#

Everything above works unchanged in a pipeline — the exit code is the whole interface, so no output parsing is needed:

bash
pulsvault validate acme-web/prod/product/web/db/uri && ./deploy.sh

If you use GitHub Actions or GitLab CI, the ready-made integrations wrap this and let you decide per outcome what should stop the pipeline.

The gate that costs nothing#

Most pipelines do not need a fresh probe. They need "was this credential known-good recently", and that is already recorded — so --max-age answers it with no traffic to your provider at all:

bash
pulsvault validation-status acme-web/prod/product/web/db --max-age 1h && ./deploy.sh

It passes only if the last recorded result was SUCCESS inside the window, using the same exit codes as validate. Reach for this by default, and for validate when now is genuinely the question — after rotating a credential, or before promoting a build.

Why this is the better default

Your plan is already re-checking these credentials on a schedule. Asking "was it green within the hour" reads that work; running a probe repeats it. On a busy repository the difference is thousands of extra sign-ins a week against a provider that is rate-limiting you.

A whole namespace#

--all triggers every enabled probe in a namespace and reports the worst outcome — one call, one gate:

bash
pulsvault validate acme-web/prod/product/web --all && ./deploy.sh
text
SUCCESS acme-web/prod/product/web/db/password (postgres) (12 ms)
FAILURE acme-web/prod/product/web/gh/token (github_pat) — AUTH_FAILED
1 of 2 validation(s) did not pass

The exit code is the worst outcome in the namespace, ranked 1 > 5 > 3 > 4 > 0 — so one revoked credential is never masked by another path merely timing out.

A namespace with no probes configured is a warning, not a failure

--all exits 0 with a warning when nothing in the namespace has validation configured. A gate that failed closed here would break every pipeline the moment it was added, before anyone had configured a single probe.

Add --require-validation to turn that into exit 4, for teams enforcing "production paths must be validated" from CI.

Investigating a failure#

validation-history shows what has been happening to one secret over time:

bash
pulsvault validation-history acme-web/prod/product/web/db/uri --limit 20
text
2026-07-21T09:14:02  SUCCESS  postgres  SCHEDULED       42ms
2026-07-20T09:14:01  FAILURE  postgres  SCHEDULED       31ms  AUTH_FAILED
2026-07-19T16:02:44  SUCCESS  postgres  ON_DEMAND_CLI   38ms

The distinction that usually matters: a credential failing on a schedule has been broken since the last successful run, while one failing only when you triggered it points at something about the caller — network egress, an IP allowlist, a proxy. ON_DEMAND_CLI is a run from pulsvault validate, kept distinct from the console's "Run now" so a check that happened mid-deploy is recognisable as one.

Permissions#

Anything addressing one path needs only read on that path — validate, validation-history, and validation-status on a single path. Triggering a probe is a use of the credential, so the grant that lets you read it is the grant that lets you check it.

Anything that enumerates needs list, which read does not grant: validation-status on an environment or a namespace, and validate --all. So a key scoped to exactly the paths it deploys can check each of them by name but cannot list them.

An environment listing shows only the namespaces your key can reach, rather than refusing the whole call because one of them is out of scope — a narrowly-scoped key still gets its own paths.

If a command refuses, pulsvault whoami shows what the key actually holds — see Access policies.

Was this page helpful?