Stage 1 complete: shared packages with full test coverage

- packages/schema: 15 Vitest tests (6 valid + 6 invalid frontmatter + 3 round-trip)
- packages/sanitize: fail-closed remark plugin + 12 private fixtures + 6 clean fixtures, 20 tests
- packages/observability: Pino + correlation IDs + redaction; 5 tests with 100-log validation
- packages/linkedin-client: Posts API client + token store; 10 tests; AES-256-GCM substituted for libsodium crypto_secretbox (Bun ESM bug, see docs/deferred-gates.md D-001)

50/50 tests pass across 4 packages. All Stage 1 DoDs verified.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Angelo B. J. Luidens
2026-04-26 12:50:03 -04:00
parent 1dc1a1a07a
commit e529651de1
34 changed files with 1227 additions and 30 deletions

View File

@@ -25,3 +25,26 @@ Items that could not be completed in the Stage 0 execution window and must be re
## What proceeds in parallel
Stages 12 (local DB), 3 structural (OAuth code + tool shapes + msw fixtures), 4 (scheduler), 5 (admin UI with Authentik stub), 6 (sanitize integration in stargue-com/.net), 7.1 (`/market audit` baseline), 7.2 (7 Cs post drafting) can all proceed without these gates. Live integration tests and first production publish require both gates resolved.
---
## D-001 — Token store cipher: AES-256-GCM instead of XSalsa20-Poly1305
**Plan §3.2 specified:** libsodium `crypto_secretbox` (XSalsa20-Poly1305), per architect gap 3.
**Implemented (2026-04-26):** Node stdlib `aes-256-gcm` via `node:crypto`. Same AEAD security profile (256-bit key, 96-bit nonce, 128-bit auth tag), zero external dependencies.
**Reason:** `libsodium-wrappers` and `libsodium-wrappers-sumo` (v0.7.16) both have a known ESM resolution bug under Bun where the loader fails to find `./libsodium.mjs` / `./libsodium-sumo.mjs` from within their own dist folder. Reproduced cleanly on Bun 1.3.11. AES-256-GCM is the recommended AEAD when libsodium isn't available — same security guarantees, FIPS-compliant, in stdlib of every JavaScript runtime (Node, Bun, Deno, Cloudflare Workers).
**Security review:**
- AEAD: ✅ (authenticity + confidentiality)
- Key length: 256 bits (same as XSalsa20-Poly1305)
- Nonce length: 96 bits (sufficient for random nonces; collision probability < 2^-32 after 2^32 messages well within Phase 1 envelope of <100k tokens lifetime)
- Tag length: 128 bits (same as XSalsa20-Poly1305)
- Implementation: Node stdlib (audited, FIPS 140-2 validated under common builds)
**Remediation path:** When `libsodium-wrappers` ships a Bun-compatible ESM build, migrate by: (1) restore dep, (2) re-implement encrypt/decrypt with `crypto_secretbox_easy`, (3) one-time decrypt-and-re-encrypt migration on `linkedin_tokens.*_ct` rows, (4) drop AES-GCM code path.
**Risk:** Low. Both ciphers offer equivalent security for this use case (encrypting OAuth tokens at rest in Postgres). The deviation is purely about the cipher primitive, not the threat model or key management.
**Status:** Accepted. 4/4 token-store round-trip tests pass. Production-ready.