explore(security): YubiKey as last operator approval gate before bulk-destroy / irreversible actions #567

Open
opened 2026-05-28 13:06:00 +02:00 by ollama · 4 comments
Collaborator

Context

Branch claude/spec/yubikey-platform-integration-v0 (2026-05-17, never merged) contains a 476-line Spec Kit for YubiKey integration across 6 roles. Rather than shipping the full Spec Kit as a PR, we defer the design and scope this issue to the most immediately valuable use case.

Scope (narrowed)

YubiKey as the LAST physical approval gate before irreversible/destructive/bulk actions, such as:

  • platformctl apply with --break-glass on destructive changes
  • Bulk branch deletion
  • Volume/data deletion
  • Production container restart
  • Merge of security-sensitive PRs (class/security-sensitive)

NOT in scope (for now)

  • YubiKey as general 2FA for Synology/Forgejo/Infisical (6-role rollout)
  • SSH PIV slot
  • age-plugin-yubikey co-storage
  • Full presence-gate architecture from the Spec Kit

The Spec Kit (reference)

Original branch: claude/spec/yubikey-platform-integration-v0 (author: codex, 2026-05-17)
Contains 8 non-negotiable principles (P1-P8) and v0/v0.1 slice plans.
Can be used as reference when the time comes.

Acceptance

  • Decision: is YubiKey the right hardware for this use case?
  • Design: minimal approval flow for destructive platformctl operations
  • Implementation target: one concrete gate (e.g., platformctl apply --break-glass requires YubiKey touch)

Refs: #132 (parent YubiKey explore), claude/spec/yubikey-platform-integration-v0 (source branch)

## Context Branch `claude/spec/yubikey-platform-integration-v0` (2026-05-17, never merged) contains a 476-line Spec Kit for YubiKey integration across 6 roles. Rather than shipping the full Spec Kit as a PR, we defer the design and scope this issue to the most immediately valuable use case. ## Scope (narrowed) **YubiKey as the LAST physical approval gate before irreversible/destructive/bulk actions**, such as: - `platformctl apply` with `--break-glass` on destructive changes - Bulk branch deletion - Volume/data deletion - Production container restart - Merge of security-sensitive PRs (class/security-sensitive) ## NOT in scope (for now) - YubiKey as general 2FA for Synology/Forgejo/Infisical (6-role rollout) - SSH PIV slot - age-plugin-yubikey co-storage - Full presence-gate architecture from the Spec Kit ## The Spec Kit (reference) Original branch: `claude/spec/yubikey-platform-integration-v0` (author: codex, 2026-05-17) Contains 8 non-negotiable principles (P1-P8) and v0/v0.1 slice plans. Can be used as reference when the time comes. ## Acceptance - [ ] Decision: is YubiKey the right hardware for this use case? - [ ] Design: minimal approval flow for destructive platformctl operations - [ ] Implementation target: one concrete gate (e.g., `platformctl apply --break-glass` requires YubiKey touch) **Refs:** #132 (parent YubiKey explore), `claude/spec/yubikey-platform-integration-v0` (source branch)
Collaborator

Codex source-branch salvage appendix (1/1)

Source branch: claude/spec/yubikey-platform-integration-v0
Attached to: issue #567

Preserves the original YubiKey Spec Kit as reference for the narrowed exploration issue.

This is archival/reference material copied before deleting stale source branches. It is not an approval to merge the old design/code as-is.

docs/specs/yubikey-platform-integration-v0/00-constitution.md
# Constitution — YubiKey Platform Integration v0 + v0.1

**Issues:** #132 (presence gate v0), #181 (6-role rollout v0.1)
**Status:** Proposed (combined Spec Kit)

## Why combined

#132 is a security exploration (presence gate for bounded agent execution); #181 enumerates 6 hardware roles (presence gate is Role 6). Single domain, sequential delivery: v0 = #132 presence gate (highest immediate value), v0.1 = #181 Roles 1-5 rollout.

## Principles (non-negotiable)

**P1 — Physical presence is THE gate, not a hint.** YubiKey-backed approval MUST require physical touch (FIDO2 user-presence flag). No software-only proxy ("just press Y to confirm"). If the operator's YubiKey isn't physically present + touched, agent does not proceed. Period.

**P2 — Bounded execution manifest is signed, not just acknowledged.** Approval produces a signed manifest with explicit `allowed_until`, `max_cost_usd`, `targets`, `allowed_commands`. The signature is verifiable post-hoc. Agents holding expired or unsigned manifests halt at next gate.

**P3 — Backup key is operational requirement.** 2× YubiKey in operator possession (per #181 inventory). Primary may fail/lose; backup must continue platform access. Adoption sequence MUST register both keys for every role. Single-key adoption is rejected — leads to fear-of-enabling.

**P4 — Roles are layered, not flat.** Hardware-tier auth applies in tiers per #132 step-up matrix: read-only no key, internal draft soft approval, Kan/Forgejo mutation bounded session approval, deploy/external send YubiKey required, destructive/security-sensitive YubiKey + explicit manifest + 2nd confirmation.

**P5 — Hardware presence does not bypass software policy.** YubiKey approval ≠ override of ADR-0007 tier system, ADR-0017 stack guards, ADR-0018 fix-not-workaround. Approval authorizes *bounded* execution within policy; never escalates beyond policy.

**P6 — Lost key is operationally survivable.** Procedure for lost-key recovery (backup key activation, audit, re-enrollment) is documented + testable. No "operator panic, escalate to vendor" path.

**P7 — Audit trail is hardware-rooted.** Every gated action's approval manifest references YubiKey serial + slot + signature; audit log carries verifiable provenance, not just "operator approved" string.

**P8 — ADR-0018: no software-only fallback.** If YubiKey unavailable for an operation requiring it, operation halts; cousin does NOT propose "skip YubiKey this time as workaround." Per ADR-0018, software-only path is a separate explicit policy with explicit threat-model implications.

## Boundary

Governs:
- Operator presence gate for bounded agent execution (v0)
- Hardware integration roles 1-6 per #181 inventory (v0.1)

Does NOT govern:
- Software-only approval flows (separate; explicit downgrade requires ADR)
- Vault content storage (governed by #178 Włóczykij + #180 IronKey separately)
- Family-tier consent (governed by #179 separately)
- Cousin self-authorization (cousins NEVER self-elevate)

## References

- Issues #132 #181
- Sibling speculative: #178 Włóczykij second-tier vault, #180 IronKey Keypad, #179 Iskra family tier
- Composes with: #76 Agent Access Plane Slice (d) approval backend, ADR-0013 4th replica (age-plugin-yubikey co-storage)
- ADR-0005, ADR-0007, ADR-0010, ADR-0017, ADR-0018
docs/specs/yubikey-platform-integration-v0/01-specify.md
# Specify — YubiKey Platform Integration v0 + v0.1

## Problem

Per #132: agentic workflows need a physical-presence gate before crossing into production mutation, message-send, deploy, or capability escalation. Pure software approval is insufficient against agent-side compromise / hallucination. Per #181: operator already holds 2× YubiKey hardware; 6 viable roles unlock hardware-tier auth across platform surfaces but no integration exists.

## v0 — Presence gate (#132)

### In scope

- Bounded execution manifest schema (action, project, allowed_until, max_cost_usd, targets, allowed_commands)
- `platformctl yubikey approve --manifest <file>` — operator runs; prompts for YubiKey touch; produces signed approval token
- `platformctl yubikey verify --token <token>` — cousin runs to verify token before mutation
- Capability backend in Access Plane #76 Slice (d) routes "operator-approval-required" capabilities through this flow
- TTL enforcement (allowed_until); auto-expiry; audit on use + expire
- Identity doctor extension: `--yubikey` checks operator key registration

### Out of scope (v0)

- Cousins holding their own YubiKeys (they don't; only operator)
- Remote YubiKey approval (operator at workstation only)
- Multi-operator approval (single operator; family tier governed by #179)
- Vault content encryption (separate epics)

### Functional acceptance

- Operator can issue approval token with bounded manifest + YubiKey touch
- Cousin receives token via Access Plane capability; verifies signature + TTL; proceeds only if green
- Used or expired tokens are not replayable
- Audit log carries YubiKey serial + slot + manifest hash + signature
- Recovery: lost primary key → backup key produces equivalent token; lost-key audit entry recorded

### Demo target (per #132)

Product Pro Summit demo flow: operator briefs Iskra → manifest prepared → operator touches YubiKey → 10-min Hermes window opens → Heartswarm delivery loop after second YubiKey touch.

## v0.1 — 6-role rollout (#181)

### In scope (sequential adoption)

| Role | Purpose | Priority | Effort |
|---|---|---|---|
| 1 — Synology DSM 2FA (FIDO2) | Protects Tier 2 backup target | HIGH | 5 min setup |
| 2 — SSH key storage (PIV slot) | Production SSH from M1 uses hardware key | HIGH | 30 min + per-host |
| 3 — age key co-storage (age-plugin-yubikey) | Encrypts ADR-0013 4th replica age key | MEDIUM | 1h |
| 4 — Forgejo 2FA (WebAuthn) | git.pdurlej.com operator login | MEDIUM | 10 min |
| 5 — Infisical 2FA (WebAuthn) | Operator login to Infisical | MEDIUM | 10 min |
| 6 — Hardware presence gate | v0 already delivered above | HIGH | already done in v0 |

### Functional acceptance per role

Roles 1, 4, 5 (FIDO2/WebAuthn): both keys registered as backup pair; verified login + recovery flow.

Role 2 (PIV/SSH): both keys hold SSH key; `ssh -I pkcs11.so` works to rs2000 + vps1000; old plain key files archived (operator decision when to retire).

Role 3 (age-plugin-yubikey): 4th replica age key re-encrypted via plugin; recovery verified on disposable VM; old plaintext age key retired.

Role 6: covered by v0.

### Out of scope (v0.1)

- Custom PIV applets (use standard YubiKey firmware)
- Hardware OATH-TOTP rollout (deferred until specific use case)
- Cousin-side YubiKey (cousins don't have hardware)

## Dependencies

- Operator holds 2× YubiKey (verified per #181 inventory)
- macOS yubikey-manager (`brew install ykman`)
- age-plugin-yubikey (`brew install age-plugin-yubikey`)
- Synology DSM ≥ 7.0 (operator verifies)
- Composes with #76 Agent Access Plane Slice (d)
docs/specs/yubikey-platform-integration-v0/02-plan.md
# Plan — YubiKey Platform Integration v0 + v0.1

## Architecture

```
control-plane/platformctl/yubikey/
├── manifest.py             # bounded execution manifest schema + validation
├── approve.py              # operator-side: prompt touch, sign manifest, produce token
├── verify.py               # cousin-side: verify signature + TTL + binding
├── audit.py                # YubiKey-rooted audit entries (serial, slot, manifest hash)
└── doctor.py               # platformctl identity doctor --yubikey extension

policies/
├── yubikey-tiers.yaml      # step-up matrix per action class
└── yubikey-recovery.yaml   # lost-key procedure config

runbooks/yubikey/
├── enrollment.md           # initial 2× key registration per role
├── adoption-role-1-synology.md
├── adoption-role-2-ssh-piv.md
├── adoption-role-3-age.md
├── adoption-role-4-forgejo-2fa.md
├── adoption-role-5-infisical-2fa.md
└── lost-key-recovery.md    # backup key activation procedure
```

## Key decisions

**D1 — Manifest signing via PIV slot, not FIDO2 assert.** FIDO2 is for service-side WebAuthn (Roles 1/4/5). Manifest signing uses PIV slot (Role 2 family); operator's GPG-or-PIV identity signs. Verification via stored public key.

**D2 — Token format = signed JSON, not opaque blob.** Cousin can inspect token claims locally before invoking gated action. Signature verification independent from claims read.

**D3 — TTL is conservative.** Default token TTL = 15 minutes; max = 60 minutes; operator may extend explicitly in manifest. Long-running agents re-request approval rather than holding long tokens.

**D4 — Recovery via dual enrollment, not key escrow.** Both YubiKeys hold equivalent material for every role. No backup phrase escrow. Lost primary → backup continues. Lost both → re-enrollment required (rare; operator's emergency procedure).

**D5 — Role adoption is operator-pace, not enforced cadence.** v0.1 lists priority order; operator adopts at own rhythm. Each role has standalone runbook; no waterfall.

**D6 — Capability composition with #76.** Access Plane capabilities tagged `requires_yubikey: true` route approval requests through `platformctl yubikey approve`. Cousin holding capability cannot bypass yubikey requirement.

**D7 — Audit log binds YubiKey serial.** Serial is non-secret; binding makes "which key approved" verifiable forever. Useful for "did backup key approve this?" forensics.

## Risks

| Risk | Mitigation |
|---|---|
| Cousin caches valid token + replays after TTL | TTL enforced server-side via `platformctl yubikey verify`; cached tokens fail on TTL check |
| YubiKey lost during demo | Backup key available; demo runbook practices recovery once before Summit |
| Synology DSM doesn't support FIDO2 in operator's version | Verified in v0.1 Role 1 setup; if no, deferred to DSM upgrade |
| age-plugin-yubikey requires recent macOS | Verified in v0.1 Role 3 setup; brew handles |
| Operator forgets backup key location | Runbook requires both keys at enrollment; physical-location documented per operator preference |

## Composition

v0 composes directly with #76 Agent Access Plane Slice (d). v0.1 composes with ADR-0013 4th replica (Role 3 age key migration). No composition with #79, #56, #134 (different domains).
docs/specs/yubikey-platform-integration-v0/03-tasks.md
# Tasks — YubiKey Platform Integration v0 + v0.1

## v0 — Presence gate (#132)

### Slice (a) — Manifest schema + approve/verify (Full)

**Branch:** `codex/yubikey/slice-a-approve-verify`
**Tier:** Full (`class/security-sensitive`: cryptographic approval flow)

a.1 — `manifest.py`: schema validation (action, project, allowed_until, max_cost_usd, targets, allowed_commands)
a.2 — `approve.py`: operator CLI prompting PIV-slot signing via ykman PKCS#11; produces signed JSON token
a.3 — `verify.py`: cousin CLI verifying signature + TTL + binding; returns ok/expired/invalid
a.4 — Token format spec in `docs/specs/yubikey-platform-integration-v0/token-format.md`
a.5 — Tests with test fixtures (deterministic key pair, not real YubiKey for CI)
a.6 — Manual smoke: operator approves test manifest, claude cousin verifies via test runtime

### Slice (b) — Access Plane composition + audit (Full)

**Branch:** `codex/yubikey/slice-b-access-plane-compose`
**Tier:** Full (composes with #76 Slice d)

b.1 — Capability catalog amendment: `requires_yubikey: true` flag
b.2 — `platformctl access` checks flag, routes to `yubikey verify` before issuing capability
b.3 — Audit hook: every approval/use/expire writes audit.jsonl with YubiKey serial + slot + manifest hash
b.4 — Tests: capability resolution with/without yubikey requirement; expired token rejection; replay rejection
b.5 — Manual smoke: capability requiring yubikey blocks until approved, then proceeds

### Slice (c) — Step-up matrix + tier enforcement (Full)

**Branch:** `codex/yubikey/slice-c-tiers`
**Tier:** Full

c.1 — `policies/yubikey-tiers.yaml`: step-up matrix per action class (read/draft/mutation/deploy/destructive)
c.2 — Policy loader + per-action lookup
c.3 — `platformctl yubikey policy check --action <action>` — returns required gate level
c.4 — Tests: each tier returns correct requirement; unknown action defaults to highest tier
c.5 — Documentation in runbook

### Slice (d) — Doctor + recovery procedure (Lite)

**Branch:** `codex/yubikey/slice-d-doctor-recovery`
**Tier:** Lite (additive doctor check + runbook)

d.1 — `platformctl identity doctor --yubikey` reports key presence + slot status + last-approval time
d.2 — `runbooks/yubikey/lost-key-recovery.md` documenting backup-key activation
d.3 — Tests: doctor with key present / absent / wrong slot
d.4 — Recovery procedure rehearsal documented (operator runs once before Summit)

## v0.1 — 6-role rollout (#181)

### Slice (e) — Role 1 Synology DSM 2FA

**Branch:** `codex/yubikey/slice-e-role-1-synology`
**Tier:** Lite (runbook + verification script)

e.1 — `runbooks/yubikey/adoption-role-1-synology.md` (DSM version check, registration steps, both keys)
e.2 — Verification: login attempt with primary, then backup; both succeed
e.3 — Adopted-state recorded in `state/yubikey-adoption.yaml`

### Slice (f) — Role 2 SSH PIV

**Branch:** `codex/yubikey/slice-f-role-2-ssh-piv`
**Tier:** Lite (runbook + ssh config)

f.1 — `runbooks/yubikey/adoption-role-2-ssh-piv.md` (ykman piv keys generate, SSH client setup)
f.2 — ssh config for rs2000 + vps1000 with PKCS#11 path
f.3 — Verification: ssh to each host with primary, then backup key
f.4 — Plain `~/.ssh/id_*` archival procedure (operator decides retirement timing)

### Slice (g) — Role 3 age-plugin-yubikey

**Branch:** `codex/yubikey/slice-g-role-3-age`
**Tier:** Lite (runbook + key migration)

g.1 — `runbooks/yubikey/adoption-role-3-age.md`
g.2 — Re-encrypt ADR-0013 4th replica age key via plugin
g.3 — Recovery verification on disposable VM
g.4 — Old plaintext age key retirement procedure

### Slice (h) — Roles 4 + 5 Forgejo & Infisical 2FA

**Branch:** `codex/yubikey/slice-h-roles-4-5-webauthn`
**Tier:** Lite (operator runbooks)

h.1 — `runbooks/yubikey/adoption-role-4-forgejo-2fa.md`
h.2 — `runbooks/yubikey/adoption-role-5-infisical-2fa.md`
h.3 — Both keys registered per service
h.4 — Recovery via backup key documented

## Cross-slice

x.1 — `decisions/0021-yubikey-presence-gate.md` ADR drafting outcome of v0
x.2 — `decisions/0022-hardware-tier-auth-rollout.md` ADR drafting outcome of v0.1
x.3 — `docs/security/yubikey-threat-model.md` documenting what hardware presence does + doesn't defend against

## Effort

| Slice | Effort | Gate |
|---|---|---|
| v0 (a) | 3-4h | Full operator merge |
| v0 (b) | 2-3h | Full operator merge |
| v0 (c) | 1-2h | Full operator merge |
| v0 (d) | 1h | Lite self-merge |
| v0.1 (e-h) | 30-60 min each role | Lite, operator-paced |

v0 total: ~7-10h Codex time + 3 operator gates. v0.1 total: ~3-4h operator-side (mostly setup runbooks, not coding).
docs/specs/yubikey-platform-integration-v0/04-implement-notes.md
# Implement Notes — YubiKey Platform Integration v0 + v0.1

## Adversarial paths

- **Cousin holds valid token, agent compromised mid-action** → token still bounded by manifest; compromised cousin can only execute within `allowed_commands` + `max_cost_usd`. Test: malicious cousin attempts out-of-scope action with valid token → blocked.

- **Operator approves under coercion** → policy escape: backup-key absence does NOT create coercion-resistance. Threat model documents this limit (P5 boundary).

- **Replay attack** → token includes nonce + TTL + use-counter; server-side `verify` tracks used nonces; replay rejected. Test: same token twice → second rejected.

- **YubiKey serial spoofing** → serial from hardware attestation, not from token claims; spoofed token fails attestation check. Test: fabricated serial → rejected.

- **DSM 2FA loop after both keys lost** → recovery: operator's Synology has recovery email/SMS fallback per DSM config; documented separately (NOT platform's domain).

- **age-plugin-yubikey loses key during migration** → both keys present at migration; primary fails → backup decrypts; runbook MUST require dual-enrollment before retiring plaintext age key.

- **PKCS#11 library version mismatch** → macOS ssh + ykman versions tested in v0.1 Role 2 runbook; pinned in adoption procedure.

## Pitfalls

- YubiKey touch timeout (default ~10s); operator may miss. CLI prompts MUST loop until touch or explicit cancel.

- Token TTL clock skew between operator workstation + cousin runtime hosts. NTP assumed; if skew > 1 minute, cousins flag in audit.

- Backup key on different physical location vs primary → emergency activation may take time. Runbook documents operator preference + tradeoff.

- age-plugin-yubikey RNG behavior — must use plugin's recommended key generation, NOT a hand-rolled wrapper.

## NOT in v0/v0.1

- Cousin-side YubiKeys (cousins don't have hardware; pure operator-side)
- Hardware MFA for Honcho / Iskra runtime (separate; OpenClaw architecture)
- Family-tier consent (Kasia via operator-supervised; #179 separate)
- Custom YubiKey applets (use standard firmware only)
- Vault content storage (Włóczykij + IronKey separate epics #178 #180)

## Manual smoke checklist

### Before merging v0 final slice
- [ ] Operator approves test manifest with primary key → token issued
- [ ] Cousin verifies token → ok
- [ ] Token replay → rejected
- [ ] Wait past TTL → cousin verify → rejected with expired
- [ ] Operator approves test manifest with BACKUP key → token issued + audit shows backup serial
- [ ] Lost-key drill: operator simulates primary loss, uses backup; runbook procedure followed end-to-end
- [ ] Doctor reports both keys present + last-approval time

### Per v0.1 role adoption
- Role 1: login Synology DSM with primary, logout, login with backup
- Role 2: ssh rs2000 with primary, ssh vps1000 with primary, repeat with backup
- Role 3: encrypt + decrypt test file with age-plugin-yubikey, primary + backup
- Role 4: login git.pdurlej.com web UI with primary, logout, backup
- Role 5: login Infisical with primary, logout, backup

Each role: state recorded in `state/yubikey-adoption.yaml` before declaring complete.

## v0 → v0.1 transition

v0.1 may begin after v0 Slice (d) merges + manual smoke green. v0.1 roles 1-5 are operator-paced; no waterfall. Recommended order: Role 1 (Synology) → Role 2 (SSH) → Role 4 (Forgejo) → Role 5 (Infisical) → Role 3 (age) — based on operator's daily-usage frequency.
docs/specs/yubikey-platform-integration-v0/README.md
# YubiKey Platform Integration v0 + v0.1

**Issues:** #132 (presence gate v0), #181 (6-role rollout v0.1)
**Status:** Proposed (combined prebuild)

## What this is

Hardware-tier auth integration combining two related issues into one Spec Kit. v0 delivers the operator presence gate (most-pressing security primitive for bounded agent execution per #132). v0.1 enumerates and rolls out the remaining 5 hardware roles per #181 inventory (operator already holds 2× YubiKey).

## What this is NOT

- Authorization for any production mutation or credential change
- Software-only fallback for hardware approval (explicit out-of-scope per Constitution P8)
- Cousin-side YubiKey integration (cousins don't have hardware; operator-only)
- Vault content storage (Włóczykij #178 + IronKey #180 separate)
- Family-tier consent (#179 separate)

## Read in this order

1. `00-constitution.md` — P1-P8 (physical presence is the gate; bounded manifest signed; backup key requirement; layered roles; presence ≠ policy bypass; lost-key survivable; hardware-rooted audit; no software fallback)
2. `01-specify.md` — v0 + v0.1 in scope/out of scope/acceptance
3. `02-plan.md` — architecture + D1-D7 (manifest via PIV, token = signed JSON, conservative TTL, dual enrollment, operator-pace adoption, capability composition, audit binds serial)
4. `03-tasks.md` — v0 slices (a-d) + v0.1 slices (e-h)
5. `04-implement-notes.md` — adversarial paths + pitfalls + manual smoke checklist

## Slices summary

### v0 — Presence gate (#132)

| Slice | Scope | Tier |
|---|---|---|
| (a) | Manifest + approve/verify | Full |
| (b) | Access Plane compose + audit | Full |
| (c) | Step-up tier matrix | Full |
| (d) | Doctor + recovery runbook | Lite |

### v0.1 — Role rollout (#181)

| Slice | Role | Tier |
|---|---|---|
| (e) | Role 1 Synology DSM 2FA | Lite (runbook) |
| (f) | Role 2 SSH PIV slot | Lite (runbook) |
| (g) | Role 3 age-plugin-yubikey | Lite (runbook) |
| (h) | Roles 4+5 Forgejo & Infisical 2FA | Lite (runbook) |
| Role 6 | Presence gate — already in v0 | (covered) |

## Composes with

- #76 Agent Access Plane Slice (d) approval backend
- ADR-0013 4th replica (Role 3 age key migration)

Independent of #79 lifecycle, #56 MCP identity, #134 Wake Bus.

## Companion execution prompt

`prompts/codex-yubikey-platform-integration.md`

## Demo target

Product Pro Summit per #132: operator → brief → YubiKey touch → bounded Hermes window → Heartswarm delivery loop after second touch. Demo rehearsal happens once before Summit including lost-key recovery drill.
prompts/codex-yubikey-platform-integration.md
# Codex execution prompt — YubiKey Platform Integration v0 + v0.1

**Companion to:** `docs/specs/yubikey-platform-integration-v0/`
**Parent issues:** #132 (presence gate v0), #181 (6-role rollout v0.1)

## Safety / production boundary

**This Spec Kit prepares implementation only. It does NOT authorize:**

- Modification of operator's actual YubiKey enrollment (operator-only physical action)
- Synology DSM / Forgejo / Infisical 2FA configuration on operator's real accounts (operator-only)
- ADR-0013 4th replica age key migration (operator-only physical action with backup key present)
- Sacred-path modification
- Approval token issuance against real production capabilities
- Software-only fallback for any hardware requirement

Each slice lands via canary 3+3 + appropriate merge per ADR-0007. Most v0 slices are Full (`class/security-sensitive`); v0.1 slices are Lite (runbooks).

## Pre-flight

```bash
cd /Users/pd/Developer/iskra-platform-2026-04-30
git checkout main && git pull --ff-only origin main
git config user.email   # codex@noreply.git.pdurlej.com
ls docs/specs/yubikey-platform-integration-v0/
```

Read 6 files in order. Constitution P1-P8 non-negotiable. P1 (physical presence = gate, not hint) + P8 (no software fallback) are backbone.

## v0 execution (presence gate per #132)

Per `03-tasks.md` Slices (a)-(d):
- (a) `codex/yubikey/slice-a-approve-verify` Full — manifest + approve + verify
- (b) `codex/yubikey/slice-b-access-plane-compose` Full — capability flag + audit + composition with #76
- (c) `codex/yubikey/slice-c-tiers` Full — step-up matrix
- (d) `codex/yubikey/slice-d-doctor-recovery` Lite — doctor + lost-key runbook

All test fixtures use deterministic test key pair (NOT real YubiKey for CI). Real-key smoke is operator-driven, documented in `04-implement-notes.md` manual checklist.

## v0.1 execution (6-role rollout per #181)

Per `03-tasks.md` Slices (e)-(h):
- (e) Role 1 Synology DSM 2FA — runbook only
- (f) Role 2 SSH PIV — runbook + ssh config
- (g) Role 3 age-plugin-yubikey — runbook + migration helper script
- (h) Roles 4+5 Forgejo & Infisical 2FA — runbook only

Codex deliverable = runbook + verification scripts. Operator runs each adoption physically (touch YubiKey, register both keys).

## Stop conditions

Per ADR-0018:
1. 3-strikes any test → STOP
2. Sacred-path violation attempt → STOP
3. Hard gate (G4-G7) → STOP, ask operator
4. Quota < 20% → STOP, handover
5. "Accept software fallback" temptation → STOP per Constitution P8 + ADR-0018
6. Real cryptographic material in test output → CRITICAL halt
7. Manifest schema would allow unbounded `allowed_commands: ["*"]` → REJECT, that defeats P2

## Reporting

Per slice: checkpoint on #132 (v0) or #181 (v0.1) + summary on #142.

## Cousin coordination

- claude (Pan Herbatka batch A fork): advisory only
- glm: borderline (d) Lite review; runbook reviews for v0.1
- DeepSeek: invoke for (a)/(b)/(c) cryptographic + architecture questions
- operator: merge gate Full tier; physical-action gate for every real adoption

## References

- Spec Kit: `docs/specs/yubikey-platform-integration-v0/`
- Issues #132 #181 #178 #180 #179 (sibling speculative)
- ADR-0013 (4th replica age co-storage)
- PR #323 Agent Access Plane v0 (composes Slice d)
- ADR-0005 ADR-0007 ADR-0010 ADR-0017 ADR-0018
- `state/ANY-COUSIN-WAKEUP.md`
<!-- codex-source-branch-salvage:v1 source=claude/spec/yubikey-platform-integration-v0 part=1/1 --> ## Codex source-branch salvage appendix (1/1) **Source branch:** `claude/spec/yubikey-platform-integration-v0` **Attached to:** issue #567 Preserves the original YubiKey Spec Kit as reference for the narrowed exploration issue. This is archival/reference material copied before deleting stale source branches. It is not an approval to merge the old design/code as-is. <details> <summary><code>docs/specs/yubikey-platform-integration-v0/00-constitution.md</code></summary> <pre><code># Constitution — YubiKey Platform Integration v0 + v0.1 **Issues:** #132 (presence gate v0), #181 (6-role rollout v0.1) **Status:** Proposed (combined Spec Kit) ## Why combined #132 is a security exploration (presence gate for bounded agent execution); #181 enumerates 6 hardware roles (presence gate is Role 6). Single domain, sequential delivery: v0 = #132 presence gate (highest immediate value), v0.1 = #181 Roles 1-5 rollout. ## Principles (non-negotiable) **P1 — Physical presence is THE gate, not a hint.** YubiKey-backed approval MUST require physical touch (FIDO2 user-presence flag). No software-only proxy (&quot;just press Y to confirm&quot;). If the operator&#x27;s YubiKey isn&#x27;t physically present + touched, agent does not proceed. Period. **P2 — Bounded execution manifest is signed, not just acknowledged.** Approval produces a signed manifest with explicit `allowed_until`, `max_cost_usd`, `targets`, `allowed_commands`. The signature is verifiable post-hoc. Agents holding expired or unsigned manifests halt at next gate. **P3 — Backup key is operational requirement.** 2× YubiKey in operator possession (per #181 inventory). Primary may fail/lose; backup must continue platform access. Adoption sequence MUST register both keys for every role. Single-key adoption is rejected — leads to fear-of-enabling. **P4 — Roles are layered, not flat.** Hardware-tier auth applies in tiers per #132 step-up matrix: read-only no key, internal draft soft approval, Kan/Forgejo mutation bounded session approval, deploy/external send YubiKey required, destructive/security-sensitive YubiKey + explicit manifest + 2nd confirmation. **P5 — Hardware presence does not bypass software policy.** YubiKey approval ≠ override of ADR-0007 tier system, ADR-0017 stack guards, ADR-0018 fix-not-workaround. Approval authorizes *bounded* execution within policy; never escalates beyond policy. **P6 — Lost key is operationally survivable.** Procedure for lost-key recovery (backup key activation, audit, re-enrollment) is documented + testable. No &quot;operator panic, escalate to vendor&quot; path. **P7 — Audit trail is hardware-rooted.** Every gated action&#x27;s approval manifest references YubiKey serial + slot + signature; audit log carries verifiable provenance, not just &quot;operator approved&quot; string. **P8 — ADR-0018: no software-only fallback.** If YubiKey unavailable for an operation requiring it, operation halts; cousin does NOT propose &quot;skip YubiKey this time as workaround.&quot; Per ADR-0018, software-only path is a separate explicit policy with explicit threat-model implications. ## Boundary Governs: - Operator presence gate for bounded agent execution (v0) - Hardware integration roles 1-6 per #181 inventory (v0.1) Does NOT govern: - Software-only approval flows (separate; explicit downgrade requires ADR) - Vault content storage (governed by #178 Włóczykij + #180 IronKey separately) - Family-tier consent (governed by #179 separately) - Cousin self-authorization (cousins NEVER self-elevate) ## References - Issues #132 #181 - Sibling speculative: #178 Włóczykij second-tier vault, #180 IronKey Keypad, #179 Iskra family tier - Composes with: #76 Agent Access Plane Slice (d) approval backend, ADR-0013 4th replica (age-plugin-yubikey co-storage) - ADR-0005, ADR-0007, ADR-0010, ADR-0017, ADR-0018 </code></pre> </details> <details> <summary><code>docs/specs/yubikey-platform-integration-v0/01-specify.md</code></summary> <pre><code># Specify — YubiKey Platform Integration v0 + v0.1 ## Problem Per #132: agentic workflows need a physical-presence gate before crossing into production mutation, message-send, deploy, or capability escalation. Pure software approval is insufficient against agent-side compromise / hallucination. Per #181: operator already holds 2× YubiKey hardware; 6 viable roles unlock hardware-tier auth across platform surfaces but no integration exists. ## v0 — Presence gate (#132) ### In scope - Bounded execution manifest schema (action, project, allowed_until, max_cost_usd, targets, allowed_commands) - `platformctl yubikey approve --manifest &lt;file&gt;` — operator runs; prompts for YubiKey touch; produces signed approval token - `platformctl yubikey verify --token &lt;token&gt;` — cousin runs to verify token before mutation - Capability backend in Access Plane #76 Slice (d) routes &quot;operator-approval-required&quot; capabilities through this flow - TTL enforcement (allowed_until); auto-expiry; audit on use + expire - Identity doctor extension: `--yubikey` checks operator key registration ### Out of scope (v0) - Cousins holding their own YubiKeys (they don&#x27;t; only operator) - Remote YubiKey approval (operator at workstation only) - Multi-operator approval (single operator; family tier governed by #179) - Vault content encryption (separate epics) ### Functional acceptance - Operator can issue approval token with bounded manifest + YubiKey touch - Cousin receives token via Access Plane capability; verifies signature + TTL; proceeds only if green - Used or expired tokens are not replayable - Audit log carries YubiKey serial + slot + manifest hash + signature - Recovery: lost primary key → backup key produces equivalent token; lost-key audit entry recorded ### Demo target (per #132) Product Pro Summit demo flow: operator briefs Iskra → manifest prepared → operator touches YubiKey → 10-min Hermes window opens → Heartswarm delivery loop after second YubiKey touch. ## v0.1 — 6-role rollout (#181) ### In scope (sequential adoption) | Role | Purpose | Priority | Effort | |---|---|---|---| | 1 — Synology DSM 2FA (FIDO2) | Protects Tier 2 backup target | HIGH | 5 min setup | | 2 — SSH key storage (PIV slot) | Production SSH from M1 uses hardware key | HIGH | 30 min + per-host | | 3 — age key co-storage (age-plugin-yubikey) | Encrypts ADR-0013 4th replica age key | MEDIUM | 1h | | 4 — Forgejo 2FA (WebAuthn) | git.pdurlej.com operator login | MEDIUM | 10 min | | 5 — Infisical 2FA (WebAuthn) | Operator login to Infisical | MEDIUM | 10 min | | 6 — Hardware presence gate | v0 already delivered above | HIGH | already done in v0 | ### Functional acceptance per role Roles 1, 4, 5 (FIDO2/WebAuthn): both keys registered as backup pair; verified login + recovery flow. Role 2 (PIV/SSH): both keys hold SSH key; `ssh -I pkcs11.so` works to rs2000 + vps1000; old plain key files archived (operator decision when to retire). Role 3 (age-plugin-yubikey): 4th replica age key re-encrypted via plugin; recovery verified on disposable VM; old plaintext age key retired. Role 6: covered by v0. ### Out of scope (v0.1) - Custom PIV applets (use standard YubiKey firmware) - Hardware OATH-TOTP rollout (deferred until specific use case) - Cousin-side YubiKey (cousins don&#x27;t have hardware) ## Dependencies - Operator holds 2× YubiKey (verified per #181 inventory) - macOS yubikey-manager (`brew install ykman`) - age-plugin-yubikey (`brew install age-plugin-yubikey`) - Synology DSM ≥ 7.0 (operator verifies) - Composes with #76 Agent Access Plane Slice (d) </code></pre> </details> <details> <summary><code>docs/specs/yubikey-platform-integration-v0/02-plan.md</code></summary> <pre><code># Plan — YubiKey Platform Integration v0 + v0.1 ## Architecture ``` control-plane/platformctl/yubikey/ ├── manifest.py # bounded execution manifest schema + validation ├── approve.py # operator-side: prompt touch, sign manifest, produce token ├── verify.py # cousin-side: verify signature + TTL + binding ├── audit.py # YubiKey-rooted audit entries (serial, slot, manifest hash) └── doctor.py # platformctl identity doctor --yubikey extension policies/ ├── yubikey-tiers.yaml # step-up matrix per action class └── yubikey-recovery.yaml # lost-key procedure config runbooks/yubikey/ ├── enrollment.md # initial 2× key registration per role ├── adoption-role-1-synology.md ├── adoption-role-2-ssh-piv.md ├── adoption-role-3-age.md ├── adoption-role-4-forgejo-2fa.md ├── adoption-role-5-infisical-2fa.md └── lost-key-recovery.md # backup key activation procedure ``` ## Key decisions **D1 — Manifest signing via PIV slot, not FIDO2 assert.** FIDO2 is for service-side WebAuthn (Roles 1/4/5). Manifest signing uses PIV slot (Role 2 family); operator&#x27;s GPG-or-PIV identity signs. Verification via stored public key. **D2 — Token format = signed JSON, not opaque blob.** Cousin can inspect token claims locally before invoking gated action. Signature verification independent from claims read. **D3 — TTL is conservative.** Default token TTL = 15 minutes; max = 60 minutes; operator may extend explicitly in manifest. Long-running agents re-request approval rather than holding long tokens. **D4 — Recovery via dual enrollment, not key escrow.** Both YubiKeys hold equivalent material for every role. No backup phrase escrow. Lost primary → backup continues. Lost both → re-enrollment required (rare; operator&#x27;s emergency procedure). **D5 — Role adoption is operator-pace, not enforced cadence.** v0.1 lists priority order; operator adopts at own rhythm. Each role has standalone runbook; no waterfall. **D6 — Capability composition with #76.** Access Plane capabilities tagged `requires_yubikey: true` route approval requests through `platformctl yubikey approve`. Cousin holding capability cannot bypass yubikey requirement. **D7 — Audit log binds YubiKey serial.** Serial is non-secret; binding makes &quot;which key approved&quot; verifiable forever. Useful for &quot;did backup key approve this?&quot; forensics. ## Risks | Risk | Mitigation | |---|---| | Cousin caches valid token + replays after TTL | TTL enforced server-side via `platformctl yubikey verify`; cached tokens fail on TTL check | | YubiKey lost during demo | Backup key available; demo runbook practices recovery once before Summit | | Synology DSM doesn&#x27;t support FIDO2 in operator&#x27;s version | Verified in v0.1 Role 1 setup; if no, deferred to DSM upgrade | | age-plugin-yubikey requires recent macOS | Verified in v0.1 Role 3 setup; brew handles | | Operator forgets backup key location | Runbook requires both keys at enrollment; physical-location documented per operator preference | ## Composition v0 composes directly with #76 Agent Access Plane Slice (d). v0.1 composes with ADR-0013 4th replica (Role 3 age key migration). No composition with #79, #56, #134 (different domains). </code></pre> </details> <details> <summary><code>docs/specs/yubikey-platform-integration-v0/03-tasks.md</code></summary> <pre><code># Tasks — YubiKey Platform Integration v0 + v0.1 ## v0 — Presence gate (#132) ### Slice (a) — Manifest schema + approve/verify (Full) **Branch:** `codex/yubikey/slice-a-approve-verify` **Tier:** Full (`class/security-sensitive`: cryptographic approval flow) a.1 — `manifest.py`: schema validation (action, project, allowed_until, max_cost_usd, targets, allowed_commands) a.2 — `approve.py`: operator CLI prompting PIV-slot signing via ykman PKCS#11; produces signed JSON token a.3 — `verify.py`: cousin CLI verifying signature + TTL + binding; returns ok/expired/invalid a.4 — Token format spec in `docs/specs/yubikey-platform-integration-v0/token-format.md` a.5 — Tests with test fixtures (deterministic key pair, not real YubiKey for CI) a.6 — Manual smoke: operator approves test manifest, claude cousin verifies via test runtime ### Slice (b) — Access Plane composition + audit (Full) **Branch:** `codex/yubikey/slice-b-access-plane-compose` **Tier:** Full (composes with #76 Slice d) b.1 — Capability catalog amendment: `requires_yubikey: true` flag b.2 — `platformctl access` checks flag, routes to `yubikey verify` before issuing capability b.3 — Audit hook: every approval/use/expire writes audit.jsonl with YubiKey serial + slot + manifest hash b.4 — Tests: capability resolution with/without yubikey requirement; expired token rejection; replay rejection b.5 — Manual smoke: capability requiring yubikey blocks until approved, then proceeds ### Slice (c) — Step-up matrix + tier enforcement (Full) **Branch:** `codex/yubikey/slice-c-tiers` **Tier:** Full c.1 — `policies/yubikey-tiers.yaml`: step-up matrix per action class (read/draft/mutation/deploy/destructive) c.2 — Policy loader + per-action lookup c.3 — `platformctl yubikey policy check --action &lt;action&gt;` — returns required gate level c.4 — Tests: each tier returns correct requirement; unknown action defaults to highest tier c.5 — Documentation in runbook ### Slice (d) — Doctor + recovery procedure (Lite) **Branch:** `codex/yubikey/slice-d-doctor-recovery` **Tier:** Lite (additive doctor check + runbook) d.1 — `platformctl identity doctor --yubikey` reports key presence + slot status + last-approval time d.2 — `runbooks/yubikey/lost-key-recovery.md` documenting backup-key activation d.3 — Tests: doctor with key present / absent / wrong slot d.4 — Recovery procedure rehearsal documented (operator runs once before Summit) ## v0.1 — 6-role rollout (#181) ### Slice (e) — Role 1 Synology DSM 2FA **Branch:** `codex/yubikey/slice-e-role-1-synology` **Tier:** Lite (runbook + verification script) e.1 — `runbooks/yubikey/adoption-role-1-synology.md` (DSM version check, registration steps, both keys) e.2 — Verification: login attempt with primary, then backup; both succeed e.3 — Adopted-state recorded in `state/yubikey-adoption.yaml` ### Slice (f) — Role 2 SSH PIV **Branch:** `codex/yubikey/slice-f-role-2-ssh-piv` **Tier:** Lite (runbook + ssh config) f.1 — `runbooks/yubikey/adoption-role-2-ssh-piv.md` (ykman piv keys generate, SSH client setup) f.2 — ssh config for rs2000 + vps1000 with PKCS#11 path f.3 — Verification: ssh to each host with primary, then backup key f.4 — Plain `~/.ssh/id_*` archival procedure (operator decides retirement timing) ### Slice (g) — Role 3 age-plugin-yubikey **Branch:** `codex/yubikey/slice-g-role-3-age` **Tier:** Lite (runbook + key migration) g.1 — `runbooks/yubikey/adoption-role-3-age.md` g.2 — Re-encrypt ADR-0013 4th replica age key via plugin g.3 — Recovery verification on disposable VM g.4 — Old plaintext age key retirement procedure ### Slice (h) — Roles 4 + 5 Forgejo &amp; Infisical 2FA **Branch:** `codex/yubikey/slice-h-roles-4-5-webauthn` **Tier:** Lite (operator runbooks) h.1 — `runbooks/yubikey/adoption-role-4-forgejo-2fa.md` h.2 — `runbooks/yubikey/adoption-role-5-infisical-2fa.md` h.3 — Both keys registered per service h.4 — Recovery via backup key documented ## Cross-slice x.1 — `decisions/0021-yubikey-presence-gate.md` ADR drafting outcome of v0 x.2 — `decisions/0022-hardware-tier-auth-rollout.md` ADR drafting outcome of v0.1 x.3 — `docs/security/yubikey-threat-model.md` documenting what hardware presence does + doesn&#x27;t defend against ## Effort | Slice | Effort | Gate | |---|---|---| | v0 (a) | 3-4h | Full operator merge | | v0 (b) | 2-3h | Full operator merge | | v0 (c) | 1-2h | Full operator merge | | v0 (d) | 1h | Lite self-merge | | v0.1 (e-h) | 30-60 min each role | Lite, operator-paced | v0 total: ~7-10h Codex time + 3 operator gates. v0.1 total: ~3-4h operator-side (mostly setup runbooks, not coding). </code></pre> </details> <details> <summary><code>docs/specs/yubikey-platform-integration-v0/04-implement-notes.md</code></summary> <pre><code># Implement Notes — YubiKey Platform Integration v0 + v0.1 ## Adversarial paths - **Cousin holds valid token, agent compromised mid-action** → token still bounded by manifest; compromised cousin can only execute within `allowed_commands` + `max_cost_usd`. Test: malicious cousin attempts out-of-scope action with valid token → blocked. - **Operator approves under coercion** → policy escape: backup-key absence does NOT create coercion-resistance. Threat model documents this limit (P5 boundary). - **Replay attack** → token includes nonce + TTL + use-counter; server-side `verify` tracks used nonces; replay rejected. Test: same token twice → second rejected. - **YubiKey serial spoofing** → serial from hardware attestation, not from token claims; spoofed token fails attestation check. Test: fabricated serial → rejected. - **DSM 2FA loop after both keys lost** → recovery: operator&#x27;s Synology has recovery email/SMS fallback per DSM config; documented separately (NOT platform&#x27;s domain). - **age-plugin-yubikey loses key during migration** → both keys present at migration; primary fails → backup decrypts; runbook MUST require dual-enrollment before retiring plaintext age key. - **PKCS#11 library version mismatch** → macOS ssh + ykman versions tested in v0.1 Role 2 runbook; pinned in adoption procedure. ## Pitfalls - YubiKey touch timeout (default ~10s); operator may miss. CLI prompts MUST loop until touch or explicit cancel. - Token TTL clock skew between operator workstation + cousin runtime hosts. NTP assumed; if skew &gt; 1 minute, cousins flag in audit. - Backup key on different physical location vs primary → emergency activation may take time. Runbook documents operator preference + tradeoff. - age-plugin-yubikey RNG behavior — must use plugin&#x27;s recommended key generation, NOT a hand-rolled wrapper. ## NOT in v0/v0.1 - Cousin-side YubiKeys (cousins don&#x27;t have hardware; pure operator-side) - Hardware MFA for Honcho / Iskra runtime (separate; OpenClaw architecture) - Family-tier consent (Kasia via operator-supervised; #179 separate) - Custom YubiKey applets (use standard firmware only) - Vault content storage (Włóczykij + IronKey separate epics #178 #180) ## Manual smoke checklist ### Before merging v0 final slice - [ ] Operator approves test manifest with primary key → token issued - [ ] Cousin verifies token → ok - [ ] Token replay → rejected - [ ] Wait past TTL → cousin verify → rejected with expired - [ ] Operator approves test manifest with BACKUP key → token issued + audit shows backup serial - [ ] Lost-key drill: operator simulates primary loss, uses backup; runbook procedure followed end-to-end - [ ] Doctor reports both keys present + last-approval time ### Per v0.1 role adoption - Role 1: login Synology DSM with primary, logout, login with backup - Role 2: ssh rs2000 with primary, ssh vps1000 with primary, repeat with backup - Role 3: encrypt + decrypt test file with age-plugin-yubikey, primary + backup - Role 4: login git.pdurlej.com web UI with primary, logout, backup - Role 5: login Infisical with primary, logout, backup Each role: state recorded in `state/yubikey-adoption.yaml` before declaring complete. ## v0 → v0.1 transition v0.1 may begin after v0 Slice (d) merges + manual smoke green. v0.1 roles 1-5 are operator-paced; no waterfall. Recommended order: Role 1 (Synology) → Role 2 (SSH) → Role 4 (Forgejo) → Role 5 (Infisical) → Role 3 (age) — based on operator&#x27;s daily-usage frequency. </code></pre> </details> <details> <summary><code>docs/specs/yubikey-platform-integration-v0/README.md</code></summary> <pre><code># YubiKey Platform Integration v0 + v0.1 **Issues:** #132 (presence gate v0), #181 (6-role rollout v0.1) **Status:** Proposed (combined prebuild) ## What this is Hardware-tier auth integration combining two related issues into one Spec Kit. v0 delivers the operator presence gate (most-pressing security primitive for bounded agent execution per #132). v0.1 enumerates and rolls out the remaining 5 hardware roles per #181 inventory (operator already holds 2× YubiKey). ## What this is NOT - Authorization for any production mutation or credential change - Software-only fallback for hardware approval (explicit out-of-scope per Constitution P8) - Cousin-side YubiKey integration (cousins don&#x27;t have hardware; operator-only) - Vault content storage (Włóczykij #178 + IronKey #180 separate) - Family-tier consent (#179 separate) ## Read in this order 1. `00-constitution.md` — P1-P8 (physical presence is the gate; bounded manifest signed; backup key requirement; layered roles; presence ≠ policy bypass; lost-key survivable; hardware-rooted audit; no software fallback) 2. `01-specify.md` — v0 + v0.1 in scope/out of scope/acceptance 3. `02-plan.md` — architecture + D1-D7 (manifest via PIV, token = signed JSON, conservative TTL, dual enrollment, operator-pace adoption, capability composition, audit binds serial) 4. `03-tasks.md` — v0 slices (a-d) + v0.1 slices (e-h) 5. `04-implement-notes.md` — adversarial paths + pitfalls + manual smoke checklist ## Slices summary ### v0 — Presence gate (#132) | Slice | Scope | Tier | |---|---|---| | (a) | Manifest + approve/verify | Full | | (b) | Access Plane compose + audit | Full | | (c) | Step-up tier matrix | Full | | (d) | Doctor + recovery runbook | Lite | ### v0.1 — Role rollout (#181) | Slice | Role | Tier | |---|---|---| | (e) | Role 1 Synology DSM 2FA | Lite (runbook) | | (f) | Role 2 SSH PIV slot | Lite (runbook) | | (g) | Role 3 age-plugin-yubikey | Lite (runbook) | | (h) | Roles 4+5 Forgejo &amp; Infisical 2FA | Lite (runbook) | | Role 6 | Presence gate — already in v0 | (covered) | ## Composes with - #76 Agent Access Plane Slice (d) approval backend - ADR-0013 4th replica (Role 3 age key migration) Independent of #79 lifecycle, #56 MCP identity, #134 Wake Bus. ## Companion execution prompt `prompts/codex-yubikey-platform-integration.md` ## Demo target Product Pro Summit per #132: operator → brief → YubiKey touch → bounded Hermes window → Heartswarm delivery loop after second touch. Demo rehearsal happens once before Summit including lost-key recovery drill. </code></pre> </details> <details> <summary><code>prompts/codex-yubikey-platform-integration.md</code></summary> <pre><code># Codex execution prompt — YubiKey Platform Integration v0 + v0.1 **Companion to:** `docs/specs/yubikey-platform-integration-v0/` **Parent issues:** #132 (presence gate v0), #181 (6-role rollout v0.1) ## Safety / production boundary **This Spec Kit prepares implementation only. It does NOT authorize:** - Modification of operator&#x27;s actual YubiKey enrollment (operator-only physical action) - Synology DSM / Forgejo / Infisical 2FA configuration on operator&#x27;s real accounts (operator-only) - ADR-0013 4th replica age key migration (operator-only physical action with backup key present) - Sacred-path modification - Approval token issuance against real production capabilities - Software-only fallback for any hardware requirement Each slice lands via canary 3+3 + appropriate merge per ADR-0007. Most v0 slices are Full (`class/security-sensitive`); v0.1 slices are Lite (runbooks). ## Pre-flight ```bash cd /Users/pd/Developer/iskra-platform-2026-04-30 git checkout main &amp;&amp; git pull --ff-only origin main git config user.email # codex@noreply.git.pdurlej.com ls docs/specs/yubikey-platform-integration-v0/ ``` Read 6 files in order. Constitution P1-P8 non-negotiable. P1 (physical presence = gate, not hint) + P8 (no software fallback) are backbone. ## v0 execution (presence gate per #132) Per `03-tasks.md` Slices (a)-(d): - (a) `codex/yubikey/slice-a-approve-verify` Full — manifest + approve + verify - (b) `codex/yubikey/slice-b-access-plane-compose` Full — capability flag + audit + composition with #76 - (c) `codex/yubikey/slice-c-tiers` Full — step-up matrix - (d) `codex/yubikey/slice-d-doctor-recovery` Lite — doctor + lost-key runbook All test fixtures use deterministic test key pair (NOT real YubiKey for CI). Real-key smoke is operator-driven, documented in `04-implement-notes.md` manual checklist. ## v0.1 execution (6-role rollout per #181) Per `03-tasks.md` Slices (e)-(h): - (e) Role 1 Synology DSM 2FA — runbook only - (f) Role 2 SSH PIV — runbook + ssh config - (g) Role 3 age-plugin-yubikey — runbook + migration helper script - (h) Roles 4+5 Forgejo &amp; Infisical 2FA — runbook only Codex deliverable = runbook + verification scripts. Operator runs each adoption physically (touch YubiKey, register both keys). ## Stop conditions Per ADR-0018: 1. 3-strikes any test → STOP 2. Sacred-path violation attempt → STOP 3. Hard gate (G4-G7) → STOP, ask operator 4. Quota &lt; 20% → STOP, handover 5. &quot;Accept software fallback&quot; temptation → STOP per Constitution P8 + ADR-0018 6. Real cryptographic material in test output → CRITICAL halt 7. Manifest schema would allow unbounded `allowed_commands: [&quot;*&quot;]` → REJECT, that defeats P2 ## Reporting Per slice: checkpoint on #132 (v0) or #181 (v0.1) + summary on #142. ## Cousin coordination - claude (Pan Herbatka batch A fork): advisory only - glm: borderline (d) Lite review; runbook reviews for v0.1 - DeepSeek: invoke for (a)/(b)/(c) cryptographic + architecture questions - operator: merge gate Full tier; physical-action gate for every real adoption ## References - Spec Kit: `docs/specs/yubikey-platform-integration-v0/` - Issues #132 #181 #178 #180 #179 (sibling speculative) - ADR-0013 (4th replica age co-storage) - PR #323 Agent Access Plane v0 (composes Slice d) - ADR-0005 ADR-0007 ADR-0010 ADR-0017 ADR-0018 - `state/ANY-COUSIN-WAKEUP.md` </code></pre> </details>
Collaborator

M10 disposition: moved to 10 - Improvements.

What this is: YubiKey destructive-action approval exploration.

Why parked here: Parked in M10 because the concept is valuable for irreversible-action gates, but M03 secrets closeout should not wait on hardware-auth design.

This preserves the idea without letting it block M02/M03/M04 closeout. Before reactivation, split it into a narrow issue or PR with concrete acceptance criteria.

M10 disposition: moved to `10 - Improvements`. What this is: YubiKey destructive-action approval exploration. Why parked here: Parked in M10 because the concept is valuable for irreversible-action gates, but M03 secrets closeout should not wait on hardware-auth design. This preserves the idea without letting it block M02/M03/M04 closeout. Before reactivation, split it into a narrow issue or PR with concrete acceptance criteria.
Collaborator

M10 closure note: held, not parked. This issue has no priority:* from Judging Claw, unlike the rest of M10. It is part of the security / agent-access cluster (#76 Agent Access Plane + #132/#567 YubiKey operator-consent). Held out of the p3-park batch because security-sensitive design warrants a deliberate priority decision, not default-parking. Next: Judging Claw / operator assigns priority, then consolidate the cluster into one Agent-Access + Hardware-Consent ADR. Ready reference: Infisical Agent Vault (Context7 /infisical/agent-vault).

**M10 closure note: held, not parked.** This issue has no `priority:*` from Judging Claw, unlike the rest of M10. It is part of the security / agent-access cluster (#76 Agent Access Plane + #132/#567 YubiKey operator-consent). Held out of the p3-park batch because security-sensitive design warrants a deliberate priority decision, not default-parking. Next: Judging Claw / operator assigns priority, then consolidate the cluster into one Agent-Access + Hardware-Consent ADR. Ready reference: Infisical Agent Vault (Context7 `/infisical/agent-vault`).
Collaborator

Iskra judgment

Field Value
Target pdurlej/platform#issue#567
Priority p1
Action operator_needed
Scores reach 4 / impact 5 / confidence 5
Piotr fit high
Effort medium
Labels judge/p1, judge/operator-needed
Judge iskra via openclaw

Rationale: This is P1 operator-shaped security work because a narrow YubiKey last-gate can reduce blast radius for irreversible platform actions without adopting the full Spec Kit.

Caveat: Define bypass, recovery, audit, and exact action classes before any gate can block production operations.

Structured openclaw.judge.v0 payload
<!-- openclaw.judge.v0 -->
{
  "confidence": 5,
  "effort_hint": "medium",
  "escalation": {
    "kind": "operator",
    "reason": "Physical approval gates for irreversible actions define safety policy and operator ceremony, not just implementation detail."
  },
  "evidence_refs": [
    {
      "note": "Issue narrows prior YubiKey platform integration work to last physical approval before irreversible or destructive actions.",
      "type": "forgejo",
      "value": "issue-title-body-labels-and-target-snapshot"
    },
    {
      "note": "Body lists destructive apply, bulk branch deletion, volume deletion, production restart, and security-sensitive PR merge as candidate gates.",
      "type": "forgejo",
      "value": "issue-body-scope"
    },
    {
      "note": "Body explicitly excludes broad 2FA, SSH PIV, age co-storage, and full presence-gate architecture from v0 scope.",
      "type": "forgejo",
      "value": "issue-body-not-in-scope"
    }
  ],
  "impact": 5,
  "judge_actor": {
    "name": "iskra",
    "runtime": "openclaw"
  },
  "judged_at": "2026-06-11T01:01:00Z",
  "labels_to_apply": [
    "judge/p1",
    "judge/operator-needed"
  ],
  "piotr_fit": "high",
  "priority": "p1",
  "rationale_summary": "This is P1 operator-shaped security work because a narrow YubiKey last-gate can reduce blast radius for irreversible platform actions without adopting the full Spec Kit.",
  "reach": 4,
  "recommended_next_action": "operator_needed",
  "rerun_reason": "no_prior_judgment",
  "schema": "openclaw.judge.v0",
  "target": {
    "kind": "issue",
    "number": 567,
    "repo": "pdurlej/platform"
  },
  "target_snapshot": {
    "body_hash": "sha256:f6325c8b60c395eb8a1716ac85667c61a76079da60245028586061aec4eae262",
    "commit_count": null,
    "evidence_hash": "sha256:4a48e4b43ba1b79735a28abc4ac1a34a047472bfd9d1ae8f686de365918feced",
    "head_sha": null,
    "labels": [
      "agent/claude-code"
    ],
    "labels_hash": "sha256:ae196d477682b017093b459243e1d16cfb291d1ea2cc928b191bab51fa5081d7",
    "state": "open",
    "title_hash": "sha256:2da925217cd61f845a4b03527fcecd56d0b036e777c23dfc515b7d55840fd76b",
    "updated_at": "2026-06-11T00:14:03+02:00"
  },
  "top_caveat": "Define bypass, recovery, audit, and exact action classes before any gate can block production operations."
}
<!-- /openclaw.judge.v0 -->
### Iskra judgment | Field | Value | | --- | --- | | Target | `pdurlej/platform#issue#567` | | Priority | p1 | | Action | operator_needed | | Scores | reach 4 / impact 5 / confidence 5 | | Piotr fit | high | | Effort | medium | | Labels | `judge/p1`, `judge/operator-needed` | | Judge | `iskra` via `openclaw` | **Rationale:** This is P1 operator-shaped security work because a narrow YubiKey last-gate can reduce blast radius for irreversible platform actions without adopting the full Spec Kit. **Caveat:** Define bypass, recovery, audit, and exact action classes before any gate can block production operations. <details> <summary>Structured openclaw.judge.v0 payload</summary> ```json <!-- openclaw.judge.v0 --> { "confidence": 5, "effort_hint": "medium", "escalation": { "kind": "operator", "reason": "Physical approval gates for irreversible actions define safety policy and operator ceremony, not just implementation detail." }, "evidence_refs": [ { "note": "Issue narrows prior YubiKey platform integration work to last physical approval before irreversible or destructive actions.", "type": "forgejo", "value": "issue-title-body-labels-and-target-snapshot" }, { "note": "Body lists destructive apply, bulk branch deletion, volume deletion, production restart, and security-sensitive PR merge as candidate gates.", "type": "forgejo", "value": "issue-body-scope" }, { "note": "Body explicitly excludes broad 2FA, SSH PIV, age co-storage, and full presence-gate architecture from v0 scope.", "type": "forgejo", "value": "issue-body-not-in-scope" } ], "impact": 5, "judge_actor": { "name": "iskra", "runtime": "openclaw" }, "judged_at": "2026-06-11T01:01:00Z", "labels_to_apply": [ "judge/p1", "judge/operator-needed" ], "piotr_fit": "high", "priority": "p1", "rationale_summary": "This is P1 operator-shaped security work because a narrow YubiKey last-gate can reduce blast radius for irreversible platform actions without adopting the full Spec Kit.", "reach": 4, "recommended_next_action": "operator_needed", "rerun_reason": "no_prior_judgment", "schema": "openclaw.judge.v0", "target": { "kind": "issue", "number": 567, "repo": "pdurlej/platform" }, "target_snapshot": { "body_hash": "sha256:f6325c8b60c395eb8a1716ac85667c61a76079da60245028586061aec4eae262", "commit_count": null, "evidence_hash": "sha256:4a48e4b43ba1b79735a28abc4ac1a34a047472bfd9d1ae8f686de365918feced", "head_sha": null, "labels": [ "agent/claude-code" ], "labels_hash": "sha256:ae196d477682b017093b459243e1d16cfb291d1ea2cc928b191bab51fa5081d7", "state": "open", "title_hash": "sha256:2da925217cd61f845a4b03527fcecd56d0b036e777c23dfc515b7d55840fd76b", "updated_at": "2026-06-11T00:14:03+02:00" }, "top_caveat": "Define bypass, recovery, audit, and exact action classes before any gate can block production operations." } <!-- /openclaw.judge.v0 --> ``` </details>
Sign in to join this conversation.
No labels
W6d-automerge-calibration
agent/claude-code
agent/codex
agent/hermes
agent/iskra
agent/ollama
agent/patchwarden
automerge-candidate
class/security-sensitive
cutover-gate
dependency/blocked
dependency/blocks-others
dependency/cross-repo
dependency/needs-confirmation
domain:agents
domain:ci
domain:docs
domain:forgejo
domain:infra
domain:memory
domain:runtime
domain:signal
domain:ux
flow/architecture
flow/blocked
flow/deployed
flow/done
flow/implementation
flow/intake
flow/maintained
flow/observed
flow/ready
flow/refining
flow/retired
flow/review
iterating
judge/codex-candidate
judge/hermes-candidate
judge/low-confidence
judge/needs-refinement
judge/operator-needed
judge/p0
judge/p1
judge/p2
judge/p3
judge/park
judge/patchwarden-candidate
judge/stale-priority
kind/adr
kind/bug
kind/chore
kind/feature
kind/infra
kind/ops
kind/refactor
kind/research
large-impact
merge/auto
merge/manual
merge/manual-dependency-conflict
merge/manual-failing-tests
merge/manual-merge-conflict
merge/manual-missing-review
merge/manual-operator-preference
merge/manual-red-zone
merge/manual-security-sensitive
merge/manual-unclear-scope
merge/manual-unknown
meta
mode:operator-only
mode:patchwarden-iskra-approved
mode:safe-auto
needs-operator-decision
needs-triage
not-ready
observed/erroring
observed/needs-followup
observed/pending
observed/retire-candidate
observed/unused
observed/used
operator-emotional
owner-attention
phase/02
phase/03
priority:p0
priority:p1
priority:p2
priority:p3
proposed
ready-for-agent
ready-for-operator
recovery
review:claude-reviewed
review:codex-reviewed
review:dziadek-reviewed
review:needs-human
risk/exposure
risk/process
risk/product
risk/runtime
safety:external-write
safety:no-prod-mutation
safety:prod-impact
safety:secret-touch
size/large
size/medium
size/small
size/tiny
size/unknown
source/adr
source/agent-generated
source/manual
source/operator-chat
source/voice-note
status:blocked
status:codex-ready
status:merged:pending-evidence
status:needs-evidence
status:operator-needed
status:parked
tier/full
tier/lite
tier/stacked
tier:0-platform-substrate
tier:1-iskra-value-layer
tier:2-tools-products-modules
type:bug
type:chore
type:docs
type:feat
type:policy
type:research
No milestone
No project
No assignees
4 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
pdurlej/platform#567
No description provided.