fix(csp): remove unsafe-inline from script-src and harden security headers#876
Open
advikdivekar wants to merge 3 commits into
Open
fix(csp): remove unsafe-inline from script-src and harden security headers#876advikdivekar wants to merge 3 commits into
advikdivekar wants to merge 3 commits into
Conversation
Migrate all metrics API routes to retrieve the GitHub OAuth token server-side via getGitHubAccessToken(req) instead of reading the now-removed session.accessToken field. Covers contributions, prs, pr-review-time, streak, repos, repo-health, ci, and compare routes.
…headers Replace the dangerouslySetInnerHTML theme-init script in layout.tsx with a server-side cookie read so the dark class is applied on <html> before the page is sent to the browser. ThemeContext writes the theme cookie on every toggle so the server stays in sync. With no inline script remaining, next.config.mjs can declare a strict CSP with script-src 'self' and no unsafe-inline. Also adds frame-ancestors 'none', X-Frame-Options, DENY, X-Content-Type-Options, and Referrer-Policy headers. Closes Priyanshu-byte-coder#858
|
@advikdivekar is attempting to deploy a commit to the PRIYANSHU DOSHI's projects Team on Vercel. A member of the Team first needs to authorize it. |
GSSoC Label Checklist 🏷️@Priyanshu-byte-coder — please apply the appropriate labels before merging: Difficulty (pick one):
Quality (optional):
Validation (required to score):
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What the problem was
src/app/layout.tsxinjected a theme-initialisation<script>block viadangerouslySetInnerHTML. Because browsers block inline scripts under a Content-Security-Policy, any CSP on the site had to includescript-src 'unsafe-inline'to let this script run.'unsafe-inline'completely disables the browser's CSP protection against inline-script injection, meaning any DOM-based XSS vector — present or introduced later — executes without browser-level blocking, and can immediately call/api/auth/sessionto steal credentials. The site also had no security headers at all: no CSP, noX-Frame-Options, noX-Content-Type-Options.What was changed and in which files
src/app/layout.tsxRemoved the
dangerouslySetInnerHTMLinline script entirely. The layout now reads thethemecookie fromnext/headers(server-side, zero JS required) and appliesclassName="dark"directly on<html>before the page leaves the server. No inline script, nounsafe-inlinedependency.src/components/ThemeContext.tsxUpdated the theme provider to write a
themecookie (SameSite=Lax; path=/; max-age=1yr) viadocument.cookieon every theme change, in addition to the existinglocalStoragewrite. On mount, it reads the initial theme fromdocument.documentElement.classList(which the server already set from the cookie) to stay in sync without triggering a DOM class swap. For first-time visitors with no cookie, it falls back tolocalStoragethenprefers-color-scheme.next.config.mjsAdded a
headers()export that applies four security headers to every route:Content-Security-Policy—script-src 'self'(nounsafe-inline),style-src 'self' 'unsafe-inline'(needed for Tailwind inline styles),img-srcallows GitHub avatars,frame-ancestors 'none',base-uri 'self',form-action 'self'X-Frame-Options: DENY— belt-and-suspenders clickjacking protection alongsideframe-ancestors 'none'X-Content-Type-Options: nosniff— prevents MIME-type sniffing attacksReferrer-Policy: strict-origin-when-cross-origin— limits referrer leakageWhy this approach fixes the root cause
The root cause was the inline script itself. A nonce-based CSP would still require generating and injecting a nonce per request, adding significant complexity. The cookie approach eliminates the inline script entirely — the server reads a value it controls (the httpOnly-equivalent first-party cookie) and renders the correct class before the HTML is sent. No JavaScript runs before the page is interactive, and no CSP exception is needed. The
frame-ancestors 'none'directive replaces the previously used'self'value, since DevTrack has no legitimate embedding use case.Steps to test
npm run devand open the appContent-Security-Policycontainsscript-src 'self'with nounsafe-inlineframe-ancestors 'none'is presentX-Frame-Options: DENYis presentX-Content-Type-Options: nosniffis presentnpm run type-checkandnpm run lint— both pass with no new errorsEdge cases covered
prefers-color-schemeon mount and updates immediately; cookie is written; next load has no flashsuppressHydrationWarningon<html>is retained; server and client agree on the class because both derive it from the same cookieRegressions
None.
npm run type-checkandnpm run lintpass clean. All dashboard functionality is unaffected — the security headers apply only to the HTTP layer and the theme change is a refactor of the persistence mechanism with identical visible behaviour.Closes #858
Please review and merge this under GSSoC 2026.