Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 1 addition & 6 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -65,12 +65,7 @@ jobs:
cache: "npm"

- name: Run npm audit
run: npm audit --audit-level=moderate
continue-on-error: true

- name: Run npm audit fix
run: npm audit fix --dry-run
continue-on-error: true
run: npm audit --audit-level=high

build:
name: Build Application
Expand Down
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ COPY --from=deps /app/node_modules ./node_modules
COPY . .

# Install all dependencies (including dev) for build
RUN npm install
RUN npm install --ignore-scripts
Copy link

Copilot AI Jan 12, 2026

Choose a reason for hiding this comment

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

Adding --ignore-scripts to npm install in the build stage may prevent necessary build scripts from running. While this flag improves security by preventing arbitrary script execution, it can break packages that rely on postinstall scripts for compilation or setup.

Ensure that all dependencies work correctly without their install scripts, or consider using a more targeted approach like npm config set ignore-scripts true for specific packages only. Test the build thoroughly to verify no functionality is broken.

Suggested change
RUN npm install --ignore-scripts
RUN npm install

Copilot uses AI. Check for mistakes.

# Build the Next.js application
ENV NEXT_TELEMETRY_DISABLED=1
Expand Down
7 changes: 5 additions & 2 deletions app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { ThemeProvider } from "@/components/theme/theme-provider";
import { Toaster } from "@/components/ui/sonner";
import type { Metadata } from "next";
import { JetBrains_Mono } from "next/font/google";
import { headers } from "next/headers";
import "./globals.css";

const jetbrainsMono = JetBrains_Mono({
Expand Down Expand Up @@ -45,17 +46,19 @@ export const metadata: Metadata = {
},
};

export default function RootLayout({
export default async function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
const nonce = (await headers()).get("x-nonce") ?? undefined;

return (
<html lang="en" suppressHydrationWarning>
<body
className={`${jetbrainsMono.variable} font-sans bg-background text-foreground antialiased`}
>
<ThemeProvider>
<ThemeProvider nonce={nonce}>
<div className="noise-overlay" aria-hidden="true" />
{children}
<Toaster richColors position="bottom-center" />
Expand Down
7 changes: 6 additions & 1 deletion components/theme/theme-provider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,18 @@

import { ThemeProvider as NextThemesProvider, type ThemeProviderProps } from "next-themes";

export function ThemeProvider({ children, ...props }: ThemeProviderProps) {
interface Props extends Omit<ThemeProviderProps, "nonce"> {
nonce?: string;
}

export function ThemeProvider({ children, nonce, ...props }: Props) {
return (
<NextThemesProvider
attribute="class"
defaultTheme="system"
enableSystem
disableTransitionOnChange
nonce={nonce}
{...props}
>
{children}
Expand Down
26 changes: 26 additions & 0 deletions next.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,32 @@ import type { NextConfig } from "next";

const nextConfig: NextConfig = {
output: 'standalone',

async headers() {
return [
{
source: '/(.*)',
headers: [
{
key: 'X-Content-Type-Options',
value: 'nosniff',
},
{
key: 'X-Frame-Options',
value: 'SAMEORIGIN',
},
{
key: 'Referrer-Policy',
value: 'strict-origin-when-cross-origin',
},
{
key: 'Permissions-Policy',
value: 'camera=(), microphone=(), geolocation=()',
},
],
},
];
},
};

export default nextConfig;
40 changes: 40 additions & 0 deletions proxy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { NextRequest, NextResponse } from "next/server";

export function proxy(request: NextRequest) {
const nonce = Buffer.from(crypto.randomUUID()).toString("base64");
Comment on lines +2 to +4
Copy link

Copilot AI Jan 12, 2026

Choose a reason for hiding this comment

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

The nonce generation is not cryptographically secure. Using Buffer.from(crypto.randomUUID()).toString("base64") generates a nonce by encoding a UUID, but crypto.randomUUID() generates a UUID v4 which is intended for unique identifiers, not cryptographic nonces.

For CSP nonces, use crypto.getRandomValues() or crypto.randomBytes() to generate truly random bytes. For example: Buffer.from(crypto.getRandomValues(new Uint8Array(16))).toString("base64") or in Node.js: crypto.randomBytes(16).toString("base64").

Suggested change
export function proxy(request: NextRequest) {
const nonce = Buffer.from(crypto.randomUUID()).toString("base64");
import { randomBytes } from "crypto";
export function proxy(request: NextRequest) {
const nonce = randomBytes(16).toString("base64");

Copilot uses AI. Check for mistakes.
const isDev = process.env.NODE_ENV === "development";

const cspHeader = `
default-src 'self';
script-src 'self' 'nonce-${nonce}' 'strict-dynamic' ${isDev ? "'unsafe-eval'" : ""};
style-src 'self' ${isDev ? "'unsafe-inline'" : `'nonce-${nonce}'`};
img-src 'self' blob: data:;
font-src 'self';
object-src 'none';
base-uri 'self';
form-action 'self';
frame-ancestors 'none';
Copy link

Copilot AI Jan 12, 2026

Choose a reason for hiding this comment

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

The CSP header does not include connect-src directive, which defaults to default-src 'self'. This might be too restrictive if the application needs to make API calls to external services. Consider explicitly setting connect-src to match your application's requirements.

Additionally, consider adding other security directives like worker-src, manifest-src, and media-src if the application uses service workers, web manifests, or media resources.

Suggested change
frame-ancestors 'none';
frame-ancestors 'none';
connect-src 'self';
worker-src 'self';
manifest-src 'self';
media-src 'self';

Copilot uses AI. Check for mistakes.
upgrade-insecure-requests;
`
.replace(/\s{2,}/g, " ")
.trim();

const requestHeaders = new Headers(request.headers);
requestHeaders.set("x-nonce", nonce);
requestHeaders.set("Content-Security-Policy", cspHeader);

const response = NextResponse.next({
request: { headers: requestHeaders },
});

response.headers.set("Content-Security-Policy", cspHeader);

return response;
}

export const config = {
matcher: [
// Match all paths except static files
"/((?!_next/static|_next/image|favicon.ico|icon.svg|.*\\.(?:svg|png|jpg|jpeg|gif|webp)$).*)",
],
};
29 changes: 1 addition & 28 deletions vercel.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,33 +3,6 @@
"buildCommand": "npm run build",
"devCommand": "npm run dev",
"installCommand": "npm install",
"framework": "nextjs",
"headers": [
{
"source": "/(.*)",
"headers": [
{
"key": "X-Content-Type-Options",
"value": "nosniff"
},
{
"key": "X-Frame-Options",
"value": "SAMEORIGIN"
},
{
"key": "X-XSS-Protection",
"value": "1; mode=block"
},
{
"key": "Referrer-Policy",
"value": "strict-origin-when-cross-origin"
},
{
"key": "Permissions-Policy",
"value": "camera=(), microphone=(), geolocation=()"
}
]
}
]
"framework": "nextjs"
}

Loading