From f2d5eb2af38ef89fadda7217808ad08b00779577 Mon Sep 17 00:00:00 2001 From: Stephen Powell Date: Thu, 27 Nov 2025 09:30:46 +0000 Subject: [PATCH 1/3] exisitng fix required --- src/server/declaration/declaration.controller.test.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/server/declaration/declaration.controller.test.js b/src/server/declaration/declaration.controller.test.js index 905e1603..184b3793 100644 --- a/src/server/declaration/declaration.controller.test.js +++ b/src/server/declaration/declaration.controller.test.js @@ -16,6 +16,10 @@ vi.mock('../common/helpers/logging/log.js', () => ({ LogCodes: { SUBMISSION: { SUBMISSION_FAILURE: { level: 'error', messageFunc: vi.fn() } + }, + SYSTEM: { + EXTERNAL_API_CALL_DEBUG: { level: 'debug', messageFunc: vi.fn() }, + EXTERNAL_API_ERROR: { level: 'error', messageFunc: vi.fn() } } } })) From 1ed8c047f4ddb0e9dfe8627285470e6a33c0ff11 Mon Sep 17 00:00:00 2001 From: Stephen Powell Date: Thu, 27 Nov 2025 09:34:11 +0000 Subject: [PATCH 2/3] add static route to prevent img alerts --- README.md | 19 +++++ .../common/helpers/serve-static-files.js | 9 ++ .../common/helpers/serve-static-files.test.js | 84 ++++++++++--------- 3 files changed, 74 insertions(+), 38 deletions(-) diff --git a/README.md b/README.md index 7ae035f3..f99d8fcf 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,7 @@ Core delivery platform Node.js Frontend Template. - [Requirements](#requirements) - [Node.js](#nodejs) - [DXT Forms Engine Plugin](#dxt-forms-engine-plugin) +- [Static File Routes & Accessibility Scanner Support](#static-file-routes--accessibility-scanner-support) - [Features](#features) - [Development Tools & Configuration](#development-tools--configuration) - [Testing Framework](#testing-framework) @@ -144,6 +145,24 @@ sequenceDiagram Ctrl-->>User: Redirect to confirmation ``` +## Static File Routes & Accessibility Scanner Support + +The CDP dev environment runs automated accessibility testing using HeadlessChrome. This scanner attempts to load icon resources (e.g., `aria.svg`, `header.svg`, `h1.svg`, etc.) to visualise accessibility features on the page. These icons are not part of the application itself but are used by the scanner's visualisation overlay. + +To prevent 400 errors in logs from these scanner requests, a catch-all route was added in `src/server/common/helpers/serve-static-files.js` that: + +- Intercepts all `/img/{param*}` requests +- Returns a valid empty SVG with 200 OK status +- Satisfies the accessibility scanner without cluttering logs with errors + +This ensures clean logs while maintaining compatibility with CDP platform accessibility monitoring tools. + +Test locally with: + +```bash +curl -v http://localhost:3000/img/icons/aria.svg` +``` + ## Development Tools & Configuration ### Testing Framework diff --git a/src/server/common/helpers/serve-static-files.js b/src/server/common/helpers/serve-static-files.js index 5495538a..999d1149 100644 --- a/src/server/common/helpers/serve-static-files.js +++ b/src/server/common/helpers/serve-static-files.js @@ -44,6 +44,15 @@ export const serveStaticFiles = { }, options }, + { + method: 'GET', + path: '/img/{param*}', + handler(_request, h) { + const emptySvg = '' + return h.response(emptySvg).code(statusCodes.ok).type('image/svg+xml') + }, + options + }, { method: 'GET', path: `${config.get('assetPath')}/{param*}`, diff --git a/src/server/common/helpers/serve-static-files.test.js b/src/server/common/helpers/serve-static-files.test.js index 2729d978..38e2a169 100644 --- a/src/server/common/helpers/serve-static-files.test.js +++ b/src/server/common/helpers/serve-static-files.test.js @@ -1,54 +1,62 @@ import { vi } from 'vitest' +import { mockHapiServer, mockHapiResponseToolkit } from '~/src/__mocks__/hapi-mocks.js' import { statusCodes } from '~/src/server/common/constants/status-codes.js' -import { startServer } from '~/src/server/common/helpers/start-server.js' -import Wreck from '@hapi/wreck' -vi.mock('@hapi/wreck') -vi.mock('~/src/server/common/helpers/logging/log.js', () => ({})) +vi.mock('~/src/config/config.js', () => ({ + config: { + get: vi.fn((key) => { + switch (key) { + case 'staticCacheTimeout': + return 86400000 + case 'assetPath': + return '/public' + default: + return undefined + } + }) + } +})) + +const { serveStaticFiles } = await import('./serve-static-files.js') -describe('#serveStaticFiles', () => { +describe('serveStaticFiles Plugin', () => { let server + let mockH - describe('When secure context is disabled', () => { - beforeEach(async () => { - // Mock the well-known OIDC config before server starts - vi.mocked(Wreck.get).mockResolvedValue({ - payload: { - authorization_endpoint: 'https://mock-auth/authorize', - token_endpoint: 'https://mock-auth/token' - } - }) - - server = await startServer() - }) + beforeEach(() => { + vi.clearAllMocks() + server = mockHapiServer() + mockH = mockHapiResponseToolkit() + }) - afterEach(async () => { - if (server && typeof server.stop === 'function') { - await server.stop({ timeout: 0 }) - } - }) + describe('/favicon.ico route handler', () => { + test('should return 204 No Content with image/x-icon type', () => { + serveStaticFiles.plugin.register(server) - test('Should serve favicon as expected', async () => { - expect(server).toBeDefined() - expect(typeof server.inject).toBe('function') + const routes = server.route.mock.calls[0][0] + const faviconRoute = routes.find((r) => r.path === '/favicon.ico') - const { statusCode } = await server.inject({ - method: 'GET', - url: '/favicon.ico' - }) + faviconRoute.handler({}, mockH) - expect(statusCode).toBe(statusCodes.ok) + expect(mockH.response).toHaveBeenCalledWith() + expect(mockH.code).toHaveBeenCalledWith(statusCodes.noContent) + expect(mockH.type).toHaveBeenCalledWith('image/x-icon') }) + }) + + describe('/img/{param*} route handler', () => { + test('should return empty SVG with 200 OK', () => { + serveStaticFiles.plugin.register(server) + + const routes = server.route.mock.calls[0][0] + const imgRoute = routes.find((r) => r.path === '/img/{param*}') - test('Should serve assets as expected', async () => { - // Note npm run build is ran in the postinstall hook in package.json to make sure there is always a file - // available for this test. Remove as you see fit - const { statusCode } = await server.inject({ - method: 'GET', - url: '/public/assets/images/govuk-crest.svg' - }) + imgRoute.handler({}, mockH) - expect(statusCode).toBe(statusCodes.ok) + const expectedSvg = '' + expect(mockH.response).toHaveBeenCalledWith(expectedSvg) + expect(mockH.code).toHaveBeenCalledWith(statusCodes.ok) + expect(mockH.type).toHaveBeenCalledWith('image/svg+xml') }) }) }) From 85dcc16da8b4d7476c9d06409c38a0690c25cc99 Mon Sep 17 00:00:00 2001 From: Stephen Powell Date: Thu, 27 Nov 2025 09:50:17 +0000 Subject: [PATCH 3/3] fix security issue in snyk --- package-lock.json | 39 +++------------------------------------ package.json | 3 ++- 2 files changed, 5 insertions(+), 37 deletions(-) diff --git a/package-lock.json b/package-lock.json index f0fd0823..bc939854 100644 --- a/package-lock.json +++ b/package-lock.json @@ -204,7 +204,6 @@ "integrity": "sha512-yDBHV9kQNcr2/sUr9jghVyz9C3Y5G2zUM2H2lo+9mKv4sFgbA8s8Z9t8D1jiTkGoO/NoIfKMyKWr4s6CN23ZwQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.27.1", @@ -1906,7 +1905,6 @@ "integrity": "sha512-eohl3hKTiVyD1ilYdw9T0OiB4hnjef89e3dMYKz+mVKDzj+5IteTseASUsOB+EU9Tf6VNTCjDePcP6wkDGmLKQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@keyv/serialize": "^1.1.1" } @@ -2020,7 +2018,6 @@ } ], "license": "MIT", - "peer": true, "engines": { "node": ">=18" }, @@ -2044,7 +2041,6 @@ } ], "license": "MIT", - "peer": true, "engines": { "node": ">=18" } @@ -5627,7 +5623,6 @@ "integrity": "sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@types/estree": "*", "@types/json-schema": "*" @@ -5719,7 +5714,6 @@ "integrity": "sha512-rzSDyhn4cYznVG+PCzGe1lwuMYJrcBS1fc3JqSa2PvtABwWo+dZ1ij5OVok3tqfpEBCBoaR4d7upFJk73HRJDw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "undici-types": "~6.21.0" } @@ -5779,7 +5773,6 @@ "integrity": "sha512-r1XG74QgShUgXph1BYseJ+KZd17bKQib/yF3SR+demvytiRXrwd12Blnz5eYGm8tXaeRdd4x88MlfwldHoudGg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.42.0", "@typescript-eslint/types": "8.42.0", @@ -6445,7 +6438,6 @@ "integrity": "sha512-hGISOaP18plkzbWEcP/QvtRW1xDXF2+96HbEX6byqQhAUbiS5oH6/9JwW+QsQCIYON2bI6QZBF+2PvOmrRZ9wA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@vitest/utils": "3.2.4", "fflate": "^0.8.2", @@ -6757,7 +6749,6 @@ "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "dev": true, "license": "MIT", - "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -6802,7 +6793,6 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", "license": "MIT", - "peer": true, "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", @@ -7639,7 +7629,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "baseline-browser-mapping": "^2.8.25", "caniuse-lite": "^1.0.30001754", @@ -7957,7 +7946,6 @@ "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", "devOptional": true, "license": "MIT", - "peer": true, "dependencies": { "anymatch": "~3.1.2", "braces": "~3.0.2", @@ -9406,7 +9394,6 @@ "integrity": "sha512-RNCHRX5EwdrESy3Jc9o8ie8Bog+PeYvvSR8sDGoZxNFTvZ4dlxUB3WzQ3bQMztFrSRODGrLLj8g6OFuGY/aiQg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.12.1", @@ -9484,7 +9471,6 @@ "integrity": "sha512-iI1f+D2ViGn+uvv5HuHVUamg8ll4tN+JRHGc6IJi4TP9Kl976C57fzPXgseXNs8v0iA8aSJpHsTWjDb9QJamGQ==", "dev": true, "license": "MIT", - "peer": true, "bin": { "eslint-config-prettier": "bin/cli.js" }, @@ -9664,7 +9650,6 @@ "integrity": "sha512-vPZZsiOKaBAIATpFE2uMI4w5IRwdv/FpQ+qZZMR4E+PeOcM4OeoEbqxRMnywdxP19TyB/3h6QBB0EWon7letSQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@typescript-eslint/types": "^8.35.0", "comment-parser": "^1.4.1", @@ -11168,7 +11153,6 @@ "integrity": "sha512-DKKrynuQRne0PNpEbzuEdHlYOMksHSUI8Zc9Unei5gTsMNA2/vMpoMz/yKba50pejK56qj98qM0SjYxAKi13gQ==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": "^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0" } @@ -12567,7 +12551,6 @@ "integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==", "dev": true, "license": "MIT", - "peer": true, "bin": { "jiti": "lib/jiti-cli.mjs" } @@ -12577,7 +12560,6 @@ "resolved": "https://registry.npmjs.org/joi/-/joi-17.13.3.tgz", "integrity": "sha512-otDA4ldcIx+ZXsKHWmp0YizCweVRZG96J10b0FevjfuncLO1oX59THoAmHkNubYJ+9gWsYsp5k8v4ib6oDv1fA==", "license": "BSD-3-Clause", - "peer": true, "dependencies": { "@hapi/hoek": "^9.3.0", "@hapi/topo": "^5.1.0", @@ -12660,7 +12642,6 @@ "integrity": "sha512-Cvc9WUhxSMEo4McES3P7oK3QaXldCfNWp7pl2NNeiIFlCoLr3kfq9kb1fxftiwk1FLV7CvpvDfonxtzUDeSOPg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "cssstyle": "^4.2.1", "data-urls": "^5.0.0", @@ -13580,9 +13561,9 @@ } }, "node_modules/node-forge": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz", - "integrity": "sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==", + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.2.tgz", + "integrity": "sha512-6xKiQ+cph9KImrRh0VsjH2d8/GXA4FIMlgU4B757iI1ApvcyA9VlouP0yZJha01V+huImO+kKMU7ih+2+E14fw==", "license": "(BSD-3-Clause OR GPL-2.0)", "engines": { "node": ">= 6.13.0" @@ -14895,7 +14876,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", @@ -15433,7 +15413,6 @@ "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz", "integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==", "license": "MIT", - "peer": true, "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" @@ -15513,7 +15492,6 @@ "integrity": "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==", "dev": true, "license": "MIT", - "peer": true, "bin": { "prettier": "bin/prettier.cjs" }, @@ -16348,7 +16326,6 @@ "dev": true, "license": "MIT", "optional": true, - "peer": true, "dependencies": { "chokidar": "^4.0.0", "immutable": "^5.0.2", @@ -16370,7 +16347,6 @@ "integrity": "sha512-daqnoAA+AmXvcL1fvJRMd4RDPZM2s27qYxb51c5TYc1B1Zugu0gVGyA5leoXQJEzo6sDTQ95J8X0yFcdBNGNtw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@bufbuild/protobuf": "^2.5.0", "buffer-builder": "^0.2.0", @@ -17808,7 +17784,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "@csstools/css-parser-algorithms": "^3.0.5", "@csstools/css-tokenizer": "^3.0.4", @@ -18251,7 +18226,6 @@ "integrity": "sha512-t/R3R/n0MSwnnazuPpPNVO60LX0SKL45pyl9YlvxIdkH0Of7D5qM2EVe+yASRIlY5pZ73nclYJfNANGWPwFDZw==", "dev": true, "license": "BSD-2-Clause", - "peer": true, "dependencies": { "@jridgewell/source-map": "^0.3.3", "acorn": "^8.15.0", @@ -18450,7 +18424,6 @@ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -18683,7 +18656,6 @@ "integrity": "sha512-+wKjMNU9w/EaQayHXb7WA7ZaHY6hN8WgfvHNQ3t1PnU91/7O8TcTnIhCDYTZwnt8JsO9IBqZ30Ln1r7pPF52Aw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "esbuild": "~0.25.0", "get-tsconfig": "^4.7.5" @@ -18858,7 +18830,6 @@ "integrity": "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==", "dev": true, "license": "Apache-2.0", - "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -18927,7 +18898,6 @@ "integrity": "sha512-lJi3PfxVmo0AkEY93ecfN+r8SofEqZNGByvHAI3GBLrvt1Cw6H5k1IM02nSzu0RfUafr2EvFSw0wAsZgubNplQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.47.0", "@typescript-eslint/types": "8.47.0", @@ -19567,7 +19537,6 @@ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -19581,7 +19550,6 @@ "integrity": "sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@types/chai": "^5.2.2", "@vitest/expect": "3.2.4", @@ -19731,7 +19699,6 @@ "integrity": "sha512-7b0dTKR3Ed//AD/6kkx/o7duS8H3f1a4w3BYpIriX4BzIhjkn4teo05cptsxvLesHFKK5KObnadmCHBwGc+51A==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@types/eslint-scope": "^3.7.7", "@types/estree": "^1.0.8", diff --git a/package.json b/package.json index 8ab9bd69..d190b438 100644 --- a/package.json +++ b/package.json @@ -60,7 +60,8 @@ "author": "Defra DDTS", "license": "OGL-UK-3.0", "overrides": { - "tmp": "0.2.4" + "tmp": "0.2.4", + "node-forge": "1.3.2" }, "dependencies": { "@babel/runtime": "7.28.4",