Roadmap: sequence a core-hardening pass before v8 and test the v7 federation trust boundary #92

Closed
opened 2026-07-15 19:50:00 +00:00 by claude-bot · 1 comment
Contributor

Severity: Medium (strategic) · Confidence: Medium · Category: architecture - roadmap note

Context
The spec frames Tea Leaves as "a personal research tool" for one researcher (docs/Tea Leaves - spec.md:6-8). v7-v9 build multi-instance / multi-user / public-facing infrastructure: ActivityPub federation with signed cross-instance writes (v7, shipped), public anonymous comments + moderation (v8), and social-media monitoring with OAuth (v9).

Assessment
Each is individually reasonable, but together they pull effort and attack surface toward speculative federation/social features while concrete high-impact core gaps sit unaddressed: mobile nav breakage (F-21), broken PWA install (F-20), silent capture data loss (F-22), broken restore (F-07), dead semantic-dedup (F-15). The security findings (F-01-F-03) are all on the newest perimeter - which is also the least tested (no signature-verify unit tests).

Recommendation
Insert this v7.1.0 - Core hardening & security milestone before v8, then harden and add signature-verify tests to the v7 federation boundary (F-02, F-03) before building v8's moderation layer on top of it. This is a sequencing decision, not a defect; the point is to fix daily-use and recovery paths and test the trust boundary before adding more perimeter.

Acceptance criteria

  • The v7.1.0 hardening milestone is completed (or consciously deprioritized) before v8 implementation begins.
  • Signature-verification has direct unit tests (valid / replayed / tampered-digest / wrong-actor).

Filed from the 2026-07-15 codebase audit. Full report: docs/.internal/report-2026-07-15.md (gitignored).

**Severity:** Medium (strategic) · **Confidence:** Medium · Category: architecture - roadmap note **Context** The spec frames Tea Leaves as "a personal research tool" for one researcher (`docs/Tea Leaves - spec.md:6-8`). v7-v9 build multi-instance / multi-user / public-facing infrastructure: ActivityPub federation with signed cross-instance writes (v7, shipped), public anonymous comments + moderation (v8), and social-media monitoring with OAuth (v9). **Assessment** Each is individually reasonable, but together they pull effort and attack surface toward speculative federation/social features while concrete high-impact core gaps sit unaddressed: mobile nav breakage (F-21), broken PWA install (F-20), silent capture data loss (F-22), broken restore (F-07), dead semantic-dedup (F-15). The security findings (F-01-F-03) are all on the newest perimeter - which is also the least tested (no signature-verify unit tests). **Recommendation** Insert this **v7.1.0 - Core hardening & security** milestone before v8, then harden and add signature-verify tests to the v7 federation boundary (F-02, F-03) before building v8's moderation layer on top of it. This is a sequencing decision, not a defect; the point is to fix daily-use and recovery paths and test the trust boundary before adding more perimeter. **Acceptance criteria** - [ ] The v7.1.0 hardening milestone is completed (or consciously deprioritized) before v8 implementation begins. - [ ] Signature-verification has direct unit tests (valid / replayed / tampered-digest / wrong-actor). --- _Filed from the 2026-07-15 codebase audit. Full report: `docs/.internal/report-2026-07-15.md` (gitignored)._
Author
Contributor

Done — the v7.1.0 milestone this issue proposed is complete (13/13), landed before any v8 work started.

Acceptance criteria:

  • The v7.1.0 hardening milestone is completed before v8 implementation begins — all 13 issues closed; no v8 code was written.
  • Signature-verification has direct unit tests (valid / replayed / tampered-digest / wrong-actor) — api/src/test/unit/ssrfGuard.test.ts and api/src/test/integration/apSignature.test.ts, which covers a correctly signed request, a replayed one (stale date), a tampered body (digest mismatch), an unsigned digest header, a signature from the wrong key, and the wrong-actor binding end-to-end through the inbox route (a Like signed by alice claiming actor: mallory → 401, with a matching-actor control returning 202 so the 401 is provably the binding).

Shipped, in the recommended order:

  1. Quick unblockers#83 proxy body limit, #88 PWA icons, #80 admin-gate backups.
  2. Federation trust boundary#82 signer↔actor binding on all activity types, #81 SSRF guard, plus the signature tests above.
  3. Recovery & lifecycle#87 migration ordering, #84 restore INSERT, #85 graceful shutdown, #86 spawn timeouts + stale reconciliation.
  4. Mobile daily-use#90 voice-note data loss, #89 More overflow nav, #91 deep-link returnTo.

The audit's core concerns, addressed: every daily-use gap it named (mobile nav F-21, PWA install F-20, silent capture data loss F-22, broken restore F-07) is fixed, and the newest perimeter is now the tested one.

Three findings the audit under-called, worth recording:

  • #90 was total, not conditional. savedEntryId was always null while the voice panel was open, so every Quick Capture voice note was discarded — which also invalidated the suggested "disable the mic pre-save" fix (it would have disabled the feature permanently). Took the auto-create-draft route instead.
  • #85's handler was inert as written. CMD ["npm", "start"] meant npm, not node, received SIGTERM — the shutdown handler never ran (exit 1, npm error signal SIGTERM). Only caught by actually sending a SIGTERM on the dev server. Needed CMD ["node", …] + init: true.
  • #86 had a second hang path the issue didn't name: no 'error' handler on the spawns, so a missing restic/pg_dump binary left the promise pending forever.

Deliberate deviation on #81: admin-federated (enabled) origins bypass the private-range denylist, otherwise two homelab instances federating over a private/Tailscale address would break. Mirrors the #62 outbound allowlist; unauthenticated attackers aren't in that table.

Still open (not in this milestone): F-15 dead semantic-dedup is #99 (v7.2.0). The rest of the audit's Medium/Low findings sit in v7.2.0 / v7.3.0 — v8 is now unblocked.

Verified throughout on the dev server (api 238/238, app 63/63, tsc clean, live SIGTERM/icon/open-redirect smoke tests). CI green on 4f8e0fe.

Done — the v7.1.0 milestone this issue proposed is complete (13/13), landed before any v8 work started. **Acceptance criteria:** - [x] The v7.1.0 hardening milestone is completed before v8 implementation begins — all 13 issues closed; no v8 code was written. - [x] Signature-verification has direct unit tests (valid / replayed / tampered-digest / wrong-actor) — `api/src/test/unit/ssrfGuard.test.ts` and `api/src/test/integration/apSignature.test.ts`, which covers a correctly signed request, a replayed one (stale date), a tampered body (digest mismatch), an unsigned digest header, a signature from the wrong key, **and** the wrong-actor binding end-to-end through the inbox route (a Like signed by `alice` claiming `actor: mallory` → 401, with a matching-actor control returning 202 so the 401 is provably the binding). **Shipped, in the recommended order:** 1. **Quick unblockers** — #83 proxy body limit, #88 PWA icons, #80 admin-gate backups. 2. **Federation trust boundary** — #82 signer↔actor binding on all activity types, #81 SSRF guard, plus the signature tests above. 3. **Recovery & lifecycle** — #87 migration ordering, #84 restore INSERT, #85 graceful shutdown, #86 spawn timeouts + stale reconciliation. 4. **Mobile daily-use** — #90 voice-note data loss, #89 More overflow nav, #91 deep-link `returnTo`. **The audit's core concerns, addressed:** every daily-use gap it named (mobile nav F-21, PWA install F-20, silent capture data loss F-22, broken restore F-07) is fixed, and the newest perimeter is now the tested one. **Three findings the audit under-called, worth recording:** - **#90 was total, not conditional.** `savedEntryId` was always `null` while the voice panel was open, so *every* Quick Capture voice note was discarded — which also invalidated the suggested "disable the mic pre-save" fix (it would have disabled the feature permanently). Took the auto-create-draft route instead. - **#85's handler was inert as written.** `CMD ["npm", "start"]` meant npm, not node, received SIGTERM — the shutdown handler never ran (exit 1, `npm error signal SIGTERM`). Only caught by actually sending a SIGTERM on the dev server. Needed `CMD ["node", …]` + `init: true`. - **#86 had a second hang path** the issue didn't name: no `'error'` handler on the spawns, so a missing `restic`/`pg_dump` binary left the promise pending forever. **Deliberate deviation on #81:** admin-federated (enabled) origins bypass the private-range denylist, otherwise two homelab instances federating over a private/Tailscale address would break. Mirrors the #62 outbound allowlist; unauthenticated attackers aren't in that table. **Still open (not in this milestone):** F-15 dead semantic-dedup is #99 (v7.2.0). The rest of the audit's Medium/Low findings sit in v7.2.0 / v7.3.0 — **v8 is now unblocked.** Verified throughout on the dev server (api 238/238, app 63/63, tsc clean, live SIGTERM/icon/open-redirect smoke tests). CI green on `4f8e0fe`.
Sign in to join this conversation.
No project
No assignees
1 participant
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
rbrooks/TeaLeaves#92
No description provided.