bob secrets
bob secrets — Presence Check for Declared Secrets
Section titled “bob secrets — Presence Check for Declared Secrets”A client’s BoB can confirm that every secret it needs is in place without anyone reading, copying or transmitting the secret (#2286, part of #2215). (Back to reference index.)
The operator profile (operator.md) declares the
secrets an operator’s BoB depends on as names and sources — never a value.
bob secrets check reads that list, probes each entry for presence on this
machine, and names the declared source for anything missing. It turns the
“verify presence, name the source of truth” rule from the
tailscale-remote-ops
and secrets-management
skills into one shared, tested implementation. Storing, distributing and
rotating stay with the declared source (1Password, gh secret set,
scripts/fleet/distribute-secret.sh); this command only ever answers is it
there?
bob secrets check # every declared secret: state, source, fixbob secrets check --json # one bob-secrets/1 documentbob secrets check --operator <dir> # probe that operator repo instead of the resolved onenode scripts/security/secrets.js probe <kind> <ref> [--name <SECRET_NAME>] [--json]| Option | Effect |
|---|---|
--json |
Print one bob-secrets/1 document instead of the table. |
--operator <dir> |
Use this operator repo for the run — the same as BOB_OPERATOR=<dir> — instead of the resolver’s answer ($BOB_OPERATOR → $BOB_HOME/.bob-operator → ~/projects/bob-operator). |
probe <kind> <ref> |
One probe for a caller that already holds the locator. --name sets the secret name a gh-secret ref is matched against. Exit 0 present · 1 missing or insecure · 3 unknown · 2 usage. |
Exit codes
Section titled “Exit codes”| Exit | Meaning |
|---|---|
0 |
Every required secret is present. An unrequired gap is reported, never graded. |
1 |
A required secret is missing, insecure or unknown. |
2 |
The operator profile is invalid (missing, unparseable, or failing schemas/operator.schema.json), or usage. |
3 |
No operator resolved — bob operator init <dir> --link, or export BOB_OPERATOR=<dir>. |
The document is printed whole on every exit; with --json an error is an
error object (code, message, fix) on the same document, never a bare
message.
Declaring where a secret lives — present_at
Section titled “Declaring where a secret lives — present_at”Each secrets[] entry in operator.json may carry a presence locator:
{ "name": "BOB_CHECKOUT_TOKEN", "source": "gh-secret example-co/*:BOB_CHECKOUT_TOKEN", "required": true, "present_at": { "kind": "gh-secret", "ref": "example-co/example-app" }}source stays what it was — where the value is kept, in the credential
inventory’s reference notation — and present_at says where this machine
should find it. kind is a closed set and ref is the locator for that kind;
present_at refuses any other key, so the locator can never hold a value
either. required: true makes the entry grade the exit code; absent or
false, the entry is reported but never fails the run, so an operator can
adopt locators one at a time without going red. An entry with no present_at
is reported unknown with a “declare present_at” fix.
kind |
ref |
What it names |
|---|---|---|
env |
a variable name (BRIDGE_SECRET) |
an environment variable of the shell that runs the check |
file |
a path; ~ expands (~/.claude/secrets/reporter-links.md) |
a regular file on this machine |
keychain |
service or service/account (example-co-signing/ada) |
a macOS login-keychain generic password |
gh-secret |
<owner>/<repo> or org:<org>, optionally :<NAME> when the forge secret is not named like the entry (example-co/app, org:example-co:CI_TOKEN) |
an Actions secret on the forge |
op |
Vault/Item or op://Vault/Item[/field] (Example/remote-decision-bridge) |
a 1Password item; a field suffix is ignored, only the item is probed |
Both fixtures beside the schema prove it both ways in make check-schemas:
operator.sample.json declares
every kind and validates; operator.present-at.rejected.json
(a kind outside the set, carrying a value) must fail.
The four states
Section titled “The four states”| State | Meaning | Fix printed |
|---|---|---|
present |
the locator resolves to something that exists | — |
missing |
the locator resolves cleanly to nothing | how to place it from its source: export NAME=…, place the file then chmod 600, security add-generic-password, gh secret set (or distribute-secret.sh distribute for BOB_CHECKOUT_TOKEN), op item create |
insecure |
a file exists but is readable by group or others |
chmod 600 <path> |
unknown |
it could not be probed: no present_at, an unparseable ref, op or gh not on PATH or not signed in, a keychain entry on a non-macOS host, a locked keychain |
install / sign in / declare the locator; for keychain, run the check on the Mac that holds it |
unknown is never reported as present. A required secret that is
unknown is a gap — exit 1 — because the machine cannot show it is there.
How each kind is probed — existence and metadata only
Section titled “How each kind is probed — existence and metadata only”kind |
Probe | What comes back |
|---|---|---|
env |
is the variable set and non-empty | a boolean; the value is never copied |
file |
stat — type and mode |
exists, regular file, mode & 077 |
keychain |
security find-generic-password -s <service> [-a <account>] without -w; stdout discarded |
exit 0 present · 44 missing · other unknown |
gh-secret |
gh secret list -R <owner>/<repo> or --org <org>; the first column of every row |
whether the name is listed; gh exit 4 is “not authenticated” |
op |
op item list --format json [--vault <vault>]; item title and id only — never op item get, op read or --fields |
whether the item is listed |
Every subprocess runs with stderr discarded and its stdout consumed by the probe that asked (a listing of names); neither is copied into a detail line, a fix or an error message. The only strings a report carries are the profile’s own names and refs, the engine’s fixed vocabulary and exit codes. That is why this command runs at automation Level 3 without widening the autonomous profile’s deny list on credential reads.
The document — bob-secrets/1
Section titled “The document — bob-secrets/1”{ "schema": "bob-secrets/1", "generated_at": "2026-09-16T05:40:00.000Z", "host": "farm-01", "platform": "linux", "operator": { "resolved": true, "source": "pointer", "root": "/home/me/projects/bob-operator", "id": "example-co" }, "ok": false, "verdict": "missing", "counts": { "declared": 5, "present": 3, "missing": 1, "insecure": 0, "unknown": 1, "required": 2, "required_gaps": 1 }, "missing_required": ["BOB_CHECKOUT_TOKEN"], "secrets": [ { "name": "BOB_CHECKOUT_TOKEN", "source": "gh-secret example-co/*:BOB_CHECKOUT_TOKEN", "required": true, "kind": "gh-secret", "ref": "example-co/example-app", "state": "unknown", "detail": "cannot list secrets on repo example-co/example-app: gh is not authenticated", "fix": "gh auth login" } ], "error": null}verdict is ok · missing (a required gap) · invalid · unresolved.
counts are the document’s own; missing_required lists the names of
required secrets whose state is not present. An entry keeps inventory_id
when the profile declares one.
The secrets line in bob doctor
Section titled “The secrets line in bob doctor”bob doctor (host-drift.js) carries the
summary of this check on its bob-host-drift/1 document as secrets —
the verdict, the counts by state, how many are required and the names of
required secrets not present — and prints it as one line after the operator
line, never graded:
INFO secrets: 5 declared · 5 present · 0 missing · 0 insecure · 0 unknown — every required secret (2) present (#2286) WARN secrets: 5 declared · 3 present · 1 missing · 0 insecure · 1 unknown — 1 required not present: BOB_CHECKOUT_TOKEN — Fix: bob secrets check (#2286) INFO secrets: no operator declared — nothing to verify (#2286) WARN secrets: operator profile invalid — … — Fix: bob operator show (#2286)A gap is the operator’s todo, not BOB_HOME drift, so the machine verdict is
unchanged. The fleet snapshot’s summary.host_drift.secrets and bob status --scope host carry the compact form (verdict, counts, missing_required), so
a support health report holds presence and names and never a value — the
summary has no per-secret rows at all.
One probe, two callers
Section titled “One probe, two callers”scripts/fleet/distribute-secret.sh status asks the same gh-secret probe
(secrets.js probe gh-secret <slug> --name BOB_CHECKOUT_TOKEN) instead of its
own gh secret list | grep, so the fleet helper and bob secrets check ask
the forge the same question the same way. Its rows, summary line and exit
codes are unchanged; a gh that cannot list still reads as “secret MISSING”,
as the old grep did.
Relation to the credential inventory
Section titled “Relation to the credential inventory”secrets-inventory.json is the product-wide
declaration of every credential and every store a copy lives in;
cred-check.js compares the inventory to the stores by name. The operator
profile is the per-operator list of the secrets its BoB needs, and bob secrets check asks whether each one is reachable from this machine. The
two agree through inventory_id on a secrets[] entry; neither ever holds a
value.
tests/test-secrets.js proves the parser per
kind, every probe state with an injected exec and stub CLIs on a sealed PATH
(no real gh, op or security can be found), the document and its exit
codes, the CLI flags and the bin/bob route — and the canary: a value
planted behind every kind (the env variable, the file’s content, a security
stub, a gh stub on stdout and stderr, an op stub’s fields) must appear in
no stdout, stderr, --json document or file the command writes.
tests/test-host-drift.js proves the doctor
line and the compact snapshot form;
tests/test-distribute-secret.js
proves distribute-secret.sh status on the shared probe with its wording and
exit codes preserved.
Related
Section titled “Related”- Check Your Secrets: the task, step by step
- operator.md — the profile the secrets are declared in
- secrets-inventory.md — the credential inventory and
cred-check.js secrets-management·tailscale-remote-ops— the skills whose presence rule this command implements- cli.md — the generated command reference