From accc734b03f883e8f5a1da3da119c2b33e3aa557 Mon Sep 17 00:00:00 2001 From: nios-x Date: Sat, 10 Jan 2026 19:34:35 +0000 Subject: [PATCH 01/13] Suppress non-actionable ZAP baseline warnings for Next.js SPA --- .zapconfig | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/.zapconfig b/.zapconfig index aedaf93b93..c661facead 100644 --- a/.zapconfig +++ b/.zapconfig @@ -1,3 +1,9 @@ +# ============================ +# ZAP Baseline Scan Suppressions +# Purpose: Ignore non-actionable or framework/browser-controlled warnings. +# Only real vulnerabilities will fail CI. +# ============================ + # Rule description: comment. # rule_idACTION(URL regex pattern) @@ -6,3 +12,32 @@ # PII disclosure: false positive credicard number. 10062 IGNORE https://nest.owasp.(dev|org)/sitemap.xml + +# Sub Resource Integrity Attribute Missing: Next.js internal chunks do not support SRI +90003 IGNORE https://nest.owasp.(dev|org)/_next/static/chunks/[a-f0-9]{16}.js + +# Insufficient Site Isolation Against Spectre: COOP/COEP not enforced for SPA, breaks site +90004 IGNORE https://nest.owasp.(dev|org)/.* + +# Sec-Fetch-Dest Header Missing: browser-controlled, cannot enforce server-side +90005 IGNORE https://nest.owasp.(dev|org)/.* + +# Base64 Disclosure: false positives due to Next.js hydration / CSS inlined assets +10094 IGNORE https://nest.owasp.(dev|org)/_next/static/chunks/[a-f0-9]{16}.js + +# Non-Storable Content: informational only, not a vulnerability +10049 IGNORE https://nest.owasp.(dev|org)/.* + +# Re-examine Cache-Control Directives: informational for SPA / framework-managed caching +10015 IGNORE https://nest.owasp.(dev|org)/.* + +# Content-Type Header Missing: harmless on 308 redirects +10019 IGNORE https://nest.owasp.(dev|org)/.* + +# CSP: Failure to Define Directive with No Fallback +# Advisory-only; CSP is defined at framework/app level +10055 IGNORE https://nest.owasp.(dev|org)/.* + +# Session Management Response Identified +# Informational detection; no insecure session behavior observed +10112 IGNORE https://nest.owasp.(dev|org)/csrf/ From 94cdac56ad93b8a9cc666cb8c06b03aa465bcdef Mon Sep 17 00:00:00 2001 From: nios-x Date: Sat, 10 Jan 2026 20:05:30 +0000 Subject: [PATCH 02/13] Suppress non-actionable ZAP baseline warnings for Next.js SPA --- .zapconfig | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/.zapconfig b/.zapconfig index c661facead..786c0e04cb 100644 --- a/.zapconfig +++ b/.zapconfig @@ -16,9 +16,6 @@ # Sub Resource Integrity Attribute Missing: Next.js internal chunks do not support SRI 90003 IGNORE https://nest.owasp.(dev|org)/_next/static/chunks/[a-f0-9]{16}.js -# Insufficient Site Isolation Against Spectre: COOP/COEP not enforced for SPA, breaks site -90004 IGNORE https://nest.owasp.(dev|org)/.* - # Sec-Fetch-Dest Header Missing: browser-controlled, cannot enforce server-side 90005 IGNORE https://nest.owasp.(dev|org)/.* @@ -32,7 +29,7 @@ 10015 IGNORE https://nest.owasp.(dev|org)/.* # Content-Type Header Missing: harmless on 308 redirects -10019 IGNORE https://nest.owasp.(dev|org)/.* +10019 IGNORE https://nest.owasp.(dev|org)/_next/static/.* # CSP: Failure to Define Directive with No Fallback # Advisory-only; CSP is defined at framework/app level From 7d024b0ee11f9d5aa50c5158698160d2cd06b8ae Mon Sep 17 00:00:00 2001 From: nios-x Date: Sun, 11 Jan 2026 18:36:30 +0000 Subject: [PATCH 03/13] Suppress non-actionable ZAP baseline warnings for Next.js SPA --- backend/settings/base.py | 6 ++++++ frontend/next.config.ts | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 38 insertions(+) diff --git a/backend/settings/base.py b/backend/settings/base.py index 4f76af0b50..0b0f5c85b6 100644 --- a/backend/settings/base.py +++ b/backend/settings/base.py @@ -31,6 +31,12 @@ class Base(Configuration): SESSION_COOKIE_SAMESITE = "Lax" SESSION_COOKIE_SECURE = True + # --- CSRF cookie settings (SPA-safe, OWASP compliant) --- + CSRF_COOKIE_SECURE = True + CSRF_COOKIE_SAMESITE = "Strict" + # CSRF_COOKIE_HTTPONLY is intentionally NOT enabled + # Django CSRF cookies must be readable by JS for SPA frameworks + SITE_NAME = "localhost" SITE_URL = "http://localhost:8000" diff --git a/frontend/next.config.ts b/frontend/next.config.ts index a1a585bd53..e1d157eba9 100644 --- a/frontend/next.config.ts +++ b/frontend/next.config.ts @@ -5,6 +5,38 @@ const isLocal = process.env.NEXT_PUBLIC_ENVIRONMENT === 'local' const nextConfig: NextConfig = { devIndicators: false, + async headers() { + return [ + { + source: '/(.*)', + headers: [ + { + key: 'Cross-Origin-Opener-Policy', + value: 'same-origin', + }, + { + key: 'Cross-Origin-Embedder-Policy', + value: 'require-corp', + }, + { + key: 'Cross-Origin-Resource-Policy', + value: 'same-origin', + }, + { + key: 'Content-Security-Policy', + value: [ + "default-src 'self'", + "script-src 'self' 'unsafe-inline'", + "style-src 'self' 'unsafe-inline'", + "img-src 'self' data: https:", + "font-src 'self'", + "connect-src 'self' https:", + ].join('; '), + }, + ], + }, + ] + }, images: { // This is a list of remote patterns that Next.js will use to determine // if an image is allowed to be loaded from a remote source. From c6b8260e8f743e986ffa6b735b22b588450c8ff1 Mon Sep 17 00:00:00 2001 From: nios-x Date: Sun, 11 Jan 2026 18:43:10 +0000 Subject: [PATCH 04/13] Suppress non-actionable ZAP baseline warnings for Next.js SPA --- frontend/next.config.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/next.config.ts b/frontend/next.config.ts index e1d157eba9..026a29e5c5 100644 --- a/frontend/next.config.ts +++ b/frontend/next.config.ts @@ -16,7 +16,7 @@ const nextConfig: NextConfig = { }, { key: 'Cross-Origin-Embedder-Policy', - value: 'require-corp', + value: 'credentialless', }, { key: 'Cross-Origin-Resource-Policy', From 52249363fac01b373bb39197b684884243d3ae7d Mon Sep 17 00:00:00 2001 From: nios-x Date: Sun, 11 Jan 2026 19:37:49 +0000 Subject: [PATCH 05/13] Suppress non-actionable ZAP baseline warnings for Next.js SPA --- backend/settings/local.py | 2 ++ backend/settings/production.py | 2 ++ backend/settings/staging.py | 2 ++ 3 files changed, 6 insertions(+) diff --git a/backend/settings/local.py b/backend/settings/local.py index fa0a847b5d..c45cc6f92c 100644 --- a/backend/settings/local.py +++ b/backend/settings/local.py @@ -23,3 +23,5 @@ class Local(Base): PUBLIC_IP_ADDRESS = values.Value() SLACK_COMMANDS_ENABLED = True SLACK_EVENTS_ENABLED = True + + CSRF_COOKIE_SAMESITE = "Lax" diff --git a/backend/settings/production.py b/backend/settings/production.py index 8b5145f9e4..ff7792aa4c 100644 --- a/backend/settings/production.py +++ b/backend/settings/production.py @@ -54,3 +54,5 @@ class Production(Base): SLACK_COMMANDS_ENABLED = True SLACK_EVENTS_ENABLED = True + + CSRF_COOKIE_SAMESITE = "Strict" diff --git a/backend/settings/staging.py b/backend/settings/staging.py index 1d7f4f408a..0aa7273ead 100644 --- a/backend/settings/staging.py +++ b/backend/settings/staging.py @@ -51,3 +51,5 @@ class Staging(Base): SLACK_COMMANDS_ENABLED = True SLACK_EVENTS_ENABLED = True + + CSRF_COOKIE_SAMESITE = "Strict" \ No newline at end of file From 6e21f40f73f8f0a652c4de417508cdc9200a657a Mon Sep 17 00:00:00 2001 From: nios-x Date: Sun, 11 Jan 2026 19:44:04 +0000 Subject: [PATCH 06/13] Suppress non-actionable ZAP baseline warnings for Next.js SPA --- backend/settings/staging.py | 2 +- cspell/custom-dict.txt | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/backend/settings/staging.py b/backend/settings/staging.py index 0aa7273ead..95dcc14139 100644 --- a/backend/settings/staging.py +++ b/backend/settings/staging.py @@ -52,4 +52,4 @@ class Staging(Base): SLACK_COMMANDS_ENABLED = True SLACK_EVENTS_ENABLED = True - CSRF_COOKIE_SAMESITE = "Strict" \ No newline at end of file + CSRF_COOKIE_SAMESITE = "Strict" diff --git a/cspell/custom-dict.txt b/cspell/custom-dict.txt index 58bc30c83a..6427d59b38 100644 --- a/cspell/custom-dict.txt +++ b/cspell/custom-dict.txt @@ -51,6 +51,7 @@ certbot collectstatic coraza corsheaders +credentialless csrfguard csrfprotector csrftoken From 406ab2ae352b3c27db930c8d2bc6af9da0b6ae5c Mon Sep 17 00:00:00 2001 From: nios-x Date: Sun, 11 Jan 2026 19:52:47 +0000 Subject: [PATCH 07/13] Suppress non-actionable ZAP baseline warnings for Next.js SPA --- backend/settings/production.py | 2 -- backend/settings/staging.py | 2 -- frontend/next.config.ts | 2 +- 3 files changed, 1 insertion(+), 5 deletions(-) diff --git a/backend/settings/production.py b/backend/settings/production.py index ff7792aa4c..8b5145f9e4 100644 --- a/backend/settings/production.py +++ b/backend/settings/production.py @@ -54,5 +54,3 @@ class Production(Base): SLACK_COMMANDS_ENABLED = True SLACK_EVENTS_ENABLED = True - - CSRF_COOKIE_SAMESITE = "Strict" diff --git a/backend/settings/staging.py b/backend/settings/staging.py index 95dcc14139..1d7f4f408a 100644 --- a/backend/settings/staging.py +++ b/backend/settings/staging.py @@ -51,5 +51,3 @@ class Staging(Base): SLACK_COMMANDS_ENABLED = True SLACK_EVENTS_ENABLED = True - - CSRF_COOKIE_SAMESITE = "Strict" diff --git a/frontend/next.config.ts b/frontend/next.config.ts index 026a29e5c5..a44767fa23 100644 --- a/frontend/next.config.ts +++ b/frontend/next.config.ts @@ -26,7 +26,7 @@ const nextConfig: NextConfig = { key: 'Content-Security-Policy', value: [ "default-src 'self'", - "script-src 'self' 'unsafe-inline'", + "script-src 'self' 'strict-dynamic' 'nonce-' https:", "style-src 'self' 'unsafe-inline'", "img-src 'self' data: https:", "font-src 'self'", From 177b75a5d0e2a9970bad5ce70adfaa0d4ebfdf6e Mon Sep 17 00:00:00 2001 From: nios-x Date: Sun, 11 Jan 2026 20:17:35 +0000 Subject: [PATCH 08/13] Suppress non-actionable ZAP baseline warnings for Next.js SPA --- frontend/next.config.ts | 11 ----------- frontend/src/proxy.ts | 40 +++++++++++++++++++++++++++++++++------- 2 files changed, 33 insertions(+), 18 deletions(-) diff --git a/frontend/next.config.ts b/frontend/next.config.ts index a44767fa23..cdbd28932c 100644 --- a/frontend/next.config.ts +++ b/frontend/next.config.ts @@ -22,17 +22,6 @@ const nextConfig: NextConfig = { key: 'Cross-Origin-Resource-Policy', value: 'same-origin', }, - { - key: 'Content-Security-Policy', - value: [ - "default-src 'self'", - "script-src 'self' 'strict-dynamic' 'nonce-' https:", - "style-src 'self' 'unsafe-inline'", - "img-src 'self' data: https:", - "font-src 'self'", - "connect-src 'self' https:", - ].join('; '), - }, ], }, ] diff --git a/frontend/src/proxy.ts b/frontend/src/proxy.ts index 65a57d9fdc..3569d7bb56 100644 --- a/frontend/src/proxy.ts +++ b/frontend/src/proxy.ts @@ -1,16 +1,42 @@ import { NextResponse } from 'next/server' import type { NextRequest } from 'next/server' import { getToken } from 'next-auth/jwt' +import crypto from 'crypto' -export default async function proxy(request: NextRequest) { - const token = await getToken({ req: request }) - if (!token) { - return NextResponse.redirect(new URL('/auth/login', request.url)) +export async function proxy(request: NextRequest) { + + const nonce = crypto.randomBytes(16).toString('base64') + + // --- Build CSP header --- + const cspHeader = [ + "default-src 'self'", + `script-src 'self' 'strict-dynamic' 'nonce-${nonce}' https:`, + "style-src 'self' 'unsafe-inline'", // required for Next.js styled-jsx + "img-src 'self' data: https:", + "font-src 'self'", + "connect-src 'self' https:", + ].join('; ') + + // --- Create initial response --- + const response = NextResponse.next() + response.headers.set('Content-Security-Policy', cspHeader) + response.headers.set('x-nonce', nonce) // optional, expose nonce to frontend if needed + + // --- JWT Auth protection for mentorship routes --- + const protectedPaths = ['/my/mentorship'] + if (protectedPaths.some(path => request.nextUrl.pathname.startsWith(path))) { + const token = await getToken({ req: request }) + if (!token) { + const loginUrl = request.nextUrl.clone() + loginUrl.pathname = '/auth/login' + return NextResponse.redirect(loginUrl) + } } - return NextResponse.next() + + return response } +// --- Configure which routes this middleware runs on --- export const config = { - // Protected routes. - matcher: ['/my/mentorship/:path*'], + matcher: ['/(.*)'], // run for all routes to apply CSP } From b3108fa321c53277ec2c6a37c423d23566f71f18 Mon Sep 17 00:00:00 2001 From: nios-x Date: Sun, 11 Jan 2026 20:25:54 +0000 Subject: [PATCH 09/13] Suppress non-actionable ZAP baseline warnings for Next.js SPA --- frontend/src/proxy.ts | 40 +++++++--------------------------------- 1 file changed, 7 insertions(+), 33 deletions(-) diff --git a/frontend/src/proxy.ts b/frontend/src/proxy.ts index 3569d7bb56..65a57d9fdc 100644 --- a/frontend/src/proxy.ts +++ b/frontend/src/proxy.ts @@ -1,42 +1,16 @@ import { NextResponse } from 'next/server' import type { NextRequest } from 'next/server' import { getToken } from 'next-auth/jwt' -import crypto from 'crypto' -export async function proxy(request: NextRequest) { - - const nonce = crypto.randomBytes(16).toString('base64') - - // --- Build CSP header --- - const cspHeader = [ - "default-src 'self'", - `script-src 'self' 'strict-dynamic' 'nonce-${nonce}' https:`, - "style-src 'self' 'unsafe-inline'", // required for Next.js styled-jsx - "img-src 'self' data: https:", - "font-src 'self'", - "connect-src 'self' https:", - ].join('; ') - - // --- Create initial response --- - const response = NextResponse.next() - response.headers.set('Content-Security-Policy', cspHeader) - response.headers.set('x-nonce', nonce) // optional, expose nonce to frontend if needed - - // --- JWT Auth protection for mentorship routes --- - const protectedPaths = ['/my/mentorship'] - if (protectedPaths.some(path => request.nextUrl.pathname.startsWith(path))) { - const token = await getToken({ req: request }) - if (!token) { - const loginUrl = request.nextUrl.clone() - loginUrl.pathname = '/auth/login' - return NextResponse.redirect(loginUrl) - } +export default async function proxy(request: NextRequest) { + const token = await getToken({ req: request }) + if (!token) { + return NextResponse.redirect(new URL('/auth/login', request.url)) } - - return response + return NextResponse.next() } -// --- Configure which routes this middleware runs on --- export const config = { - matcher: ['/(.*)'], // run for all routes to apply CSP + // Protected routes. + matcher: ['/my/mentorship/:path*'], } From 1f663d84a16e5124f9baac2458502b318fec14ef Mon Sep 17 00:00:00 2001 From: nios-x Date: Sun, 11 Jan 2026 20:34:34 +0000 Subject: [PATCH 10/13] Suppress non-actionable ZAP baseline warnings for Next.js SPA --- frontend/next.config.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/next.config.ts b/frontend/next.config.ts index cdbd28932c..422c87b055 100644 --- a/frontend/next.config.ts +++ b/frontend/next.config.ts @@ -8,7 +8,7 @@ const nextConfig: NextConfig = { async headers() { return [ { - source: '/(.*)', + source: '/:path*', headers: [ { key: 'Cross-Origin-Opener-Policy', From 6ec71491c9c8eb3df4c9a03f7c5ed32bd79342c0 Mon Sep 17 00:00:00 2001 From: nios-x Date: Sun, 11 Jan 2026 20:53:29 +0000 Subject: [PATCH 11/13] Suppress non-actionable ZAP baseline warnings for Next.js SPA --- frontend/next.config.ts | 2 +- frontend/src/proxy.ts | 46 ++++++++++++++++++++++++++++++++++------- 2 files changed, 40 insertions(+), 8 deletions(-) diff --git a/frontend/next.config.ts b/frontend/next.config.ts index 422c87b055..95249cc817 100644 --- a/frontend/next.config.ts +++ b/frontend/next.config.ts @@ -8,7 +8,7 @@ const nextConfig: NextConfig = { async headers() { return [ { - source: '/:path*', + source: '/:path*', headers: [ { key: 'Cross-Origin-Opener-Policy', diff --git a/frontend/src/proxy.ts b/frontend/src/proxy.ts index 65a57d9fdc..1b4eba7777 100644 --- a/frontend/src/proxy.ts +++ b/frontend/src/proxy.ts @@ -1,16 +1,48 @@ import { NextResponse } from 'next/server' import type { NextRequest } from 'next/server' import { getToken } from 'next-auth/jwt' +import crypto from 'crypto' -export default async function proxy(request: NextRequest) { - const token = await getToken({ req: request }) - if (!token) { - return NextResponse.redirect(new URL('/auth/login', request.url)) +export async function middleware(request: NextRequest) { + // --- Exclude API and auth callback routes --- + const excludedPaths = ['/api', '/auth/callback'] + if (excludedPaths.some(p => request.nextUrl.pathname.startsWith(p))) { + return NextResponse.next() } - return NextResponse.next() + + // --- Generate a dynamic nonce for CSP --- + const nonce = crypto.randomBytes(16).toString('base64') + + // --- Build CSP header --- + const cspHeader = [ + "default-src 'self'", + `script-src 'self' 'strict-dynamic' 'nonce-${nonce}' https:`, + "style-src 'self' 'unsafe-inline'", // required for Next.js styled-jsx + "img-src 'self' data: https:", + "font-src 'self'", + "connect-src 'self' https:", + ].join('; ') + + // --- Create response with headers --- + const response = NextResponse.next() + response.headers.set('Content-Security-Policy', cspHeader) + response.headers.set('x-nonce', nonce) // optional: expose nonce to frontend + + // --- JWT Auth for mentorship routes --- + const protectedPaths = ['/my/mentorship'] + if (protectedPaths.some(path => request.nextUrl.pathname.startsWith(path))) { + const token = await getToken({ req: request }) + if (!token) { + const loginUrl = request.nextUrl.clone() + loginUrl.pathname = '/auth/login' + return NextResponse.redirect(loginUrl) + } + } + + return response } +// --- Apply middleware to all paths --- export const config = { - // Protected routes. - matcher: ['/my/mentorship/:path*'], + matcher: ['/:path*'], } From c445bb592db54584f07cc6bee765a713ce7ef11f Mon Sep 17 00:00:00 2001 From: nios-x Date: Sun, 11 Jan 2026 20:56:14 +0000 Subject: [PATCH 12/13] Suppress non-actionable ZAP baseline warnings for Next.js SPA --- frontend/src/proxy.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/proxy.ts b/frontend/src/proxy.ts index 1b4eba7777..6721f1af55 100644 --- a/frontend/src/proxy.ts +++ b/frontend/src/proxy.ts @@ -3,7 +3,7 @@ import type { NextRequest } from 'next/server' import { getToken } from 'next-auth/jwt' import crypto from 'crypto' -export async function middleware(request: NextRequest) { +export async function proxy(request: NextRequest) { // --- Exclude API and auth callback routes --- const excludedPaths = ['/api', '/auth/callback'] if (excludedPaths.some(p => request.nextUrl.pathname.startsWith(p))) { From edafea82f1a32a2a0a488fbce4a3677ccc1feaef Mon Sep 17 00:00:00 2001 From: nios-x Date: Sun, 11 Jan 2026 20:59:32 +0000 Subject: [PATCH 13/13] Suppress non-actionable ZAP baseline warnings for Next.js SPA --- frontend/src/proxy.ts | 46 +++++++------------------------------------ 1 file changed, 7 insertions(+), 39 deletions(-) diff --git a/frontend/src/proxy.ts b/frontend/src/proxy.ts index 6721f1af55..65a57d9fdc 100644 --- a/frontend/src/proxy.ts +++ b/frontend/src/proxy.ts @@ -1,48 +1,16 @@ import { NextResponse } from 'next/server' import type { NextRequest } from 'next/server' import { getToken } from 'next-auth/jwt' -import crypto from 'crypto' -export async function proxy(request: NextRequest) { - // --- Exclude API and auth callback routes --- - const excludedPaths = ['/api', '/auth/callback'] - if (excludedPaths.some(p => request.nextUrl.pathname.startsWith(p))) { - return NextResponse.next() +export default async function proxy(request: NextRequest) { + const token = await getToken({ req: request }) + if (!token) { + return NextResponse.redirect(new URL('/auth/login', request.url)) } - - // --- Generate a dynamic nonce for CSP --- - const nonce = crypto.randomBytes(16).toString('base64') - - // --- Build CSP header --- - const cspHeader = [ - "default-src 'self'", - `script-src 'self' 'strict-dynamic' 'nonce-${nonce}' https:`, - "style-src 'self' 'unsafe-inline'", // required for Next.js styled-jsx - "img-src 'self' data: https:", - "font-src 'self'", - "connect-src 'self' https:", - ].join('; ') - - // --- Create response with headers --- - const response = NextResponse.next() - response.headers.set('Content-Security-Policy', cspHeader) - response.headers.set('x-nonce', nonce) // optional: expose nonce to frontend - - // --- JWT Auth for mentorship routes --- - const protectedPaths = ['/my/mentorship'] - if (protectedPaths.some(path => request.nextUrl.pathname.startsWith(path))) { - const token = await getToken({ req: request }) - if (!token) { - const loginUrl = request.nextUrl.clone() - loginUrl.pathname = '/auth/login' - return NextResponse.redirect(loginUrl) - } - } - - return response + return NextResponse.next() } -// --- Apply middleware to all paths --- export const config = { - matcher: ['/:path*'], + // Protected routes. + matcher: ['/my/mentorship/:path*'], }