Skip to content

fix(security): consolidate email regex, shell executor, CSRF token patterns#857

Merged
2witstudios merged 4 commits intomasterfrom
pu/sec-p3-consolidate
Apr 8, 2026
Merged

fix(security): consolidate email regex, shell executor, CSRF token patterns#857
2witstudios merged 4 commits intomasterfrom
pu/sec-p3-consolidate

Conversation

@2witstudios
Copy link
Copy Markdown
Owner

@2witstudios 2witstudios commented Apr 8, 2026

Summary

Three P3 nice-to-have consolidation fixes from independent QA audit. No vulnerabilities — maintenance risk reduction only.

  • F-3d/e: Shared email validator — Extract bounded-quantifier RFC 5322 regex to packages/lib/src/validators/email.ts with isValidEmail(). Replaces 4 inconsistent inline regex patterns across web, control-plane, and marketing. Adds 254-char RFC 5321 length check and TLD requirement. 12 unit tests including ReDoS resistance.
  • F-advisory: Shell executor defense-in-depth — Add execFile() method to ShellExecutor using child_process.execFile (no shell interpretation). Add slug validation to upgrade-service.ts (the only caller that didn't validate). 6 new tests.
  • F-5d: CSRF token leak in OAuth redirects — Remove unused csrfToken from Google/Apple OAuth redirect URLs. No client code reads it from the URL — CSRF tokens come from /api/auth/csrf endpoint. Eliminates exposure in browser history, server logs, and Referer headers. 5 test assertions updated.

Test plan

  • packages/lib validator tests pass (46/46) — email + id validators
  • control-plane affected tests pass (83/83) — tenant-validation, shell-executor, upgrade-service
  • web OAuth tests pass (295/295) — all Google + Apple auth test files
  • Full monorepo typecheck + build passes (12/12 tasks)
  • Verify OAuth login flow works end-to-end (Google + Apple sign-in redirects correctly without csrfToken in URL)

🤖 Generated with Claude Code

Summary by CodeRabbit

Release Notes

  • New Features

    • Added email validation utility with RFC 5322 compliance across the platform.
    • Enhanced shell command execution capabilities with dedicated method and history tracking.
    • Implemented tenant slug validation to prevent invalid configurations.
  • Bug Fixes

    • Removed CSRF tokens from OAuth callback redirects for enhanced security.
    • Added shell injection prevention for tenant upgrade operations.
  • Tests

    • Added comprehensive test coverage for validation and execution features.

…tterns (#F-3d/e, F-advisory, F-5d)

Three P3 pattern consolidation fixes from QA audit:

- Extract shared isValidEmail() to packages/lib with bounded-quantifier
  RFC 5322 regex, 254-char length check, and TLD requirement. Replace
  4 inline regex usages across web, control-plane, and marketing.

- Add execFile() method to shell-executor using child_process.execFile
  (no shell interpretation). Add slug validation to upgrade-service as
  defense-in-depth (all other callers already validate).

- Remove unused csrfToken from OAuth redirect URLs (Google + Apple
  callbacks). No client code reads it; CSRF tokens come from the
  /api/auth/csrf endpoint. Eliminates exposure in browser history,
  server logs, and Referer headers.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@chatgpt-codex-connector
Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, you can upgrade your account or add credits to your account and enable them for code reviews in your settings.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 8, 2026

Warning

Rate limit exceeded

@2witstudios has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 12 minutes and 0 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 12 minutes and 0 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: c028eab9-4a71-46d1-94df-05e9f6fd3758

📥 Commits

Reviewing files that changed from the base of the PR and between 1c2cce2 and 6fdde79.

📒 Files selected for processing (3)
  • apps/web/src/app/api/auth/apple/callback/__tests__/route.test.ts
  • apps/web/src/app/api/auth/google/__tests__/google-callback-redirect.test.ts
  • apps/web/src/app/api/auth/google/callback/__tests__/route.test.ts
📝 Walkthrough

Walkthrough

This PR adds execFile method to ShellExecutor with history tracking and timeout handling, introduces tenant slug validation in upgrade service, creates a centralized email validation utility in packages/lib, integrates it across multiple apps, and removes csrfToken from OAuth redirect URLs.

Changes

Cohort / File(s) Summary
ShellExecutor Enhancement
apps/control-plane/src/services/shell-executor.ts, apps/control-plane/src/services/__tests__/shell-executor.test.ts
Added new execFile(program, args, options?) method returning { stdout, stderr, exitCode } with timeout handling in mock executor; extended HistoryEntry with optional args field; tests verify return shape, history logging, factory behavior, and options forwarding.
Tenant Slug Validation
apps/control-plane/src/services/upgrade-service.ts, apps/control-plane/src/services/__tests__/upgrade-service.test.ts
Added validateSlug check in upgradeTenant to prevent execution with shell metacharacters; tests confirm invalid slugs fail early and valid slugs proceed normally.
Email Validation Utility
packages/lib/src/validators/email.ts, packages/lib/src/validators/__tests__/email.test.ts, packages/lib/src/validators/index.ts
Created new isValidEmail(email) function with RFC 5322-style regex (bounded quantifiers for ReDoS protection), 254-character max length, and mandatory TLD requirement; comprehensive test coverage including performance validation and edge cases.
Email Validation Integration
apps/control-plane/src/validation/tenant-validation.ts, apps/control-plane/vitest.config.ts, apps/marketing/src/app/api/contact/route.ts, apps/web/src/app/api/account/route.ts, apps/web/src/app/api/account/__tests__/get-patch-route.test.ts, apps/web/src/app/api/internal/contact/route.ts
Replaced inline email regexes with shared isValidEmail helper across multiple routes; updated test mocks to include new validator and additional helper functions; added @pagespace/lib/validators path alias to vitest config.
CSRF Token Removal from OAuth Redirects
apps/web/src/app/api/auth/apple/callback/route.ts, apps/web/src/app/api/auth/apple/callback/__tests__/route.test.ts, apps/web/src/app/api/auth/google/callback/route.ts, apps/web/src/app/api/auth/google/callback/__tests__/route.test.ts, apps/web/src/app/api/auth/google/__tests__/google-callback-redirect.test.ts, apps/web/src/app/api/auth/google/__tests__/open-redirect-protection.test.ts
Removed csrfToken query parameter from final redirect URLs in Apple and Google OAuth callbacks; updated all related test assertions to verify token is absent from redirect locations.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant ShellExecutor
    participant History as History Log
    participant ChildProcess as child_process.execFile
    
    rect rgb(100, 150, 200, 0.5)
    note over Client,ChildProcess: Real ShellExecutor Flow
    Client->>ShellExecutor: execFile(program, args, options)
    ShellExecutor->>ChildProcess: execFile(program, args)
    alt Command Completes
        ChildProcess-->>ShellExecutor: callback(error, stdout, stderr)
        ShellExecutor->>History: record {command, args, options}
        ShellExecutor-->>Client: {stdout, stderr, exitCode}
    else Command Fails
        ChildProcess-->>ShellExecutor: callback(error, stdout, stderr)
        ShellExecutor->>History: record {command, args, options}
        ShellExecutor-->>Client: {stdout, stderr, exitCode: 1}
    end
    end
    
    rect rgb(200, 100, 100, 0.5)
    note over Client,ChildProcess: Mock ShellExecutor Flow (with Timeout)
    Client->>ShellExecutor: execFile(program, args, options)
    alt Response Delay > Timeout
        ShellExecutor->>History: (no record on timeout)
        ShellExecutor-->>Client: {stdout: '', stderr: 'command timed out', exitCode: -1}
    else Within Timeout
        ShellExecutor->>ShellExecutor: await response.delay
        ShellExecutor->>History: record {command, args}
        ShellExecutor-->>Client: {stdout, stderr, exitCode}
    end
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~70 minutes

Possibly related issues

Possibly related PRs

Poem

🐰 With execFile hops and slugs validated,
Email checks now centralized and fated,
CSRF tokens hop away—no more stray,
The rabbit bounds through cleaner code today! 🌟

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 10.00% 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
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes three main security/maintenance fixes: shared email validation, shell executor hardening with execFile, and CSRF token removal from OAuth redirects.

✏️ 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/sec-p3-consolidate

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.

2witstudios and others added 2 commits April 8, 2026 12:40
# Conflicts:
#	apps/web/src/app/api/account/route.ts
The @pagespace/lib mock in get-patch-route.test.ts was missing the
isValidEmail function after it was added to the import. Also adds
deleteMonitoringDataForUser from the master merge.

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

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
apps/web/src/app/api/auth/google/callback/__tests__/route.test.ts (1)

778-786: ⚠️ Potential issue | 🟡 Minor

Test description is now inconsistent with the assertion.

The test is named "redirects to returnUrl with auth success and CSRF token" (line 778), but line 786 now asserts that csrfToken is not in the URL. The description should be updated to reflect the new expected behavior.

Suggested fix
-    it('redirects to returnUrl with auth success and CSRF token', async () => {
+    it('redirects to returnUrl with auth success without CSRF token in URL', async () => {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web/src/app/api/auth/google/callback/__tests__/route.test.ts` around
lines 778 - 786, The test title is inconsistent with its assertion: update the
it(...) description string currently "redirects to returnUrl with auth success
and CSRF token" to reflect the expectation that csrfToken is absent (e.g.,
"redirects to returnUrl with auth success without CSRF token") so the test name
matches the assertion expect(location).not.toContain('csrfToken') in the test
for the GET handler.
apps/web/src/app/api/auth/apple/callback/__tests__/route.test.ts (1)

933-946: ⚠️ Potential issue | 🟡 Minor

Test description is inconsistent with the updated assertion.

The test name says "redirects to returnUrl with auth success and CSRF token" (line 933), but line 946 now asserts that csrfToken is not in the redirect URL. This matches the same issue in the Google OAuth tests; update for consistency.

Suggested fix
-    it('redirects to returnUrl with auth success and CSRF token', async () => {
+    it('redirects to returnUrl with auth success without CSRF token in URL', async () => {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web/src/app/api/auth/apple/callback/__tests__/route.test.ts` around
lines 933 - 946, Update the test title in the Apple callback test block so it
matches the assertions: change the it(...) description that currently reads
"redirects to returnUrl with auth success and CSRF token" to reflect that
csrfToken should NOT be present (e.g., "redirects to returnUrl with auth success
and no CSRF token"); locate the test in route.test.ts where createSignedState,
createCallbackRequest and POST are used and adjust only the string description
to match the existing expectations.
apps/web/src/app/api/auth/google/__tests__/google-callback-redirect.test.ts (1)

204-236: ⚠️ Potential issue | 🟡 Minor

Test description is inconsistent with the updated assertion.

The test name says "should create session and redirect with CSRF token" (line 204), but line 236 now asserts that csrfToken is not in the redirect URL. Update the description to match the new expected behavior.

Suggested fix
-    it('given successful OAuth, should create session and redirect with CSRF token', async () => {
+    it('given successful OAuth, should create session and redirect without CSRF token in URL', async () => {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web/src/app/api/auth/google/__tests__/google-callback-redirect.test.ts`
around lines 204 - 236, Update the test description string in the it(...) block
named "should create session and redirect with CSRF token" to reflect the actual
assertion (no csrfToken in redirect), e.g. change to "should create session and
redirect without CSRF token" so the it() description matches the expectation in
the test that location does not contain csrfToken; keep all assertions and mocks
(sessionService.createSession, appendSessionCookie, GET call) unchanged.
🧹 Nitpick comments (1)
apps/web/src/app/api/account/__tests__/get-patch-route.test.ts (1)

45-57: Mock implementation duplicates validation logic.

The mock replicates the isValidEmail logic verbatim. If the real implementation in packages/lib/src/validators/email.ts changes (e.g., different max length or regex), this mock will diverge, causing tests to pass while production behavior differs.

Consider using vi.importActual for the email validator to avoid drift:

vi.mock('@pagespace/lib', async () => {
  const actual = await vi.importActual<typeof import('@pagespace/lib')>('@pagespace/lib');
  return {
    isValidEmail: actual.isValidEmail, // use real implementation
    createUserServiceToken: vi.fn(),
    deleteAiUsageLogsForUser: vi.fn(),
    deleteMonitoringDataForUser: vi.fn(),
  };
});

This ensures test validation behavior stays in sync with production.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web/src/app/api/account/__tests__/get-patch-route.test.ts` around lines
45 - 57, The mock in the vi.mock block duplicates the isValidEmail
implementation causing drift; replace the copied validator by importing the real
implementation via vi.importActual and return isValidEmail from the actual
module while keeping createUserServiceToken, deleteAiUsageLogsForUser, and
deleteMonitoringDataForUser as vi.fn() so tests use production validation but
stub the side-effect functions. Ensure you reference the existing vi.mock call
and the symbols isValidEmail, createUserServiceToken, deleteAiUsageLogsForUser,
and deleteMonitoringDataForUser when making the change.
🤖 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/marketing/src/app/api/contact/route.ts`:
- Around line 3-9: This file duplicates the email validator (EMAIL_PATTERN and
isValidEmail); replace the local implementation by importing and using the
shared validator exported from `@pagespace/lib` or `@pagespace/lib/validators`
instead: remove the EMAIL_PATTERN constant and local isValidEmail function, add
an import for the shared isValidEmail (or named export) and update any
references in route.ts to call that imported function so validation is
centralized with the other consumers (apps/web and control-plane).

---

Outside diff comments:
In `@apps/web/src/app/api/auth/apple/callback/__tests__/route.test.ts`:
- Around line 933-946: Update the test title in the Apple callback test block so
it matches the assertions: change the it(...) description that currently reads
"redirects to returnUrl with auth success and CSRF token" to reflect that
csrfToken should NOT be present (e.g., "redirects to returnUrl with auth success
and no CSRF token"); locate the test in route.test.ts where createSignedState,
createCallbackRequest and POST are used and adjust only the string description
to match the existing expectations.

In `@apps/web/src/app/api/auth/google/__tests__/google-callback-redirect.test.ts`:
- Around line 204-236: Update the test description string in the it(...) block
named "should create session and redirect with CSRF token" to reflect the actual
assertion (no csrfToken in redirect), e.g. change to "should create session and
redirect without CSRF token" so the it() description matches the expectation in
the test that location does not contain csrfToken; keep all assertions and mocks
(sessionService.createSession, appendSessionCookie, GET call) unchanged.

In `@apps/web/src/app/api/auth/google/callback/__tests__/route.test.ts`:
- Around line 778-786: The test title is inconsistent with its assertion: update
the it(...) description string currently "redirects to returnUrl with auth
success and CSRF token" to reflect the expectation that csrfToken is absent
(e.g., "redirects to returnUrl with auth success without CSRF token") so the
test name matches the assertion expect(location).not.toContain('csrfToken') in
the test for the GET handler.

---

Nitpick comments:
In `@apps/web/src/app/api/account/__tests__/get-patch-route.test.ts`:
- Around line 45-57: The mock in the vi.mock block duplicates the isValidEmail
implementation causing drift; replace the copied validator by importing the real
implementation via vi.importActual and return isValidEmail from the actual
module while keeping createUserServiceToken, deleteAiUsageLogsForUser, and
deleteMonitoringDataForUser as vi.fn() so tests use production validation but
stub the side-effect functions. Ensure you reference the existing vi.mock call
and the symbols isValidEmail, createUserServiceToken, deleteAiUsageLogsForUser,
and deleteMonitoringDataForUser when making the change.
🪄 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: 285d9103-7a95-4d8b-92c0-2f1858ba9bbb

📥 Commits

Reviewing files that changed from the base of the PR and between 38dd34a and 1c2cce2.

📒 Files selected for processing (19)
  • apps/control-plane/src/services/__tests__/shell-executor.test.ts
  • apps/control-plane/src/services/__tests__/upgrade-service.test.ts
  • apps/control-plane/src/services/shell-executor.ts
  • apps/control-plane/src/services/upgrade-service.ts
  • apps/control-plane/src/validation/tenant-validation.ts
  • apps/control-plane/vitest.config.ts
  • apps/marketing/src/app/api/contact/route.ts
  • apps/web/src/app/api/account/__tests__/get-patch-route.test.ts
  • apps/web/src/app/api/account/route.ts
  • apps/web/src/app/api/auth/apple/callback/__tests__/route.test.ts
  • apps/web/src/app/api/auth/apple/callback/route.ts
  • apps/web/src/app/api/auth/google/__tests__/google-callback-redirect.test.ts
  • apps/web/src/app/api/auth/google/__tests__/open-redirect-protection.test.ts
  • apps/web/src/app/api/auth/google/callback/__tests__/route.test.ts
  • apps/web/src/app/api/auth/google/callback/route.ts
  • apps/web/src/app/api/internal/contact/route.ts
  • packages/lib/src/validators/__tests__/email.test.ts
  • packages/lib/src/validators/email.ts
  • packages/lib/src/validators/index.ts
💤 Files with no reviewable changes (2)
  • apps/web/src/app/api/auth/apple/callback/route.ts
  • apps/web/src/app/api/auth/google/callback/route.ts

Test names referenced "CSRF token" in redirect but assertions now
verify csrfToken is absent. Updated 3 test descriptions to match.

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

Addressing CodeRabbit review feedback:

Test descriptions (3 files) — Fixed in commit 6fdde79. Updated all test names that still referenced "CSRF token" to say "without CSRF token in URL" to match the not.toContain('csrfToken') assertions.

Marketing email duplication — Intentional. apps/marketing/ has zero @pagespace/* dependencies — it's a fully standalone Next.js app. Adding @pagespace/lib as a dependency just for a 6-line pure function would be over-coupling. The regex is identical to the canonical source.

Mock duplication in get-patch-route.test.tsvi.importActual('@pagespace/lib') would transitively import @pagespace/db and other server-side modules, causing resolution failures in the test environment (we hit this exact issue during development). The inlined function is tiny, pure, and stable. Trade-off: minor duplication vs test reliability.

@2witstudios 2witstudios merged commit fd21e40 into master Apr 8, 2026
10 checks passed
@2witstudios 2witstudios deleted the pu/sec-p3-consolidate branch April 8, 2026 19:20
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