Skip to content

feat(security): wire SecurityAuditService into comms routes - #876

Merged
2witstudios merged 9 commits into
masterfrom
pu/comms-auditor
Apr 11, 2026
Merged

2witstudios merged 9 commits into
masterfrom
pu/comms-auditor

Conversation

@2witstudios

@2witstudios 2witstudios commented Apr 10, 2026 •

Copy link
Copy Markdown
Owner

Summary

  • Wire securityAudit.logDataAccess and logTokenCreated into 12 communication route files (channels, messages, notifications, inbox)
  • 18 total audit calls covering read, write, and delete operations across all comms routes
  • Uses fire-and-forget .catch() pattern consistent with existing audit wiring in auth/export routes
  • GDPR-safe: uses 'self' as resourceId for list/self endpoints to avoid leaking userId into hash-protected fields
  • Added logTokenRevoked audit for push-token DELETE (token revocation)

Routes Covered

Domain Routes Audit Calls
Channels messages, reactions, upload 5
Messages DMs, conversations, threads 7
Notifications list, delete, push-tokens (create+revoke), unsubscribe 5
Inbox unified inbox 1

GDPR Compliance

  • resourceId is included in the tamper-evident hash chain and cannot be anonymized under right-to-erasure
  • All list/self endpoints use 'self' instead of userId as resourceId
  • 2 new tests document this invariant in security-audit.test.ts

Test plan

  • pnpm typecheck passes
  • All 74 audit tests pass (5 test files)
  • No functional changes — all audit calls are fire-and-forget, non-blocking
  • Imports resolve to @pagespace/lib/server
  • Review feedback addressed (inbox audit timing, push-token DELETE gap, GDPR resourceId fix)

🤖 Generated with Claude Code

2witstudios and others added 4 commits April 10, 2026 17:01
Add audit logging for channel messages (read/write), reactions
(write/delete), and file uploads (write).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add audit logging for DM messages (read/write/mark_read), conversations
(read/write), and message threads (read).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add audit logging for notifications (read/delete), push token
creation (logTokenCreated), and email unsubscribe preferences (write).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add audit logging for inbox read access.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@vercel

vercel Bot commented Apr 10, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
pagespace-master-plan Ready Ready Preview, Comment Apr 11, 2026 1:45am

@coderabbitai

coderabbitai Bot commented Apr 10, 2026 •

Copy link
Copy Markdown
Contributor

Warning

Rate limit exceeded

@2witstudios has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 4 minutes and 28 seconds before requesting another review.

Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 4 minutes and 28 seconds.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 9aa5fd9e-2d67-4904-909e-f926dd9960d3

📥 Commits

Reviewing files that changed from the base of the PR and between 386acdb and 6968fcd.

📒 Files selected for processing (7)
  • apps/web/src/app/api/inbox/route.ts
  • apps/web/src/app/api/messages/conversations/route.ts
  • apps/web/src/app/api/messages/threads/route.ts
  • apps/web/src/app/api/notifications/push-tokens/route.ts
  • apps/web/src/app/api/notifications/route.ts
  • apps/web/src/app/api/notifications/unsubscribe/[token]/route.ts
  • packages/lib/src/audit/__tests__/security-audit.test.ts
📝 Walkthrough

Walkthrough

This pull request adds non-blocking audit logging to 11 API route handlers across channels, inbox, messages, and notifications services. Each handler now imports securityAudit and invokes audit calls (e.g., logDataAccess, logTokenCreated) after successful operations, with failures caught and logged via loggers.security.warn without affecting response flow.

Changes

Cohort / File(s) Summary
Channels audit logging
apps/web/src/app/api/channels/[pageId]/messages/[messageId]/reactions/route.ts, apps/web/src/app/api/channels/[pageId]/messages/route.ts, apps/web/src/app/api/channels/[pageId]/upload/route.ts
Added securityAudit.logDataAccess() calls to POST/DELETE/GET handlers for reactions, messages, and uploads; all audit calls are non-blocking with error handling via .catch().
Inbox audit logging
apps/web/src/app/api/inbox/route.ts
Added securityAudit.logDataAccess() call in GET handler after authentication; non-blocking with error handling.
Messages audit logging
apps/web/src/app/api/messages/[conversationId]/route.ts, apps/web/src/app/api/messages/conversations/[conversationId]/route.ts, apps/web/src/app/api/messages/conversations/route.ts, apps/web/src/app/api/messages/threads/route.ts
Added securityAudit.logDataAccess() calls to GET/POST/PATCH handlers for message conversations and threads; all non-blocking with error handling.
Notifications audit logging
apps/web/src/app/api/notifications/[id]/route.ts, apps/web/src/app/api/notifications/push-tokens/route.ts, apps/web/src/app/api/notifications/route.ts, apps/web/src/app/api/notifications/unsubscribe/[token]/route.ts
Added securityAudit.logDataAccess() and securityAudit.logTokenCreated() calls to DELETE/POST/GET handlers; all non-blocking with error handling via .catch().

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~12 minutes

Possibly related PRs

  • PR #869: Wires securityAudit into server API routes with similar fire-and-forget audit call patterns and error handling.
  • PR #871: Related audit call error handling changes, using loggers.security.warn() for audit failures.
  • PR #724: Introduces and exports the securityAudit service and audit helper methods that this PR imports and invokes.

Poem

🐰 Through digital pathways, I hop with glee,
Logging each action for all to see!
With audit trails woven through every request,
Security and trust are now truly blessed! ✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 70.59% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately captures the main objective: wiring SecurityAuditService into communication routes across 12 files with audit logging for channels, messages, notifications, and inbox operations.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch pu/comms-auditor

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
apps/web/src/app/api/inbox/route.ts (1)

19-21: Move this audit emission to successful response paths.

Right now this runs before data retrieval, so failed requests can still be logged as successful inbox reads. Prefer emitting right before each successful NextResponse.json(...) return.

♻️ Proposed adjustment
-    securityAudit.logDataAccess(userId, 'read', 'inbox', userId).catch((error) => {
-      loggers.security.warn('[Inbox] audit log failed', { error: error instanceof Error ? error.message : String(error), userId });
-    });
...
-      return NextResponse.json({
+      securityAudit.logDataAccess(userId, 'read', 'inbox', userId).catch((error) => {
+        loggers.security.warn('[Inbox] audit log failed', { error: error instanceof Error ? error.message : String(error), userId });
+      });
+      return NextResponse.json({
         items: paginatedItems,
         pagination: {
           hasMore,
           nextCursor,
         },
       } satisfies InboxResponse);
...
-    return NextResponse.json({
+    securityAudit.logDataAccess(userId, 'read', 'inbox', userId).catch((error) => {
+      loggers.security.warn('[Inbox] audit log failed', { error: error instanceof Error ? error.message : String(error), userId });
+    });
+    return NextResponse.json({
       items: paginatedItems,
       pagination: {
         hasMore,
         nextCursor,
       },
     } satisfies InboxResponse);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web/src/app/api/inbox/route.ts` around lines 19 - 21, The current
securityAudit.logDataAccess(userId, 'read', 'inbox', userId) call must be moved
out of the pre-fetch area and invoked immediately before each successful
NextResponse.json(...) return so only successful inbox reads are audited; remove
the existing early call, and in the handler(s) that return NextResponse.json
(e.g., the GET/default export), call securityAudit.logDataAccess(...) right
before constructing the successful NextResponse.json response and chain
.catch(err => loggers.security.warn('[Inbox] audit log failed', { error: err
instanceof Error ? err.message : String(err), userId })) so failures in logging
are still non-blocking.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/web/src/app/api/messages/conversations/route.ts`:
- Around line 236-238: The successful early return that returns
existingConversation bypasses the audit call; ensure every successful branch
logs the write to the conversation audit. Add a
securityAudit.logDataAccess(userId, 'write', 'conversation',
existingConversation.id) (matching the existing newConversation call) before
returning existingConversation in the code path that currently returns early, or
consolidate audit logging so both branches (existingConversation and
newConversation) invoke securityAudit.logDataAccess and handle errors the same
way (using loggers.security.warn).

---

Nitpick comments:
In `@apps/web/src/app/api/inbox/route.ts`:
- Around line 19-21: The current securityAudit.logDataAccess(userId, 'read',
'inbox', userId) call must be moved out of the pre-fetch area and invoked
immediately before each successful NextResponse.json(...) return so only
successful inbox reads are audited; remove the existing early call, and in the
handler(s) that return NextResponse.json (e.g., the GET/default export), call
securityAudit.logDataAccess(...) right before constructing the successful
NextResponse.json response and chain .catch(err =>
loggers.security.warn('[Inbox] audit log failed', { error: err instanceof Error
? err.message : String(err), userId })) so failures in logging are still
non-blocking.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: b33bee88-a525-4ea4-8826-f0f5e91a4ad9

📥 Commits

Reviewing files that changed from the base of the PR and between 76f3af9 and 386acdb.

📒 Files selected for processing (12)
  • apps/web/src/app/api/channels/[pageId]/messages/[messageId]/reactions/route.ts
  • apps/web/src/app/api/channels/[pageId]/messages/route.ts
  • apps/web/src/app/api/channels/[pageId]/upload/route.ts
  • apps/web/src/app/api/inbox/route.ts
  • apps/web/src/app/api/messages/[conversationId]/route.ts
  • apps/web/src/app/api/messages/conversations/[conversationId]/route.ts
  • apps/web/src/app/api/messages/conversations/route.ts
  • apps/web/src/app/api/messages/threads/route.ts
  • apps/web/src/app/api/notifications/[id]/route.ts
  • apps/web/src/app/api/notifications/push-tokens/route.ts
  • apps/web/src/app/api/notifications/route.ts
  • apps/web/src/app/api/notifications/unsubscribe/[token]/route.ts

Comment thread apps/web/src/app/api/messages/conversations/route.ts

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 386acdbb5d

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

? `${page[0].createdAt.toISOString()}|${page[0].id}`
: null;

securityAudit.logDataAccess(userId, 'read', 'channel_message', pageId).catch((error) => {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Avoid serial audit write on hot message-read path

This new call makes every GET /api/channels/[pageId]/messages request enqueue a security-audit DB write, and SecurityAuditService.logEvent() serializes all writes behind a global pg_advisory_xact_lock transaction (packages/lib/src/audit/security-audit.ts). On high-traffic channel reads (initial loads, reconnections, polling bursts), this creates a lock queue and shared pool pressure that can spill over into user-facing query latency/timeouts, so this read endpoint should not directly emit per-request chain-locked writes.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Acknowledged. The advisory lock serialization is a pre-existing architectural property of SecurityAuditService.logEvent(), not introduced by this PR. Since the audit call is fire-and-forget (.catch()), it won't affect response latency — the advisory lock queue only affects audit write throughput, not the user-facing request path.

For high-volume read endpoints, a future optimization could batch audit writes or use a write-behind queue, but that's out of scope for this wiring PR. The current pattern matches how audit calls are wired in all other routes (auth, export, account, drives, pages).

Log a read audit event when POST /conversations returns
an existing conversation instead of creating a new one.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace userId with 'self' in 5 audit calls where userId was passed as
resourceId. Since resourceId is included in the hash chain computation,
it cannot be anonymized under GDPR right-to-erasure without breaking
tamper-evident integrity. The userId column (excluded from hash) already
records the actor. Add tests documenting the invariant.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…vocation audit

Move inbox audit call from pre-fetch to right before each successful
response, so failed requests are not logged as successful reads.
Add missing logTokenRevoked audit call to push-token DELETE handler.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@2witstudios

Copy link
Copy Markdown
Owner Author

Addressing Review Feedback

CodeRabbit nitpick: Inbox audit placement (inbox/route.ts:19)

Fixed in c8a0ac8. Moved the audit call from pre-fetch (where it would log even on failed requests) to right before each successful NextResponse.json(...) return. There are two success paths (drive-specific and dashboard), so the audit call now appears in both.

CodeRabbit: Unaudited existingConversation branch (conversations/route.ts)

Already fixed in 3f407d8 (prior commit on this branch).

Codex: Advisory lock serialization on hot read path (channels/messages/route.ts)

Acknowledged. Fire-and-forget .catch() ensures the advisory lock queue never affects response latency. The lock serialization is a pre-existing property of SecurityAuditService, consistent with how audit is wired in all other routes. Batching/write-behind optimization is out of scope for this wiring PR.

Additional fixes in this push:

  • GDPR fix (56267e8): Replaced userId with 'self' as resourceId in 5 audit calls where user ID was leaking into the hash-protected field. resourceId is included in the tamper-evident hash chain and cannot be anonymized under GDPR right-to-erasure. Added 2 tests documenting this invariant.
  • Push-token DELETE audit (c8a0ac8): Added missing logTokenRevoked call to the push-token DELETE handler — token revocation is security-sensitive and was unaudited.

@2witstudios
2witstudios merged commit c9c12aa into master Apr 11, 2026
5 checks passed
@2witstudios
2witstudios deleted the pu/comms-auditor branch April 11, 2026 11:35

This branch was previously deployed

1 inactive deployment
Preview — 6968fcdc Deployed Apr 11, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant