I read this as a read-only security audit of the first-claim invite flow - I’ll trace claim, persistence, migration, review, and activation paths, then report exploitable blockers and minimal fixes without modifying data or sending messages.


The claim ledger itself has atomic CAS and tamper-evident persistence; I’m now tracing the downstream registration and owner-review producers because capacity, duplicate identity, placeholder activation, and receipt authenticity are enforced outside the token claim method.


The audit has found a real activation-authority gap: the normal Telegram path authenticates the owner callback, but activation does not require that authenticated receipt, and a separate shipped low-level activation command bypasses the bootstrap cutover entirely. I’m validating the exact reachability and separating those blockers from fail-closed race/replay controls.


I read this as a completed read-only security audit - I traced the full invite-to-activation path and verified the relevant tests.

## Verdict

**FAIL**
**blocker_count: 3**

### Blockers

1. **Owner review is not cryptographically required**
   - `validate_first_claim_owner_review()` accepts matching arbitrary 64-character strings; it never validates the authenticated Telegram owner callback stored in the publication outbox (`dualcoach_activation_cutover.py:310-349`).
   - Existing test explicitly proves `"a" * 64` unlocks this gate (`test_new_customer_consent_authority.py:137-172`).
   - **Exploit:** create/recompute digest-valid onboarding artifacts with an invented owner receipt, then invoke cutover. The HMAC-authenticated callback receipt available through `GatewayOnboardingPublicationOutbox.owner_callback()` is never consulted.
   - **Smallest fix:** require the exact HMAC-authenticated owner callback/publication receipt, bound to session ID, customer key, owner identity, route, callback action, generation, and finalized baseline digest.

2. **Shipped direct activation bypasses bootstrap cutover**
   - `python -m checkin_cli.customer_admin ... activate` directly calls `activate_customer()` (`customer_admin.py:7417-7460`, `7557-7565`).
   - This bypasses bootstrap preflight, first-claim review validation, Task26 authority, bootstrap `ACTIVE` transition, and activation-notice reservation.
   - **Exploit:** after producing ordinary readiness/checklist/membership evidence, invoke this low-level command. It enables the registry and writes committed activation authority while the bootstrap can remain `AWAITING_ACTIVATION`.
   - **Smallest fix:** remove the direct activation command and require an unforgeable cutover capability at the `activate_customer()` boundary; only `dualcoach_activation_cutover` may supply it.

3. **Bootstrap integrity is hash-only and permits authority rewriting**
   - Ledger, journal, head, and marker use public SHA-256 digests, not a MAC or external immutable anchor.
   - **Exploit:** a profile-writer can rewrite journal snapshots changing `first_claim` to `false`, recompute the chain/head/ledger digests, and restart. Activation then skips owner review at `dualcoach_activation_cutover.py:315`.
   - Legacy ledger-only migration also accepts missing `first_claim` as `false` (`telegram_customer_bootstrap.py:1588-1595`, `1694`) and blesses the snapshot on the next mutation.
   - **Smallest fix:** authenticate bootstrap state with a key outside the writable profile boundary or an external append-only authority; explicitly reject missing `first_claim` for any post-introduction or ambiguous lifecycle.

## Threat results

| Threat | Result | Notes |
|---|---|---|
| Forwarded token | PASS for escalation, residual DoS | First-claim is intentionally bearer-based: a forwarded recipient can consume it and create a disabled placeholder, but should not activate without owner review. Blockers above undermine that final guarantee. |
| Concurrent claim | PASS | Exclusive file lock, generation CAS, and re-read under lock yield exactly one winner. |
| Owner/bot/group claim | PASS | Owner rejected in store; bots, sender chats, groups, and non-DM identity mismatches rejected by Telegram handler. |
| Duplicate Telegram identity | PASS for activation | Registry uniqueness rejects duplicate address/space atomically, though the invite is already consumed. |
| Capacity race | PASS for enabled capacity | Issuance does not reserve capacity, so multiple disabled drafts can exist, but locked activation rechecks capacity. |
| Replay/restart | PASS | Claim is persisted before registration; restart resumes `REGISTERING` and `AWAITING_CONSENT`; replay is rejected. |
| Legacy bootstrap migration | FAIL under tampering | Ordinary genuine legacy rows default safely, but unauthenticated ledger migration enables downgrade as described above. |
| Forged `first_claim` flag | FAIL | Blocker 3. |
| Placeholder draft activation | PASS through canonical path | Registration remains disabled; readiness finalization projects reviewed profile/plan before activation validation. |
| Missing/tampered owner receipts | FAIL | Missing/simple mismatch fails, but recomputed or invented receipts pass without authenticated callback evidence. |
| Activation bypass | FAIL | Blocker 2. |

Verification: **27 relevant tests passed** with the correct profile package path. No repository files were modified and no messages were sent.


I read this as a focused post-fix security re-review under the established owner-only filesystem threat model - I’ll inspect the new owner-callback gate, removed CLI surface, and regression tests, then return only the verdict, blocker count, and any exact remaining blocker.


I read this as a post-fix verdict - reassessed under the accepted owner-only filesystem model.

**PASS**
**blocker_count: 0**
**exact remaining blocker: none**