diff --git a/dbal/cpp/src/daemon/http/security_limits.hpp b/dbal/cpp/src/daemon/http/security_limits.hpp new file mode 100644 index 000000000..5b099b730 --- /dev/null +++ b/dbal/cpp/src/daemon/http/security_limits.hpp @@ -0,0 +1,28 @@ +/** + * @file security_limits.hpp + * @brief Security constants and limits for HTTP server + * + * Defines limits to prevent CVE-style attacks. + */ +#ifndef DBAL_SECURITY_LIMITS_HPP +#define DBAL_SECURITY_LIMITS_HPP + +#include + +namespace dbal { +namespace daemon { +namespace http { + +// Security limits to prevent CVE-style attacks +constexpr size_t MAX_REQUEST_SIZE = 65536; // 64KB max request (prevent buffer overflow) +constexpr size_t MAX_HEADERS = 100; // Max 100 headers (prevent header bomb) +constexpr size_t MAX_HEADER_SIZE = 8192; // 8KB max per header +constexpr size_t MAX_PATH_LENGTH = 2048; // Max URL path length +constexpr size_t MAX_BODY_SIZE = 10485760; // 10MB max body size +constexpr size_t MAX_CONCURRENT_CONNECTIONS = 1000; // Prevent thread exhaustion + +} // namespace http +} // namespace daemon +} // namespace dbal + +#endif diff --git a/frontends/nextjs/src/app/packages/[...path]/route.ts b/frontends/nextjs/src/app/packages/[...path]/route.ts new file mode 100644 index 000000000..f8ece3ca6 --- /dev/null +++ b/frontends/nextjs/src/app/packages/[...path]/route.ts @@ -0,0 +1,35 @@ +import { NextResponse } from 'next/server' +import { readFile, stat } from 'fs/promises' +import { getPackageContentType } from '@/lib/packages/server/get-package-content-type' +import { resolvePackageFilePath } from '@/lib/packages/server/resolve-package-file-path' + +interface PackageParams { + path?: string[] +} + +export async function GET(request: Request, { params }: { params: PackageParams }) { + const segments = params.path ?? [] + const filePath = resolvePackageFilePath(segments) + + if (!filePath) { + return NextResponse.json({ error: 'Not found' }, { status: 404 }) + } + + try { + const fileStat = await stat(filePath) + if (!fileStat.isFile()) { + return NextResponse.json({ error: 'Not found' }, { status: 404 }) + } + + const fileBuffer = await readFile(filePath) + const contentType = getPackageContentType(filePath) + + return new NextResponse(fileBuffer, { + headers: { + 'Content-Type': contentType, + }, + }) + } catch (error) { + return NextResponse.json({ error: 'Not found' }, { status: 404 }) + } +} diff --git a/frontends/nextjs/src/lib/packages/server/resolve-package-file-path.test.ts b/frontends/nextjs/src/lib/packages/server/resolve-package-file-path.test.ts new file mode 100644 index 000000000..40fda3f5b --- /dev/null +++ b/frontends/nextjs/src/lib/packages/server/resolve-package-file-path.test.ts @@ -0,0 +1,20 @@ +import { describe, expect, it } from 'vitest' +import { resolvePackageFilePath } from './resolve-package-file-path' + +const root = '/repo/packages' + +describe('resolvePackageFilePath', () => { + it('returns null for empty path', () => { + expect(resolvePackageFilePath([], root)).toBeNull() + }) + + it('returns resolved path for valid segments', () => { + expect(resolvePackageFilePath(['social_hub', 'seed', 'metadata.json'], root)).toBe( + '/repo/packages/social_hub/seed/metadata.json' + ) + }) + + it('blocks path traversal', () => { + expect(resolvePackageFilePath(['..', 'secrets.txt'], root)).toBeNull() + }) +})