Skip to content

fix: relax devtools-specific security headers in dev#2331

Merged
ghostdevv merged 4 commits intonpmx-dev:mainfrom
RYGRIT:fix/devtools-csp
Apr 4, 2026
Merged

fix: relax devtools-specific security headers in dev#2331
ghostdevv merged 4 commits intonpmx-dev:mainfrom
RYGRIT:fix/devtools-csp

Conversation

@RYGRIT
Copy link
Copy Markdown
Contributor

@RYGRIT RYGRIT commented Mar 30, 2026

🔗 Linked issue

🧭 Context

Nuxt DevTools relies on a same-origin iframe and dev-only runtime connections, which were being blocked by our CSP and frame restrictions during development.

Instead of disabling security-headers entirely in dev, this change keeps the existing security headers in place and only relaxes the DevTools-specific parts needed for the local dev runtime.

📚 Description

This PR updates security-headers so that:

  • DevTools-only CSP requirements are allowed in dev
  • the DevTools route uses SAMEORIGIN instead of DENY
  • DevTools-specific relaxations are not applied when DevTools is explicitly disabled

@vercel
Copy link
Copy Markdown

vercel bot commented Mar 30, 2026

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

Project Deployment Actions Updated (UTC)
npmx.dev Ready Ready Preview, Comment Apr 4, 2026 1:22pm
2 Skipped Deployments
Project Deployment Actions Updated (UTC)
docs.npmx.dev Ignored Ignored Preview Apr 4, 2026 1:22pm
npmx-lunaria Ignored Ignored Apr 4, 2026 1:22pm

Request Review

@codecov
Copy link
Copy Markdown

codecov bot commented Mar 30, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ All tests successful. No failed tests found.

📢 Thoughts on this report? Let us know!

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Mar 30, 2026

📝 Walkthrough

Walkthrough

The module modules/security-headers.ts now obtains Nuxt via useNuxt() and computes isDevtoolsRuntime from Nuxt dev mode, devtools enablement and the TEST env var. It installs shared security headers in all cases, but when isDevtoolsRuntime is true it additionally extends CSP connect-src with ws://localhost:*, appends 'self' to frame-src, and adds a routeRules['/__nuxt_devtools__/**'] override that relaxes X-Frame-Options to SAMEORIGIN. When false, the devtools-specific route rule is not created.

Possibly related PRs

Suggested reviewers

  • danielroe
🚥 Pre-merge checks | ✅ 1
✅ Passed checks (1 passed)
Check name Status Explanation
Description check ✅ Passed The pull request description clearly relates to the changeset, explaining why security headers for DevTools are being relaxed in development.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

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


ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 207422fc-b94f-41a9-a915-47432109451b

📥 Commits

Reviewing files that changed from the base of the PR and between b3da028 and f02eb39.

📒 Files selected for processing (1)
  • modules/security-headers.ts

Copy link
Copy Markdown
Contributor

@ghostdevv ghostdevv left a comment

Choose a reason for hiding this comment

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

Could we instead add the necessary nuxt permissions in dev?

@RYGRIT
Copy link
Copy Markdown
Contributor Author

RYGRIT commented Apr 2, 2026

@ghostdevv I’m not fully sure what you mean by “nuxt permissions” here. If you mean a built-in Nuxt/DevTools setting, I couldn’t find one that seems to apply here.

From what I can tell, the issue is caused by our security headers rather than a Nuxt option: DevTools uses a same-origin iframe under /__nuxt_devtools__/client/, so instead of skipping security-headers entirely in dev, I think the better fix is to keep the headers and only relax the dev-only bits it needs, like allowing 'self' in frame-src, using SAMEORIGIN for the DevTools route instead of DENY, and adding any required dev connect-src entries if needed.

@ghostdevv
Copy link
Copy Markdown
Contributor

@ghostdevv I’m not fully sure what you mean by “nuxt permissions” here. If you mean a built-in Nuxt/DevTools setting, I couldn’t find one that seems to apply here.

From what I can tell, the issue is caused by our security headers rather than a Nuxt option: DevTools uses a same-origin iframe under /__nuxt_devtools__/client/, so instead of skipping security-headers entirely in dev, I think the better fix is to keep the headers and only relax the dev-only bits it needs, like allowing 'self' in frame-src, using SAMEORIGIN for the DevTools route instead of DENY, and adding any required dev connect-src entries if needed.

What I meant was can we expand the permissions in dev to support the devtools iframe? I'm weary against relaxing the policy in dev as it may make it harder for us to catch issues with the security policy as we have one less testing ground before production - if that makes sense?

@RYGRIT
Copy link
Copy Markdown
Contributor Author

RYGRIT commented Apr 4, 2026

Yep, that makes sense. I kept the headers in dev and only relaxed the bits DevTools needs (frame-src 'self', SAMEORIGIN for /__nuxt_devtools__/, and the dev-only websocket source), so we still exercise the CSP in dev instead of bypassing it entirely.

@RYGRIT RYGRIT changed the title fix: skip security headers when devtools is active fix: relax devtools-specific security headers in dev Apr 4, 2026
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.

🧹 Nitpick comments (3)
modules/security-headers.ts (1)

26-30: The isDevtoolsRuntime condition is correct but complex; consider adding a clarifying comment.

The logic correctly handles all devtools configuration forms (boolean false, null/undefined, and object with optional enabled property), defaulting to enabled when not explicitly disabled. This aligns with Nuxt DevTools' default behaviour where enabled defaults to true. The past review suggested !!nuxt.options.devtools?.enabled, but that would incorrectly treat unset/undefined as disabled.

A brief inline comment would help future maintainers understand the intent.

📝 Suggested clarifying comment
+    // DevTools is considered active when:
+    // - We're in dev mode
+    // - devtools is not explicitly disabled (boolean false, or object with enabled: false)
+    // - We're not running tests
     const isDevtoolsRuntime =
       nuxt.options.dev &&
       devtools !== false &&
       (devtools == null || typeof devtools !== 'object' || devtools.enabled !== false) &&
       !process.env.TEST
test/unit/modules/security-headers.spec.ts (2)

49-53: Consider adding test cases for additional edge configurations.

The current tests cover devtools: { enabled: true } and devtools: { enabled: false }, but the module's isDevtoolsRuntime logic also handles:

  • devtools: undefined or devtools: null (should enable in dev mode)
  • devtools: {} (enabled property not specified, should enable)
  • process.env.TEST being set (should disable regardless of devtools config)
  • dev: false (production mode, should disable)

Adding these cases would strengthen coverage of the defensive checks in lines 26-30 of the module.

💡 Suggested additional test cases
it('enables devtools relaxations when devtools config is undefined', () => {
  const nuxt = createNuxt({
    dev: true,
    devtools: undefined,
  })

  useNuxt.mockReturnValue(nuxt)
  securityHeadersModule.setup()

  const csp = getCsp(nuxt)

  expect(csp).toContain('ws://localhost:*')
  expect(nuxt.options.routeRules['/__nuxt_devtools__/**']).toBeDefined()
})

it('enables devtools relaxations when devtools is empty object', () => {
  const nuxt = createNuxt({
    dev: true,
    devtools: {},
  })

  useNuxt.mockReturnValue(nuxt)
  securityHeadersModule.setup()

  const csp = getCsp(nuxt)

  expect(csp).toContain('ws://localhost:*')
})

it('disables devtools relaxations in production mode', () => {
  const nuxt = createNuxt({
    dev: false,
    devtools: { enabled: true },
  })

  useNuxt.mockReturnValue(nuxt)
  securityHeadersModule.setup()

  const csp = getCsp(nuxt)

  expect(csp).not.toContain('ws://localhost:*')
  expect(nuxt.options.routeRules['/__nuxt_devtools__/**']).toBeUndefined()
})

it('disables devtools relaxations when TEST env is set', () => {
  process.env.TEST = 'true'
  const nuxt = createNuxt({
    dev: true,
    devtools: { enabled: true },
  })

  useNuxt.mockReturnValue(nuxt)
  securityHeadersModule.setup()

  const csp = getCsp(nuxt)

  expect(csp).not.toContain('ws://localhost:*')
})

112-114: Consider adding a positive assertion for base frame-src when devtools is disabled.

The current assertion verifies 'self' is absent, but doesn't confirm the base frame sources (https://bsky.app https://pdsmoover.com) are still present. Adding a positive assertion would catch potential regressions where the entire directive might be incorrectly modified.

💡 Suggested additional assertion
     expect(csp).not.toContain('ws://localhost:*')
     expect(csp).not.toContain("frame-src https://bsky.app https://pdsmoover.com 'self'")
+    expect(csp).toContain('frame-src https://bsky.app https://pdsmoover.com')
     expect(nuxt.options.routeRules['/__nuxt_devtools__/**']).toBeUndefined()

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 68456026-73b0-4f4e-8aa0-522cd51c4ea1

📥 Commits

Reviewing files that changed from the base of the PR and between f02eb39 and 0c92f08.

📒 Files selected for processing (2)
  • modules/security-headers.ts
  • test/unit/modules/security-headers.spec.ts

@ghostdevv ghostdevv added this pull request to the merge queue Apr 4, 2026
Merged via the queue into npmx-dev:main with commit ae16a6b Apr 4, 2026
22 checks passed
@RYGRIT RYGRIT deleted the fix/devtools-csp branch April 4, 2026 23:41
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.

2 participants