feat: add auth-capability lanes and pilot closeout
Add the warden-sign auth-capability lane, AppRole handoff, verification guards, docs, and tests. Point the whynot-design pilot at the canonical decision and add the real publish closeout preflight/runbook.
This commit is contained in:
parent
a621fbaffd
commit
6382139890
27 changed files with 1455 additions and 107 deletions
36
docs/cli.md
36
docs/cli.md
|
|
@ -42,7 +42,8 @@ secrets-engine decision inspect <decision-or-ccr-id>
|
|||
secrets-engine plan <ref> --stage <build|test|prod>
|
||||
secrets-engine apply <ref> --stage <stage> [--dry-run] [--bootstrap-token-file F]
|
||||
secrets-engine provision <catalog-id> --stage <stage> --field NAME (--from-file F | --generate)
|
||||
secrets-engine verify <catalog-id> --field NAME [--positive] [--negative]
|
||||
secrets-engine verify <catalog-id> [--field NAME] [--positive] [--negative]
|
||||
secrets-engine handoff <catalog-id> --stage <stage> --role-id-file F --secret-id-file F
|
||||
secrets-engine exec --catalog <catalog-id> [--field NAME] [--mode auto|npm-config|exec-env] -- CMD...
|
||||
secrets-engine policy publication <catalog-id>
|
||||
secrets-engine route <catalog-id> [--json]
|
||||
|
|
@ -55,6 +56,13 @@ var the token is injected under, per the netkingdom publication-scope policy
|
|||
|
||||
`<ref>` is a catalog id or a decision/CCR ref (matched against
|
||||
`approval.decision_ref`). `plan` and `apply --dry-run` never mutate OpenBao.
|
||||
For decision-gated lanes they may render with `decision: <none>` when the
|
||||
approval object is not reachable; non-dry-run `apply` remains decision-gated.
|
||||
|
||||
`handoff` is for `kind: auth-capability` lanes such as `warden-sign`. It mints a
|
||||
fresh AppRole `secret_id` and writes `role_id` plus `secret_id` to caller-chosen
|
||||
mode-0600 files outside Git worktrees. It never prints the `secret_id`; use the
|
||||
resulting files only for attended out-of-band delivery.
|
||||
|
||||
## Exit codes
|
||||
|
||||
|
|
@ -92,6 +100,32 @@ scratch package. Proves npm in the child resolves its auth token from the temp
|
|||
step — while the parent shell never holds the token. For a real publish,
|
||||
provision a real npm automation token the same way and drop `--dry-run`.
|
||||
|
||||
## Auth-capability: warden-sign
|
||||
|
||||
```bash
|
||||
# Preview the non-KV policy/AppRole lane; no mutation.
|
||||
SECRETS_ENGINE_HUB_URL="" secrets-engine apply warden-sign --stage prod --dry-run
|
||||
|
||||
# Live apply and handoff require an approved decision and an out-of-repo
|
||||
# bootstrap token file. Values are written to files, not stdout.
|
||||
BAO_ADDR=https://bao.coulomb.social \
|
||||
secrets-engine handoff warden-sign --stage prod \
|
||||
--bootstrap-token-file ~/.secrets-engine/bootstrap/prod-warden-sign.token \
|
||||
--role-id-file ~/.secrets-engine/handoff/warden-sign.role_id \
|
||||
--secret-id-file ~/.secrets-engine/handoff/warden-sign.secret_id
|
||||
```
|
||||
|
||||
See [warden-sign-auth-capability.md](warden-sign-auth-capability.md) for the full
|
||||
runbook and State Hub pointer payload.
|
||||
|
||||
## Pilot closeout: whynot-design real publish
|
||||
|
||||
```bash
|
||||
scripts/whynot-real-publish-preflight.sh
|
||||
```
|
||||
|
||||
The real publish path is documented in [whynot-design-real-publish-closeout.md](whynot-design-real-publish-closeout.md).
|
||||
|
||||
## Pilot: whynot-design npm publish
|
||||
|
||||
```bash
|
||||
|
|
|
|||
|
|
@ -14,8 +14,19 @@ Every minted bootstrap token has a revocation task. Track each here:
|
|||
| Token file | Stage | Minted | TTL | Revoked? |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| `~/.secrets-engine/bootstrap/prod.token` | prod | (n/a — demo uses dev server) | 1h | n/a |
|
||||
| `~/.secrets-engine/bootstrap/prod-warden-sign.token` | prod | pending SECRETS-WP-0004 live apply | 1h | pending |
|
||||
| `~/.secrets-engine/bootstrap/prod-whynot-design.token` | prod | pending SECRETS-WP-0003 live apply/provision | 1h | pending |
|
||||
|
||||
Revoke: `bao token revoke -accessor <accessor>` then `shred -u <file>`.
|
||||
For SECRETS-WP-0004, revoke the scoped `warden-sign` token/accessor after the
|
||||
FLEX-WP-0007 T4 smoke or when its `15m` TTL expires, and remove the handoff
|
||||
files under `~/.secrets-engine/handoff/`.
|
||||
|
||||
## H0a — Revoke pilot workload tokens
|
||||
|
||||
| Credential | Custody path | Minted | Revocation task | Revoked? |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| whynot-design Gitea bot npm/package token | OpenBao lane `whynot-design-npm-publish`, source handoff file `~/.secrets-engine/handoff/whynot-design-npm.token` | pending SECRETS-WP-0003 bot/token gate | Revoke or rotate the bot token in Gitea, delete the source handoff file with `shred -u`, and record non-secret package/version evidence after publish | pending |
|
||||
|
||||
## H1 — Replace bootstrap token files with OIDC / service auth
|
||||
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ explicit capability set.
|
|||
| --- | --- | --- | --- | --- |
|
||||
| `secrets-engine-build` | `policies/secrets-engine-build.hcl` | `secret/.../build/` | manage build metadata + generated test values; own `se-build-*` policies/roles | touch test/prod, `sys/*`, `auth/token/*`, `identity/*`, act as root |
|
||||
| `secrets-engine-test` | `policies/secrets-engine-test.hcl` | `secret/.../test/` | manage test metadata + values; run positive/negative checks; own `se-test-*` | touch build/prod, `sys/*`, `auth/token/*`, `identity/*`, act as root |
|
||||
| `secrets-engine-prod` | `policies/secrets-engine-prod.hcl` | owner-scoped prod lanes | apply approved prod ACL policies + approle roles; write approved values; own `se-prod-*` | reach build/test, edit the stage roles themselves, admin `sys/auth`, `sys/mounts`, `identity/*`, `auth/token/*`, act as root |
|
||||
| `secrets-engine-prod` | `policies/secrets-engine-prod.hcl` | owner-scoped prod lanes | apply approved prod ACL policies + approle roles; write approved values; own `se-prod-*`; exact SECRETS-WP-0004 `warden-sign` policy/AppRole exception | reach build/test, edit the stage roles themselves, admin `sys/auth`, `sys/mounts`, `identity/*`, `auth/token/*`, act as root |
|
||||
|
||||
The **product requirement** is the stage *distinction* and the *denials*, not the
|
||||
exact policy names — those may evolve as info-tech-canon hardens.
|
||||
|
|
@ -20,8 +20,11 @@ exact policy names — those may evolve as info-tech-canon hardens.
|
|||
2. **In secrets-engine** — `roles.assert_path_in_stage()` and
|
||||
`roles.assert_policy_safe()` reject any *plan* that would touch another
|
||||
stage's prefix, use a wildcard, name itself like an admin policy, or carry a
|
||||
capability outside `create/read/update/delete/list`. A bad plan fails closed
|
||||
**before** any OpenBao call.
|
||||
capability outside `create/read/update/delete/list`. Auth-capability lanes use
|
||||
`roles.assert_auth_capability_safe()`, which refuses broad `ssh/*`, wildcard,
|
||||
`sys/`, `auth/token/`, `identity/`, root-like, or non-`update` grants and, for
|
||||
the SSH mount, renders only exact `ssh/sign/<role>` paths. A bad plan fails
|
||||
closed **before** any OpenBao call.
|
||||
|
||||
Run the negative checks:
|
||||
|
||||
|
|
|
|||
|
|
@ -1,9 +1,10 @@
|
|||
# ops-warden → secrets-engine Routing Contract
|
||||
|
||||
ops-warden issues **SSH certificates only**. Every other credential need (API
|
||||
keys, provider tokens, DB passwords, npm publish tokens) routes to
|
||||
**secrets-engine**, which is OpenBao-backed. ops-warden must never request, hold,
|
||||
cache, or vend a raw secret value. A route result is a **pointer**, not a key.
|
||||
keys, provider tokens, DB passwords, npm publish tokens, and OpenBao API token
|
||||
lanes) routes to **secrets-engine**, which is OpenBao-backed. ops-warden must
|
||||
never request, hold, cache, or vend a raw secret value. A route result is a
|
||||
**pointer**, not a key.
|
||||
|
||||
## What ops-warden calls
|
||||
|
||||
|
|
@ -16,10 +17,11 @@ secrets-engine route <catalog-id> --json
|
|||
```json
|
||||
{
|
||||
"catalog_id": "whynot-design-npm-publish",
|
||||
"kind": "kv",
|
||||
"owner": "coulomb/whynot-design",
|
||||
"stage": "prod",
|
||||
"decision_status": "resolved",
|
||||
"decision_ref": "whynot-design-npm-publish",
|
||||
"decision_ref": "e6381a56-6b04-4fd5-b2de-f3ef59cde888",
|
||||
"review_url": "http://127.0.0.1:8000/decisions/<id>",
|
||||
"metadata_applied": true,
|
||||
"value_present": true,
|
||||
|
|
@ -32,16 +34,18 @@ secrets-engine route <catalog-id> --json
|
|||
| Field | Meaning |
|
||||
| --- | --- |
|
||||
| `decision_status` | `resolved`/`approved` => approved; `missing`/`pending` => not yet |
|
||||
| `kind` | `kv` for stored-value lanes, `auth-capability` for policy/AppRole capability lanes |
|
||||
| `metadata_applied` | OpenBao ACL policy + approle exist for the lane |
|
||||
| `value_present` | the secret value has been provisioned (boolean only — value never read) |
|
||||
| `ready` | approved **and** applied **and** provisioned |
|
||||
| `value_present` | for `kv`, the secret value has been provisioned (boolean only — value never read); for `auth-capability`, there is no KV value and this is true once metadata is handoff-ready |
|
||||
| `ready` | approved **and** applied **and** provisioned or handoff-ready |
|
||||
| `next_command` | the single safe command the caller should run next |
|
||||
| `missing` | the one human/provisioning step still outstanding |
|
||||
|
||||
## Guarantees
|
||||
|
||||
- **No value crosses this boundary.** `route` reports a boolean `value_present`,
|
||||
derived from a metadata/presence check — it never reads the secret.
|
||||
derived from a metadata/presence check for KV lanes or from policy/AppRole
|
||||
readiness for auth-capability lanes — it never reads or mints a secret.
|
||||
- **Actionable when not ready.** If a lane is unapproved, unapplied, or
|
||||
unprovisioned, `next_command` + `missing` tell the caller exactly what to do.
|
||||
- **Idempotent / read-only.** `route` performs no mutation.
|
||||
|
|
@ -55,8 +59,36 @@ secrets-engine route <catalog-id> --json
|
|||
4. Once `ready=true`, the workload runs
|
||||
`secrets-engine exec --catalog whynot-design-npm-publish -- npm publish`.
|
||||
|
||||
Safe whynot-design pointer payload for ops-warden:
|
||||
|
||||
```json
|
||||
{
|
||||
"catalog_id": "whynot-design-npm-publish",
|
||||
"kind": "kv",
|
||||
"decision_ref": "e6381a56-6b04-4fd5-b2de-f3ef59cde888",
|
||||
"owner": "coulomb/whynot-design",
|
||||
"stage": "prod",
|
||||
"next_command": "secrets-engine exec --catalog whynot-design-npm-publish -- npm publish"
|
||||
}
|
||||
```
|
||||
|
||||
## warden-sign auth-capability flow
|
||||
|
||||
1. ops-warden needs a scoped `VAULT_TOKEN` for the FLEX-WP-0007 T4 smoke.
|
||||
2. ops-warden runs `secrets-engine route warden-sign --json`.
|
||||
3. If `ready=false`, it surfaces `missing` + `next_command` to the human.
|
||||
4. Once `ready=true`, `next_command` points to `secrets-engine handoff ...`; the
|
||||
operator receives `role_id` and `secret_id` out-of-band and performs AppRole
|
||||
login on CoulombCore.
|
||||
5. State Hub receives only non-secret pointers: addr, mount, policy, AppRole,
|
||||
TTLs, allowed paths, and status. It must not receive `role_id`, `secret_id`,
|
||||
`VAULT_TOKEN`, token accessor, or raw smoke output containing token material.
|
||||
|
||||
See [warden-sign-auth-capability.md](warden-sign-auth-capability.md).
|
||||
|
||||
## Anti-patterns (forbidden)
|
||||
|
||||
- ops-warden `POST /messages/` asking for `NPM_TOKEN` / `OPENROUTER_API_KEY`.
|
||||
- ops-warden `POST /messages/` asking for `NPM_TOKEN` / `OPENROUTER_API_KEY` /
|
||||
`VAULT_TOKEN`.
|
||||
- Caching `value_present` as if it were the value.
|
||||
- Inventing `warden secret` / `warden bao` — they do not exist.
|
||||
|
|
|
|||
142
docs/warden-sign-auth-capability.md
Normal file
142
docs/warden-sign-auth-capability.md
Normal file
|
|
@ -0,0 +1,142 @@
|
|||
# warden-sign auth-capability lane
|
||||
|
||||
`warden-sign` is a non-KV secrets-engine lane for SECRETS-WP-0004. It creates an
|
||||
OpenBao ACL policy plus AppRole that lets ops-warden run `warden sign` over the
|
||||
HTTP API for the FLEX-WP-0007 T4 production smoke.
|
||||
|
||||
No token value, AppRole `secret_id`, or SSH private material belongs in Git,
|
||||
State Hub, chat, prompts, workplans, or normal logs. State Hub gets pointers
|
||||
only; the operator receives `role_id` and `secret_id` out-of-band.
|
||||
|
||||
## Non-secret pointers
|
||||
|
||||
| Pointer | Value |
|
||||
| --- | --- |
|
||||
| OpenBao address | `https://bao.coulomb.social` |
|
||||
| SSH mount | `ssh` |
|
||||
| Policy | `warden-sign` |
|
||||
| AppRole | `warden-sign` |
|
||||
| Token TTL | `15m` |
|
||||
| Secret ID TTL | `30m` |
|
||||
| Secret ID uses | `1` |
|
||||
| Allowed paths | `ssh/sign/agt-role`, `ssh/sign/adm-role`, `ssh/sign/atm-role` |
|
||||
| Denied probes | `ssh/sign/unlisted-role`, `ssh/roles/agt-role`, `ssh/config/ca`, `sys/policies/acl/warden-sign`, `auth/token/create`, `identity/entity/id` |
|
||||
|
||||
## Plan and apply
|
||||
|
||||
Preview without mutation:
|
||||
|
||||
```bash
|
||||
SECRETS_ENGINE_HUB_URL="" secrets-engine apply warden-sign --stage prod --dry-run
|
||||
```
|
||||
|
||||
Live apply requires an approved decision/workplan and a short-lived bootstrap
|
||||
token file outside any repo:
|
||||
|
||||
```bash
|
||||
BAO_ADDR=https://bao.coulomb.social \
|
||||
secrets-engine apply warden-sign --stage prod \
|
||||
--bootstrap-token-file ~/.secrets-engine/bootstrap/prod-warden-sign.token
|
||||
```
|
||||
|
||||
The bootstrap token file must be mode `0600`, revocable, and tracked in
|
||||
[hardening-backlog.md](hardening-backlog.md) H0 until revoked and shredded.
|
||||
|
||||
## Handoff
|
||||
|
||||
Mint a fresh single-use AppRole `secret_id` and write both handoff values to
|
||||
mode-0600 files outside any Git worktree:
|
||||
|
||||
```bash
|
||||
install -m 700 -d ~/.secrets-engine/handoff
|
||||
BAO_ADDR=https://bao.coulomb.social \
|
||||
secrets-engine handoff warden-sign --stage prod \
|
||||
--bootstrap-token-file ~/.secrets-engine/bootstrap/prod-warden-sign.token \
|
||||
--role-id-file ~/.secrets-engine/handoff/warden-sign.role_id \
|
||||
--secret-id-file ~/.secrets-engine/handoff/warden-sign.secret_id
|
||||
```
|
||||
|
||||
The command prints only file paths and TTL metadata. It does not print the
|
||||
`secret_id`.
|
||||
|
||||
On CoulombCore, the operator can authenticate without the `bao` CLI by posting
|
||||
the file contents to AppRole login and capturing the returned token into process
|
||||
state only:
|
||||
|
||||
```bash
|
||||
ROLE_ID="$(cat ~/.secrets-engine/handoff/warden-sign.role_id)"
|
||||
SECRET_ID="$(cat ~/.secrets-engine/handoff/warden-sign.secret_id)"
|
||||
VAULT_TOKEN="$(
|
||||
printf '{"role_id":"%s","secret_id":"%s"}' "${ROLE_ID}" "${SECRET_ID}" \
|
||||
| curl -fsS \
|
||||
--request POST \
|
||||
--header 'Content-Type: application/json' \
|
||||
--data @- \
|
||||
https://bao.coulomb.social/v1/auth/approle/login \
|
||||
| jq -r '.auth.client_token'
|
||||
)"
|
||||
unset ROLE_ID SECRET_ID
|
||||
```
|
||||
|
||||
Run the smoke with the scoped token:
|
||||
|
||||
```bash
|
||||
FLEX_AUTH_EXTERNAL=1 SMOKE_VAULT=1 VAULT_TOKEN="${VAULT_TOKEN}" \
|
||||
~/ops-warden/scripts/policy_gate_production_smoke.sh
|
||||
```
|
||||
|
||||
## Verification
|
||||
|
||||
After live apply and handoff, verify the scoped AppRole token shape without
|
||||
printing the token:
|
||||
|
||||
```bash
|
||||
BAO_ADDR=https://bao.coulomb.social \
|
||||
secrets-engine verify warden-sign --positive --negative \
|
||||
--bootstrap-token-file ~/.secrets-engine/bootstrap/prod-warden-sign.token
|
||||
```
|
||||
|
||||
Positive verification checks `update` on the three allowlisted `ssh/sign` paths.
|
||||
Negative verification checks the denial probes lack `update`, `sudo`, and `root`.
|
||||
|
||||
## State Hub pointer payload
|
||||
|
||||
Post only non-secret pointers, for example:
|
||||
|
||||
```json
|
||||
{
|
||||
"catalog_id": "warden-sign",
|
||||
"kind": "auth-capability",
|
||||
"addr": "https://bao.coulomb.social",
|
||||
"mount": "ssh",
|
||||
"policy": "warden-sign",
|
||||
"approle": "warden-sign",
|
||||
"token_ttl": "15m",
|
||||
"secret_id_ttl": "30m",
|
||||
"secret_id_num_uses": 1,
|
||||
"allowed_paths": ["ssh/sign/agt-role", "ssh/sign/adm-role", "ssh/sign/atm-role"],
|
||||
"status": "applied-and-handoff-ready"
|
||||
}
|
||||
```
|
||||
|
||||
Do not include `role_id`, `secret_id`, `VAULT_TOKEN`, token accessors, raw curl
|
||||
responses, or smoke logs containing token material.
|
||||
|
||||
## Revocation and cleanup
|
||||
|
||||
After the smoke, revoke the scoped token by accessor if available, then remove
|
||||
handoff files:
|
||||
|
||||
```bash
|
||||
bao token revoke -accessor <accessor>
|
||||
shred -u ~/.secrets-engine/handoff/warden-sign.role_id
|
||||
shred -u ~/.secrets-engine/handoff/warden-sign.secret_id
|
||||
```
|
||||
|
||||
To retire the lane metadata itself:
|
||||
|
||||
```bash
|
||||
BAO_ADDR=https://bao.coulomb.social \
|
||||
secrets-engine revoke warden-sign \
|
||||
--bootstrap-token-file ~/.secrets-engine/bootstrap/prod-warden-sign.token
|
||||
```
|
||||
114
docs/whynot-design-real-publish-closeout.md
Normal file
114
docs/whynot-design-real-publish-closeout.md
Normal file
|
|
@ -0,0 +1,114 @@
|
|||
# whynot-design real publish closeout
|
||||
|
||||
This runbook is the SECRETS-WP-0003 closeout path for turning the
|
||||
`whynot-design-npm-publish` pilot from a dry-run/demo lane into a real production
|
||||
`@whynot/design` publish through `secrets-engine exec`.
|
||||
|
||||
No raw npm token, OpenBao token, token accessor, `NPM_AUTH_TOKEN`, package-token
|
||||
value, or private credential material may be written to Git, State Hub, chat,
|
||||
prompts, workplans, or normal logs.
|
||||
|
||||
## Current non-secret pointers
|
||||
|
||||
| Pointer | Value |
|
||||
| --- | --- |
|
||||
| Catalog id | `whynot-design-npm-publish` |
|
||||
| Canonical decision | `e6381a56-6b04-4fd5-b2de-f3ef59cde888` |
|
||||
| Org/repo | `coulomb/whynot-design` |
|
||||
| npm package | `@whynot/design` |
|
||||
| npm registry | `https://gitea.coulomb.social/api/packages/coulomb/npm/` |
|
||||
| Catalog mount/path | `secret/coulomb/whynot-design/npm/publish` |
|
||||
| Catalog field | `npm_token` |
|
||||
| Injected env var | `NPM_AUTH_TOKEN` |
|
||||
|
||||
The decision UUID above is resolved from State Hub in normal operation. A
|
||||
UUID-named local fixture exists only as an offline fallback for throwaway demos.
|
||||
|
||||
## Source-side preflight
|
||||
|
||||
From this repo, with State Hub reachable:
|
||||
|
||||
```bash
|
||||
scripts/whynot-real-publish-preflight.sh
|
||||
```
|
||||
|
||||
Expected source-side result:
|
||||
|
||||
- `decision inspect e6381a56-6b04-4fd5-b2de-f3ef59cde888` shows `source: hub` and `APPROVED`;
|
||||
- `policy publication whynot-design-npm-publish` resolves to the repo-clamped
|
||||
`NPM_AUTH_TOKEN` path while netkingdom remains `maturity-build`;
|
||||
- `route whynot-design-npm-publish --json` returns a pointer only. If OpenBao is
|
||||
not reachable or the value is not provisioned, `ready=false` is acceptable and
|
||||
must be handled by the operator steps below.
|
||||
|
||||
## Operator live gates
|
||||
|
||||
1. Create or confirm a dedicated Gitea bot account such as `se-whynot-design`.
|
||||
Record only non-secret evidence: username, owning org/repo, package scope,
|
||||
creation timestamp, and who confirmed it.
|
||||
2. Confirm the bot can publish `@whynot/design` to the coulomb registry and cannot
|
||||
publish outside the intended org/scope. Record only pass/fail and package names.
|
||||
3. Mint the Gitea npm/package token for that bot and place it in a mode-0600 file
|
||||
outside any Git worktree. Do not print the value.
|
||||
4. Apply/provision through secrets-engine using approved OpenBao authority:
|
||||
|
||||
```bash
|
||||
BAO_ADDR=https://bao.coulomb.social \
|
||||
secrets-engine apply whynot-design-npm-publish --stage prod \
|
||||
--bootstrap-token-file ~/.secrets-engine/bootstrap/prod-whynot-design.token
|
||||
|
||||
BAO_ADDR=https://bao.coulomb.social \
|
||||
secrets-engine provision whynot-design-npm-publish --stage prod \
|
||||
--field npm_token \
|
||||
--from-file ~/.secrets-engine/handoff/whynot-design-npm.token \
|
||||
--bootstrap-token-file ~/.secrets-engine/bootstrap/prod-whynot-design.token
|
||||
```
|
||||
|
||||
5. Verify without disclosing the value:
|
||||
|
||||
```bash
|
||||
BAO_ADDR=https://bao.coulomb.social \
|
||||
secrets-engine verify whynot-design-npm-publish \
|
||||
--field npm_token --positive --negative \
|
||||
--bootstrap-token-file ~/.secrets-engine/bootstrap/prod-whynot-design.token
|
||||
```
|
||||
|
||||
6. In the `whynot-design` repo, coordinate a real version bump. Run the publish
|
||||
through child-only injection:
|
||||
|
||||
```bash
|
||||
BAO_ADDR=https://bao.coulomb.social \
|
||||
secrets-engine exec --catalog whynot-design-npm-publish -- npm publish
|
||||
```
|
||||
|
||||
7. Confirm the published version appears in the coulomb Gitea npm registry.
|
||||
Record package name, version, registry URL, timestamp, and result only.
|
||||
|
||||
## ops-warden handoff
|
||||
|
||||
ops-warden should route npm publish credential requests here and return a pointer,
|
||||
not a value:
|
||||
|
||||
```bash
|
||||
secrets-engine route whynot-design-npm-publish --json
|
||||
```
|
||||
|
||||
Safe pointer payload:
|
||||
|
||||
```json
|
||||
{
|
||||
"catalog_id": "whynot-design-npm-publish",
|
||||
"kind": "kv",
|
||||
"decision_ref": "e6381a56-6b04-4fd5-b2de-f3ef59cde888",
|
||||
"owner": "coulomb/whynot-design",
|
||||
"stage": "prod",
|
||||
"next_command": "secrets-engine exec --catalog whynot-design-npm-publish -- npm publish"
|
||||
}
|
||||
```
|
||||
|
||||
## Revocation and cleanup
|
||||
|
||||
Track the OpenBao bootstrap token and the Gitea bot package token in
|
||||
[hardening-backlog.md](hardening-backlog.md). After publish verification, remove
|
||||
any handoff/source token files with `shred -u` and revoke or rotate the Gitea bot
|
||||
token according to the recorded lifecycle task.
|
||||
Loading…
Add table
Add a link
Reference in a new issue