Ship safe-op denylist for URL-type credential items (5th leak in 5 weeks; bare `op item get <uuid>` shape not yet blocked)
completedAgent: sergey-engineer
Priority: 1
Context: 5th credential leak in 5 weeks self-reported 2026-06-16T16:06Z. Pattern: `op item get <uuid>` with NO flags (no --reveal, no --format json) renders URL-type fields (TigerData `psql url`, similar Neon/Snowflake URI fields) in plaintext by default. MEMORY.md already warns about this; AGENTS.md notes it; the existing `~/bin/safe-op` wrapper does not block it because the call shape contains no banned flag — the leak is structural to the field-type, not the call-shape.
The 6/12 meta-rule says: any anti-pattern in the credential/secret family that has happened ≥3 times MUST file a structural-fix fleet-task in the same reflection as the self-report. This is leak #5. The fix model is mechanical, not "remember the warning."
Subtasks (atomic, each ≤30 min, heartbeat-pickable per 6/5 decomposition):
1. ENUMERATE flagged items (≤5min): build the deny-list. Items known to contain URL-type credential fields that render plaintext on bare `op item get <uuid>`. Seed list from MEMORY.md UUID table + the two known leak items:
- 5spgf22s24vopai2i5incj6evq (TigerData read replica — confirmed today)
- any Neon items in MEMORY.md (2x) — verify field-types
- any AWS Postgres items — already covered structurally because field is "password" (concealed by default, not URL-type), but include for belt-and-suspenders
- Doppler personal token — concealed (not URL-type)
Final deny-list: UUIDs known to contain URL-category fields.
2. PATCH `~/bin/safe-op` (≤15min): add a new rejection rule. If argv matches `item get <uuid>` (no `--fields` flag, no `--reveal`, no `--format`) AND the uuid is in the deny-list, EXIT 78 with message "BLOCKED: <uuid> contains URL-type field that renders plaintext on bare get. Use `op item get <uuid> --fields <safe-field>` or `op read 'op://...'` with explicit field."
3. AUDIT-LOG schema (≤5min): the new BLOCKED case logs with reason=URL_FIELD_DENYLIST so I can distinguish it from --format-json/--reveal blocks in future audit-log reviews.
4. SELF-TESTS (≤10min):
- `safe-op item get 5spgf22s24vopai2i5incj6evq` → BLOCKED exit 78 with URL_FIELD_DENYLIST reason
- `safe-op item get 5spgf22s24vopai2i5incj6evq --fields hostname` → reaches real op (auth or value, not wrapper rejection)
- `safe-op item get <uuid-NOT-in-denylist>` → reaches real op (ALLOWED path, unchanged behavior)
- `safe-op --version` → ALLOWED, exits cleanly, no recursion (regression test for the 6/12 exec-loop bug)
5. UPDATE AGENTS.md `🔐 Secret Handling` section (≤10min): add the new denylist mechanism description. Reference this fleet-task by ID. Add the 5/16 leak entry to the leak table.
6. UPDATE MEMORY.md UUID table (≤5min): mark URL-type-bearing items with a `⚠️ DENYLISTED` flag so future-me sees the wrapper-coverage status at a glance.
7. UPDATE LEARNINGS.md: tonight's reflection (2026-06-16) appends the 5th-leak entry + the meta-rule's second clean application (6/11 → 6/16).
8. CROSS-LINK: LEARNINGS ↔ AGENTS ↔ MEMORY ↔ `~/bin/safe-op` ↔ this fleet-task ID.
NOT in scope: rotating the leaked TigerData credential — Sergey owns the rotation. Self-report already sent 16:06Z. I owe structural fix.
SOFT DEADLINE: 2026-06-16 reflection (~12h from filing). Same-day shipping per the meta-rule.
Event Timeline
created
status_change
queued → in_progress
failed
lease expired — re-queued for retry
in_progress → queued
status_change
queued → completed