fix(federation): security hardening — OID verification, atomic activation, audit on failure #501

Merged
jason.woltje merged 2 commits from fix/federation-m2-security into main 2026-04-22 06:02:53 +00:00
Owner

Summary

Security remediation for 6 findings from the M2 independent review. All changes are in apps/gateway/src/federation/.

  • CRIT-1: Added post-issuance OID verification in CaService.issueCert() — parses the returned cert with @peculiar/x509 and validates that OID 1.3.6.1.4.1.99999.1 (mosaic_grant_id) and 1.3.6.1.4.1.99999.2 (mosaic_subject_user_id) are present and match the request values. Based on infra/step-ca/templates/federation.tpl which uses ASN.1 UTF8String TLV encoding (0x0C + 1-byte length + UUID bytes). Throws CaServiceError on mismatch or absence.
  • CRIT-2: Added WHERE status=pending guard on the grant activation UPDATE in redeem() transaction using .returning() to detect no-op. Throws ConflictException if the grant was already activated. Also added WHERE state=pending guard on the federationPeers UPDATE.
  • HIGH-2: Removed 90-day silent fallback in extractCertNotAfter() — an unparseable cert now propagates as a 500 error rather than silently setting a wrong expiry date.
  • HIGH-4: Enrollment token is now logged with only its first 8 hex chars for correlation instead of the full 64-char plaintext token.
  • HIGH-5: Wrapped the redeem() body in try/catch; writes a best-effort failure audit row (outside transaction, .catch guarded) on any error path so all enrollment attempts are recorded in federation_audit_log regardless of success or failure.
  • MED-3: Added grant<->peer binding verification in createToken() — queries federationGrants to confirm the grantId peer matches dto.peerId before issuing a token, preventing cross-wiring attacks.

Test plan

  • Typecheck: pnpm typecheck passes (38/38 tasks successful)
  • Lint: pnpm lint passes
  • Format: pnpm format:check passes
  • No regressions: same 398 tests pass, same 5 pre-existing suite failures remain (jose/@peculiar/x509 not loaded in vitest context)
  • Woodpecker CI pipeline expected green

Closes #461

## Summary Security remediation for 6 findings from the M2 independent review. All changes are in apps/gateway/src/federation/. - **CRIT-1**: Added post-issuance OID verification in CaService.issueCert() — parses the returned cert with @peculiar/x509 and validates that OID 1.3.6.1.4.1.99999.1 (mosaic_grant_id) and 1.3.6.1.4.1.99999.2 (mosaic_subject_user_id) are present and match the request values. Based on infra/step-ca/templates/federation.tpl which uses ASN.1 UTF8String TLV encoding (0x0C + 1-byte length + UUID bytes). Throws CaServiceError on mismatch or absence. - **CRIT-2**: Added WHERE status=pending guard on the grant activation UPDATE in redeem() transaction using .returning() to detect no-op. Throws ConflictException if the grant was already activated. Also added WHERE state=pending guard on the federationPeers UPDATE. - **HIGH-2**: Removed 90-day silent fallback in extractCertNotAfter() — an unparseable cert now propagates as a 500 error rather than silently setting a wrong expiry date. - **HIGH-4**: Enrollment token is now logged with only its first 8 hex chars for correlation instead of the full 64-char plaintext token. - **HIGH-5**: Wrapped the redeem() body in try/catch; writes a best-effort failure audit row (outside transaction, .catch guarded) on any error path so all enrollment attempts are recorded in federation_audit_log regardless of success or failure. - **MED-3**: Added grant<->peer binding verification in createToken() — queries federationGrants to confirm the grantId peer matches dto.peerId before issuing a token, preventing cross-wiring attacks. ## Test plan - Typecheck: pnpm typecheck passes (38/38 tasks successful) - Lint: pnpm lint passes - Format: pnpm format:check passes - No regressions: same 398 tests pass, same 5 pre-existing suite failures remain (jose/@peculiar/x509 not loaded in vitest context) - Woodpecker CI pipeline expected green Closes #461
jason.woltje added 1 commit 2026-04-22 05:54:40 +00:00
fix(federation): security hardening — OID verification, atomic activation, audit on failure
Some checks failed
ci/woodpecker/push/ci Pipeline failed
ci/woodpecker/pr/ci Pipeline failed
55c870f421
CRIT-1: Add post-issuance OID verification in CaService.issueCert() — parses
the returned cert with @peculiar/x509 and validates that OIDs 1.3.6.1.4.1.99999.1
(mosaic_grant_id) and 1.3.6.1.4.1.99999.2 (mosaic_subject_user_id) are present
and match the request values. Throws CaServiceError on mismatch or absence.

CRIT-2: Guard grant activation in the redeem() transaction with
WHERE status='pending' (RETURNING to detect no-op). Throw ConflictException
if the grant was already activated. Also add WHERE state='pending' guard on
the federationPeers UPDATE.

HIGH-2: Remove 90-day silent fallback in extractCertNotAfter() — an unparseable
cert now propagates as a 500 error rather than silently setting a wrong expiry.

HIGH-4: Log only the first 8 hex chars of the enrollment token in the issueCert
failure error log — never log the full 64-char token.

HIGH-5: Wrap redeem() body in try/catch; write a best-effort failure audit row
(outside transaction, .catch(() => {}) guarded) on any error path so all
enrollment attempts are audited regardless of outcome.

MED-3: Verify grantId ↔ peerId binding in createToken() before inserting the
token — prevents cross-wiring a grant to an attacker-controlled peer.

Closes #461

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
jason.woltje added 1 commit 2026-04-22 06:02:41 +00:00
fix(federation): use null fallback for audit log FK cols when token row missing
Some checks failed
ci/woodpecker/pr/ci Pipeline failed
ci/woodpecker/push/ci Pipeline failed
9b718d3e06
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
jason.woltje merged commit fc1600b738 into main 2026-04-22 06:02:53 +00:00
Sign in to join this conversation.
No Reviewers
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: mosaicstack/stack#501