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 |
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?
pulsvault validation-status acme-web/prodacme-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 disabledGrouped by namespace, with paths shown relative to their heading. --status narrows it to one health
state — the same five the console's tiles use:
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 runA 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:
showing 50 of 213 — raise --limit to see the restThis 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?
pulsvault validation-status acme-web/prod/product/web/dbacme-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:
acme-web/prod/product/web/db/password
probe postgres
validation enabled
last result SUCCESS (4m12s ago)
configured on acme-web/prod/product/web/dbOne namespace#
Between the two: every validated path in a single namespace.
pulsvault validation-status acme-web/prod/product/web
pulsvault validation-status acme-web/prod/product/web -o jsondb 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:
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.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:
pulsvault validate acme-web/prod/product/web/db/uriSUCCESS acme-web/prod/product/web/db/uri (42 ms)A failure names the category, so the output says what to fix:
FAILURE acme-web/prod/product/web/db/uri (38 ms) — AUTH_FAILED: authentication failedThis 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:
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 foundRunning 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:
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 runValidating 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:
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:
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.
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:
pulsvault validate acme-web/prod/product/web/db/uri && ./deploy.shIf 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:
pulsvault validation-status acme-web/prod/product/web/db --max-age 1h && ./deploy.shIt 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.
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:
pulsvault validate acme-web/prod/product/web --all && ./deploy.shSUCCESS 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 passThe 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.
--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:
pulsvault validation-history acme-web/prod/product/web/db/uri --limit 202026-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 38msThe 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.
Related#
- CI/CD pipelines — the GitHub Action and GitLab template that wrap these codes
- Validation overview — what probes are and how to configure them
- Probes — the supported credential types
- Alerts & routing — being told without polling
- Command reference — exact flags