diff --git a/.jules/sentinel.md b/.jules/sentinel.md new file mode 100644 index 00000000..88b85f2c --- /dev/null +++ b/.jules/sentinel.md @@ -0,0 +1,5 @@ +## 2024-06-07 - Add Security Headers + +**Vulnerability:** Fastify server lacked explicit security headers (Helmet) and a defined CORS policy. +**Learning:** Security middleware (like `fastify-helmet` and `fastify-cors`) is crucial for defense-in-depth but must be version-matched to the core framework (Fastify v4 requires older plugin versions). +**Prevention:** Always verify the framework version in `package.json` before installing security plugins to ensure compatibility. diff --git a/apps/api/package.json b/apps/api/package.json index f30d34c2..2f48e983 100644 --- a/apps/api/package.json +++ b/apps/api/package.json @@ -9,6 +9,8 @@ "typecheck": "cd ../.. && tsc -p tsconfig.json --noEmit" }, "dependencies": { + "@fastify/cors": "^8.5.0", + "@fastify/helmet": "^11.1.1", "@vooster/contracts": "workspace:*" } } diff --git a/apps/api/src/http/server.ts b/apps/api/src/http/server.ts index c04e47f9..2827a358 100644 --- a/apps/api/src/http/server.ts +++ b/apps/api/src/http/server.ts @@ -1,3 +1,5 @@ +import fastifyCors from "@fastify/cors"; +import fastifyHelmet from "@fastify/helmet"; import Fastify, { type FastifyInstance } from "fastify"; import { healthResponseSchema } from "@vooster/contracts"; import { createMemoryApiKeyStore } from "../infrastructure/memory-api-key-store.js"; @@ -91,6 +93,10 @@ export async function createServer(options: ServerOptions): Promise healthResponseSchema.parse({ status: "ok" })); if (serverOptions.signupStore !== undefined) { app.addHook("onClose", async () => { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b8dfc0d5..5bb44afd 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -92,6 +92,12 @@ importers: apps/api: dependencies: + '@fastify/cors': + specifier: ^8.5.0 + version: 8.5.0 + '@fastify/helmet': + specifier: ^11.1.1 + version: 11.1.1 '@vooster/contracts': specifier: workspace:* version: link:../../packages/contracts @@ -951,12 +957,18 @@ packages: '@fastify/ajv-compiler@3.6.0': resolution: {integrity: sha512-LwdXQJjmMD+GwLOkP7TVC68qa+pSSogeWWmznRJ/coyTcfe9qA05AHFSe1eZFwK6q+xVRpChnvFUkf1iYaSZsQ==} + '@fastify/cors@8.5.0': + resolution: {integrity: sha512-/oZ1QSb02XjP0IK1U0IXktEsw/dUBTxJOW7IpIeO8c/tNalw/KjoNSJv1Sf6eqoBPO+TDGkifq6ynFK3v68HFQ==} + '@fastify/error@3.4.1': resolution: {integrity: sha512-wWSvph+29GR783IhmvdwWnN4bUxTD01Vm5Xad4i7i1VuAOItLvbPAb69sb0IQ2N57yprvhNIwAP5B6xfKTmjmQ==} '@fastify/fast-json-stringify-compiler@4.3.0': resolution: {integrity: sha512-aZAXGYo6m22Fk1zZzEUKBvut/CIIQe/BapEORnxiD5Qr0kPHqqI69NtEMCme74h+at72sPhbkb4ZrLd1W3KRLA==} + '@fastify/helmet@11.1.1': + resolution: {integrity: sha512-pjJxjk6SLEimITWadtYIXt6wBMfFC1I6OQyH/jYVCqSAn36sgAIFjeNiibHtifjCd+e25442pObis3Rjtame6A==} + '@fastify/merge-json-schemas@0.1.1': resolution: {integrity: sha512-fERDVz7topgNjtXsJTTW1JKLy0rhuLRcquYqNR9rF7OcVpCa2OVW49ZPDIhaRRCaUuvVxI+N416xUoF76HNSXA==} @@ -3519,6 +3531,9 @@ packages: fast-wrap-ansi@0.2.2: resolution: {integrity: sha512-7F2Fl+TjRSenLqlU3UjSH0iyqopqoZIu7eZVpEirP2g1GtWa2G/ecEmBdgz31+Mxr+ELclgg6sokpSFIQiZ02Q==} + fastify-plugin@4.5.1: + resolution: {integrity: sha512-stRHYGeuqpEZTL1Ef0Ovr2ltazUT9g844X5z/zEBFLG8RYlpDiOCIG+ATvYEp+/zmc7sN29mcIMp8gvYplYPIQ==} + fastify@4.29.1: resolution: {integrity: sha512-m2kMNHIG92tSNWv+Z3UeTR9AWLLuo7KctC7mlFPtMEVrfjIhmQhkQnT9v15qA/BfVq3vvj134Y0jl9SBje3jXQ==} @@ -3734,6 +3749,10 @@ packages: headers-polyfill@5.0.1: resolution: {integrity: sha512-1TJ6Fih/b8h5TIcv+1+Hw0PDQWJTKDKzFZzcKOiW1wJza3XoAQlkCuXLbymPYB8+ZQyw8mHvdw560e8zVFIWyA==} + helmet@7.2.0: + resolution: {integrity: sha512-ZRiwvN089JfMXokizgqEPXsl2Guk094yExfoDXR0cBYWxtBbaSww/w+vT4WEJsBW2iTUi1GgZ6swmoug3Oy4Xw==} + engines: {node: '>=16.0.0'} + hono@4.12.21: resolution: {integrity: sha512-uV63apnb0kyPtAUwoWgaGh9HyIFcv8lgmzPZSiTBQAFOFGIzka5EZ1dZocmGnn0XdX0+XTqJ6Tqv7selMuGLRQ==} engines: {node: '>=16.9.0'} @@ -4323,6 +4342,9 @@ packages: minimist@1.2.8: resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} + mnemonist@0.39.6: + resolution: {integrity: sha512-A/0v5Z59y63US00cRSLiloEIw3t5G+MiKz4BhX21FI+YBJXBOGW0ohFxTxO08dsOYlzxo87T7vGfZKYp2bcAWA==} + mrmime@2.0.1: resolution: {integrity: sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==} engines: {node: '>=10'} @@ -4456,6 +4478,9 @@ packages: resolution: {integrity: sha512-EFVjAYfzWqWsBMRHPMAXLCDIJnpMhdWAqR7xG6M6a2cs6PMFpl/+Z20w9zDW4vkxOFfddegBKq9Rehd0bxWE7A==} engines: {node: '>= 10'} + obliterator@2.0.5: + resolution: {integrity: sha512-42CPE9AhahZRsMNslczq0ctAEtqk8Eka26QofnqC346BZdHDySk3LWka23LI7ULIw11NmltpiLagIq8gBozxTw==} + obug@2.1.1: resolution: {integrity: sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==} @@ -6468,12 +6493,22 @@ snapshots: ajv-formats: 2.1.1(ajv@8.20.0) fast-uri: 2.4.0 + '@fastify/cors@8.5.0': + dependencies: + fastify-plugin: 4.5.1 + mnemonist: 0.39.6 + '@fastify/error@3.4.1': {} '@fastify/fast-json-stringify-compiler@4.3.0': dependencies: fast-json-stringify: 5.16.1 + '@fastify/helmet@11.1.1': + dependencies: + fastify-plugin: 4.5.1 + helmet: 7.2.0 + '@fastify/merge-json-schemas@0.1.1': dependencies: fast-deep-equal: 3.1.3 @@ -9165,6 +9200,8 @@ snapshots: dependencies: fast-string-width: 3.0.2 + fastify-plugin@4.5.1: {} + fastify@4.29.1: dependencies: '@fastify/ajv-compiler': 3.6.0 @@ -9457,6 +9494,8 @@ snapshots: '@types/set-cookie-parser': 2.4.10 set-cookie-parser: 3.1.0 + helmet@7.2.0: {} + hono@4.12.21: {} html-escaper@2.0.2: {} @@ -10114,6 +10153,10 @@ snapshots: minimist@1.2.8: {} + mnemonist@0.39.6: + dependencies: + obliterator: 2.0.5 + mrmime@2.0.1: {} ms@2.1.3: {} @@ -10236,6 +10279,8 @@ snapshots: object-treeify@1.1.33: {} + obliterator@2.0.5: {} + obug@2.1.1: {} ofetch@1.5.1: