diff --git a/agents/application-security-architect.agent.md b/agents/application-security-architect.agent.md index aeba868..3f418b3 100644 --- a/agents/application-security-architect.agent.md +++ b/agents/application-security-architect.agent.md @@ -1,7 +1,7 @@ --- name: application-security-architect description: Designs secure architectures and guardrails. Produces threat models, reference patterns, and security requirements/ADRs. -tools: ['vscode', 'execute', 'read', 'edit', 'search', 'web', 'todo'] +tools: ['vscode', 'execute', 'read', 'edit', 'search', 'web', 'mermaidchart.vscode-mermaid-chart/get_syntax_docs', 'mermaidchart.vscode-mermaid-chart/mermaid-diagram-validator', 'mermaidchart.vscode-mermaid-chart/mermaid-diagram-preview', 'todo'] model: GPT-5.2 --- diff --git a/package-lock.json b/package-lock.json index aa56012..a3b2640 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,10 +10,10 @@ "license": "ISC", "dependencies": { "@modelcontextprotocol/sdk": "^1.17.2", - "ajv": "^8.17.1", "amqplib": "^0.10.8", "dotenv": "^17.2.1", "express": "^5.1.0", + "express-rate-limit": "^8.2.1", "mysql2": "^3.14.3", "sequelize": "^6.37.7", "ulid": "^3.0.1", @@ -51,9 +51,9 @@ } }, "node_modules/@eslint-community/eslint-utils": { - "version": "4.7.0", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.7.0.tgz", - "integrity": "sha512-dyybb3AcajC7uha6CvhdVRJqaKyn7w2YKqKyAN37NKYgZT36w+iRb0Dymmc5qEJ549c/S31cMMSFd75bteCpCw==", + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", + "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==", "dev": true, "license": "MIT", "dependencies": { @@ -93,13 +93,13 @@ } }, "node_modules/@eslint/config-array": { - "version": "0.21.0", - "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.0.tgz", - "integrity": "sha512-ENIdc4iLu0d93HeYirvKmrzshzofPw6VkZRKQGe9Nv46ZnWUzcF1xV01dcvEg/1wXUR61OmmlSfyeyO7EvjLxQ==", + "version": "0.21.1", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.1.tgz", + "integrity": "sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@eslint/object-schema": "^2.1.6", + "@eslint/object-schema": "^2.1.7", "debug": "^4.3.1", "minimatch": "^3.1.2" }, @@ -108,19 +108,22 @@ } }, "node_modules/@eslint/config-helpers": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.3.1.tgz", - "integrity": "sha512-xR93k9WhrDYpXHORXpxVL5oHj3Era7wo6k/Wd8/IsQNnZUTzkGS29lyn3nAT05v6ltUuTFVCCYDEGfy2Or/sPA==", + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.2.tgz", + "integrity": "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==", "dev": true, "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.17.0" + }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, "node_modules/@eslint/core": { - "version": "0.15.2", - "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.15.2.tgz", - "integrity": "sha512-78Md3/Rrxh83gCxoUc0EiciuOHsIITzLy53m3d9UyiW8y9Dj2D29FeETqyKA+BRK76tnTp6RXWb3pCay8Oyomg==", + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz", + "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -131,9 +134,9 @@ } }, "node_modules/@eslint/eslintrc": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.1.tgz", - "integrity": "sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==", + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.3.tgz", + "integrity": "sha512-Kr+LPIUVKz2qkx1HAMH8q1q6azbqBAsXJUxBl/ODDuVPX45Z9DfwB8tPjTi6nNZ8BuM3nbJxC5zCAg5elnBUTQ==", "dev": true, "license": "MIT", "dependencies": { @@ -143,7 +146,7 @@ "globals": "^14.0.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", - "js-yaml": "^4.1.0", + "js-yaml": "^4.1.1", "minimatch": "^3.1.2", "strip-json-comments": "^3.1.1" }, @@ -155,9 +158,9 @@ } }, "node_modules/@eslint/eslintrc/node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz", + "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==", "dev": true, "license": "MIT", "dependencies": { @@ -192,9 +195,9 @@ "license": "MIT" }, "node_modules/@eslint/js": { - "version": "9.34.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.34.0.tgz", - "integrity": "sha512-EoyvqQnBNsV1CWaEJ559rxXL4c8V92gxirbawSmVUOWXlsRxxQXl6LmCpdUblgxgSkDIqKnhzba2SjRTI/A5Rw==", + "version": "9.39.2", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.2.tgz", + "integrity": "sha512-q1mjIoW1VX4IvSocvM/vbTiveKC4k9eLrajNEuSsmjymSDEbpGddtpfOoN7YGAqBK3NG+uqo8ia4PDTt8buCYA==", "dev": true, "license": "MIT", "engines": { @@ -205,9 +208,9 @@ } }, "node_modules/@eslint/object-schema": { - "version": "2.1.6", - "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.6.tgz", - "integrity": "sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==", + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.7.tgz", + "integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==", "dev": true, "license": "Apache-2.0", "engines": { @@ -215,13 +218,13 @@ } }, "node_modules/@eslint/plugin-kit": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.3.5.tgz", - "integrity": "sha512-Z5kJ+wU3oA7MMIqVR9tyZRtjYPr4OC004Q4Rw7pgOKUOKkJfZ3O24nz3WYfGRpMDNmcOi3TwQOmgm7B7Tpii0w==", + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz", + "integrity": "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@eslint/core": "^0.15.2", + "@eslint/core": "^0.17.0", "levn": "^0.4.1" }, "engines": { @@ -306,36 +309,13 @@ "url": "https://github.com/sponsors/nzakas" } }, - "node_modules/@isaacs/balanced-match": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@isaacs/balanced-match/-/balanced-match-4.0.1.tgz", - "integrity": "sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": "20 || >=22" - } - }, - "node_modules/@isaacs/brace-expansion": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/@isaacs/brace-expansion/-/brace-expansion-5.0.0.tgz", - "integrity": "sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@isaacs/balanced-match": "^4.0.1" - }, - "engines": { - "node": "20 || >=22" - } - }, "node_modules/@modelcontextprotocol/sdk": { - "version": "1.25.2", - "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.25.2.tgz", - "integrity": "sha512-LZFeo4F9M5qOhC/Uc1aQSrBHxMrvxett+9KLHt7OhcExtoiRN9DKgbZffMP/nxjutWDQpfMDfP3nkHI4X9ijww==", + "version": "1.26.0", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.26.0.tgz", + "integrity": "sha512-Y5RmPncpiDtTXDbLKswIJzTqu2hyBKxTNsgKqKclDbhIgg1wgtf1fRuvxgTnRfcnxtvvgbIEcqUOzZrJ6iSReg==", "license": "MIT", "dependencies": { - "@hono/node-server": "^1.19.7", + "@hono/node-server": "^1.19.9", "ajv": "^8.17.1", "ajv-formats": "^3.0.1", "content-type": "^1.0.5", @@ -343,14 +323,15 @@ "cross-spawn": "^7.0.5", "eventsource": "^3.0.2", "eventsource-parser": "^3.0.0", - "express": "^5.0.1", - "express-rate-limit": "^7.5.0", - "jose": "^6.1.1", + "express": "^5.2.1", + "express-rate-limit": "^8.2.1", + "hono": "^4.11.4", + "jose": "^6.1.3", "json-schema-typed": "^8.0.2", "pkce-challenge": "^5.0.0", "raw-body": "^3.0.0", "zod": "^3.25 || ^4.0", - "zod-to-json-schema": "^3.25.0" + "zod-to-json-schema": "^3.25.1" }, "engines": { "node": ">=18" @@ -465,6 +446,7 @@ "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "dev": true, "license": "MIT", + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -483,9 +465,9 @@ } }, "node_modules/ajv": { - "version": "8.17.1", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", - "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", + "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.3", @@ -1546,25 +1528,25 @@ } }, "node_modules/eslint": { - "version": "9.34.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.34.0.tgz", - "integrity": "sha512-RNCHRX5EwdrESy3Jc9o8ie8Bog+PeYvvSR8sDGoZxNFTvZ4dlxUB3WzQ3bQMztFrSRODGrLLj8g6OFuGY/aiQg==", + "version": "9.39.2", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.2.tgz", + "integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { - "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", - "@eslint/config-array": "^0.21.0", - "@eslint/config-helpers": "^0.3.1", - "@eslint/core": "^0.15.2", + "@eslint/config-array": "^0.21.1", + "@eslint/config-helpers": "^0.4.2", + "@eslint/core": "^0.17.0", "@eslint/eslintrc": "^3.3.1", - "@eslint/js": "9.34.0", - "@eslint/plugin-kit": "^0.3.5", + "@eslint/js": "9.39.2", + "@eslint/plugin-kit": "^0.4.1", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", - "@types/json-schema": "^7.0.15", "ajv": "^6.12.4", "chalk": "^4.0.0", "cross-spawn": "^7.0.6", @@ -1741,9 +1723,9 @@ } }, "node_modules/eslint/node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz", + "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==", "dev": true, "license": "MIT", "dependencies": { @@ -1859,18 +1841,20 @@ } }, "node_modules/express": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/express/-/express-5.1.0.tgz", - "integrity": "sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA==", + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz", + "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==", "license": "MIT", + "peer": true, "dependencies": { "accepts": "^2.0.0", - "body-parser": "^2.2.0", + "body-parser": "^2.2.1", "content-disposition": "^1.0.0", "content-type": "^1.0.5", "cookie": "^0.7.1", "cookie-signature": "^1.2.1", "debug": "^4.4.0", + "depd": "^2.0.0", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "etag": "^1.8.1", @@ -1901,10 +1885,13 @@ } }, "node_modules/express-rate-limit": { - "version": "7.5.1", - "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-7.5.1.tgz", - "integrity": "sha512-7iN8iPMDzOMHPUYllBEsQdWVB6fPDMPqwjBaFrgr4Jgr/+okjvzAy+UHlYYL/Vs0OsOrMkwS6PJDkFlJwoxUnw==", + "version": "8.2.1", + "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.2.1.tgz", + "integrity": "sha512-PCZEIEIxqwhzw4KF0n7QF4QqruVTcF73O5kFKUnGOyjbCCgizBBiFaYpd/fnBLUMPw/BWw9OsiN7GgrNYr7j6g==", "license": "MIT", + "dependencies": { + "ip-address": "10.0.1" + }, "engines": { "node": ">= 16" }, @@ -2345,9 +2332,9 @@ } }, "node_modules/hono": { - "version": "4.11.4", - "resolved": "https://registry.npmjs.org/hono/-/hono-4.11.4.tgz", - "integrity": "sha512-U7tt8JsyrxSRKspfhtLET79pU8K+tInj5QZXs1jSugO1Vq5dFj3kmZsRldo29mTBfcjDRVRXrEZ6LS63Cog9ZA==", + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/hono/-/hono-4.12.0.tgz", + "integrity": "sha512-NekXntS5M94pUfiVZ8oXXK/kkri+5WpX2/Ik+LVsl+uvw+soj4roXIsPqO+XsWrAw20mOzaXOZf3Q7PfB9A/IA==", "license": "MIT", "peer": true, "engines": { @@ -2470,6 +2457,15 @@ "node": ">= 0.4" } }, + "node_modules/ip-address": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.0.1.tgz", + "integrity": "sha512-NWv9YLW4PoW2B7xtzaS3NCot75m6nK7Icdv0o3lfMceJVRfSoQwqD4wEH5rLwoKJwUiZ/rfpiVBhnaF0FK4HoA==", + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, "node_modules/ipaddr.js": { "version": "1.9.1", "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", @@ -3136,9 +3132,9 @@ } }, "node_modules/lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "version": "4.17.23", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz", + "integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==", "license": "MIT" }, "node_modules/lodash.merge": { @@ -3196,9 +3192,9 @@ } }, "node_modules/markdown-it": { - "version": "14.1.0", - "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.1.0.tgz", - "integrity": "sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg==", + "version": "14.1.1", + "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.1.1.tgz", + "integrity": "sha512-BuU2qnTti9YKgK5N+IeMubp14ZUKUUw7yeJbkjtosvHiP0AZ5c8IAgEMk79D0eC8F23r4Ac/q8cAIFdm2FtyoA==", "dev": true, "license": "MIT", "dependencies": { @@ -3263,6 +3259,29 @@ "node": ">=20" } }, + "node_modules/markdownlint-cli/node_modules/balanced-match": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.3.tgz", + "integrity": "sha512-1pHv8LX9CpKut1Zp4EXey7Z8OfH11ONNH6Dhi2WDUt31VVZFXZzKwXcysBgqSumFCmR+0dqjMK5v5JiFHzi0+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/markdownlint-cli/node_modules/brace-expansion": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.2.tgz", + "integrity": "sha512-Pdk8c9poy+YhOgVWw1JNN22/HcivgKWwpxKq04M/jTmHyCZn12WPJebZxdjSa5TmBqISrUSgNYU3eRORljfCCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "20 || >=22" + } + }, "node_modules/markdownlint-cli/node_modules/ignore": { "version": "7.0.5", "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", @@ -3274,13 +3293,13 @@ } }, "node_modules/markdownlint-cli/node_modules/minimatch": { - "version": "10.1.1", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.1.1.tgz", - "integrity": "sha512-enIvLvRAFZYXJzkCYG5RKmPfrFArdLv+R+lbQ53BmIMLIry74bjKzX6iHAm8WYamJkhSSEabrWN5D97XnKObjQ==", + "version": "10.1.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.1.3.tgz", + "integrity": "sha512-IF6URNyBX7Z6XfvjpaNy5meRxPZiIf2OqtOoSLs+hLJ9pJAScnM1RjrFcbCaD85y42KcI+oZmKjFIJKYDFjQfg==", "dev": true, "license": "BlueOak-1.0.0", "dependencies": { - "@isaacs/brace-expansion": "^5.0.0" + "brace-expansion": "^5.0.2" }, "engines": { "node": "20 || >=22" @@ -3884,9 +3903,9 @@ } }, "node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.4.tgz", + "integrity": "sha512-twmL+S8+7yIsE9wsqgzU3E8/LumN3M3QELrBZ20OdmQ9jB2JvW5oZtBEmft84k/Gs5CG9mqtWc6Y9vW+JEzGxw==", "dev": true, "license": "ISC", "dependencies": { @@ -3982,9 +4001,9 @@ } }, "node_modules/nodemon": { - "version": "3.1.10", - "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.10.tgz", - "integrity": "sha512-WDjw3pJ0/0jMFmyNDp3gvY2YizjLmmOUQo6DEBY+JgdvW/yQ9mEeSw6H5ythl5Ny2ytb7f9C2nIbjSxMNzbJXw==", + "version": "3.1.11", + "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.11.tgz", + "integrity": "sha512-is96t8F/1//UHAjNPHpbsNY46ELPpftGUoSVNXwUfMk/qdjSylYrWSu1XavVTBOn526kFiOR733ATgNBCQyH0g==", "dev": true, "license": "MIT", "dependencies": { @@ -4412,9 +4431,9 @@ } }, "node_modules/qs": { - "version": "6.14.1", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.1.tgz", - "integrity": "sha512-4EK3+xJl8Ts67nLYNwqw/dsFVnCf+qR7RgXSK9jEEm9unao3njwMDdmsdvoKBKHzxd7tCYz5e5M+SnMjdtXGQQ==", + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.0.tgz", + "integrity": "sha512-mAZTtNCeetKMH+pSjrb76NAM8V9a05I9aBZOHztWy/UqcJdQYNsf59vrRKWnojAT9Y+GbIvoTBC++CPHqpDBhQ==", "license": "BSD-3-Clause", "dependencies": { "side-channel": "^1.1.0" @@ -5230,6 +5249,7 @@ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -5695,14 +5715,15 @@ "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", "license": "MIT", + "peer": true, "funding": { "url": "https://github.com/sponsors/colinhacks" } }, "node_modules/zod-to-json-schema": { - "version": "3.25.0", - "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.25.0.tgz", - "integrity": "sha512-HvWtU2UG41LALjajJrML6uQejQhNJx+JBO9IflpSja4R03iNWfKXrj6W2h7ljuLyc1nKS+9yDyL/9tD1U/yBnQ==", + "version": "3.25.1", + "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.25.1.tgz", + "integrity": "sha512-pM/SU9d3YAggzi6MtR4h7ruuQlqKtad8e9S0fmxcMi+ueAK5Korys/aWcV9LIIHTVbj01NdzxcnXSN+O74ZIVA==", "license": "ISC", "peerDependencies": { "zod": "^3.25 || ^4" diff --git a/package.json b/package.json index e68ced7..dc7b689 100644 --- a/package.json +++ b/package.json @@ -41,10 +41,10 @@ "license": "ISC", "dependencies": { "@modelcontextprotocol/sdk": "^1.17.2", - "ajv": "^8.17.1", "amqplib": "^0.10.8", "dotenv": "^17.2.1", "express": "^5.1.0", + "express-rate-limit": "^8.2.1", "mysql2": "^3.14.3", "sequelize": "^6.37.7", "ulid": "^3.0.1", diff --git a/prompts/threat-model.prompt.md b/prompts/threat-model.prompt.md index ac50c33..a5569c6 100644 --- a/prompts/threat-model.prompt.md +++ b/prompts/threat-model.prompt.md @@ -1,91 +1,288 @@ --- agent: "application-security-architect" name: threat-model -description: "Threat model the system using the 4Q framework and produce actionable artifacts." +description: "Threat model the system using Shostack’s 4Q framework and produce actionable artifacts with repo-grounded diagrams validated via Mermaid Chart tools." --- -# Prompt: 4Q Threat Model +# Prompt: 4Q Threat Model (DFDs + Supporting Diagrams, Tool-Validated Mermaid) ## Mission & Scope -**Goal:** Embed Adam Shostack’s **Four-Question** threat modeling into daily dev flow using VS Code + GitHub. The agent infers design from code, collaborates with the developer, and produces durable artifacts (e.g., a threat model markdown report), plus a concise PR-ready summary. +**Goal:** Embed Adam Shostack’s **Four-Question** threat modeling into daily dev flow using VS Code + GitHub. Infer design from the repository (and/or PR diff), collaborate with the developer, and produce durable artifacts: + +1. a repo-grounded threat model Markdown report **with validated Mermaid diagrams** +2. a concise PR-ready summary (copy/paste) **4 Questions:** -1. *What are we working on?* → Infer & confirm scope, dataflows, trust boundaries. -2. *What can go wrong?* → Brainstorm threats (context-specific, STRIDE/OWASP mapped). -3. *What are we going to do about it?* → Check current mitigations, propose mitigation status. -4. *Did we do a good job?* → Define validation evidence to collect and owners. +1. *What are we working on?* → Infer & confirm scope, assets, data flows, trust boundaries +2. *What can go wrong?* → Enumerate threats (context-specific), map to STRIDE + OWASP +3. *What are we going to do about it?* → Identify mitigations + gaps; status w/ evidence +4. *Did we do a good job?* → Validation plan: evidence to collect + owners **Where it runs:** -- **Local:** VS Code Copilot Chat / Agent mode for developers. -- **PR review:** Use the same output format as a PR comment or issue description. +- **Local:** VS Code Copilot Chat / Agent mode +- **PR review:** Same output format works as PR comment or issue description + +--- ## ✅ Context / Assumptions - Threat model the current repository and/or current PR diff (if available). -- Persist the resulting threat model as a Markdown file in the project root named: `Threat Model Review - {{DATE}}.md`. -- Evidence-first: cite file paths and (when possible) line ranges for claims about mitigations. -- If you cannot confirm something from the repo/diff, label it as **ASSUMPTION** or **UNKNOWN** (do not guess). -- Ask 2–4 clarifying questions if scope/dataflows/deployment assumptions are unclear. -- Do not generate code changes unless explicitly requested; focus on analysis and artifacts. +- Persist output as a Markdown file in project root: + - `Threat Model Review - {{DATE}}.md` +- Evidence-first: cite file paths and (when possible) line ranges for claims about design, flows, mitigations, and trust boundaries. +- If you cannot confirm something from the repo/diff, label it **ASSUMPTION** or **UNKNOWN** (do not guess). +- Ask **2–4 clarifying questions** if scope, dataflows, deployment, or identities are unclear. +- Do not generate code changes unless explicitly requested. + +--- + +## 🧰 Mermaid Diagram Tooling (Mandatory) + +You have access to Mermaid Chart tools: + +- `mermaidchart.vscode-mermaid-chart/get_syntax_docs` +- `mermaidchart.vscode-mermaid-chart/mermaid-diagram-validator` +- `mermaidchart.vscode-mermaid-chart/mermaid-diagram-preview` + +**You MUST use them** to prevent syntax errors. + +### Tool-driven diagram workflow (required) + +For every Mermaid diagram you include: + +1. **Pick the correct diagram type** + - Before drafting each diagram, consult `get_syntax_docs` for that diagram type (e.g., `flowchart`, `sequenceDiagram`, `C4`, `classDiagram`, `erDiagram`). +2. **Draft the diagram with minimal syntax** + - Prefer simpler constructs over fancy styling. + - Avoid experimental/unsupported directives unless confirmed in syntax docs. +3. **Validate** + - Run `mermaid-diagram-validator` on each diagram block. + - If validation fails, fix and re-validate until it passes. +4. **Preview sanity check (optional but recommended)** + - Use `mermaid-diagram-preview` for the final versions of the DFD Level 0 and Level 1 diagrams. + +### Mermaid reliability rules (to avoid common breakage) + +- Always start with a valid diagram header: `flowchart LR`, `sequenceDiagram`, `classDiagram`, `erDiagram`, etc. +- Don’t mix diagram grammars (e.g., don’t use `participant` in `flowchart`). +- Avoid parentheses/brackets in node IDs; put complex text in node *labels*. + - Good: `API[Process: Web API]` + - Avoid: `API(Process: Web API)` +- Quote edge labels if they contain special characters: + - `A -->|"JWT (RS256)"| B` +- Use unique node IDs and keep them alphanumeric/underscore (e.g., `svc_orders`, `db_main`). +- Keep subgraph titles simple; avoid `:` if it breaks parsing. +- Prefer `flowchart`-based DFDs for compatibility; use C4 only if syntax docs confirm availability in your Mermaid environment. + +**Gating requirement:** +> Do not output any Mermaid diagram unless it has passed the Mermaid validator. + +--- + +## 🔒 Diagram Requirements (Mermaid) + +**You MUST include diagrams** unless Mermaid rendering is not supported. Use Mermaid code blocks. + +### Required diagram set + +1. **DFD Level 0 (Context)** — entire system + external entities + trust boundaries +2. **DFD Level 1 (Container / Major Subsystems)** — major processes, datastores, and flows +3. **Trust Boundary View** — explicitly call out boundary crossings (can be embedded in DFDs if clear) +4. **Top 2–3 Sequence Diagrams** — highest-risk flows (auth/login, payment, admin action, data export) + +### Optional (include when discoverable) + +- **Deployment / Runtime Topology** (k8s/compose/serverless/IaC-derived) +- **Identity & Authorization model diagram** (actors → roles → permissions → enforcement points) +- **Data classification map** (PII/PHI/secrets/payment data) tied to datastores and flows + +### Diagram evidence rules + +- Every diagram must include a short **Evidence** list: + - file path(s) + relevant symbol(s) (and line ranges when possible) +- If you cannot infer an element, label it **UNKNOWN** in the diagram and explain what evidence is missing. + +--- ## 🔍 Procedure (4Q) -1) **Q1 — What are we working on?** - - Summarize scope, assets, key dataflows, and trust boundaries. -2) **Q2 — What can go wrong?** - - Enumerate threats specific to the flows; map each to STRIDE + OWASP tag. -3) **Q3 — What are we going to do about it?** - - Identify mitigations as **PRESENT / ABSENT / UNKNOWN**, with evidence when present. -4) **Q4 — Did we do a good job?** - - Define a validation plan (no code): scenarios + evidence to collect + owners. +### 0) Triage & Inventory (fast, evidence-based) + +- Identify entry points, deployables, and primary data stores: + - manifests (`package.json`, `pom.xml`, `.csproj`, `pyproject.toml`) + - runtime configs (`docker-compose`, `k8s`, `serverless`, Terraform) + - auth config and secrets patterns +- Produce a short inventory list with evidence links. + +### 1) **Q1 — What are we working on?** + +Deliver: + +- System purpose (from README/docs where possible) +- Components / containers / deployables +- Key assets (data + systems) +- **Key dataflows** (ranked by sensitivity and exposure) +- Trust boundaries (internet/app/network/cloud/3rd party/admin) +- **Diagrams (DFD L0 + L1 + trust boundaries)** — tool-validated + +### 2) **Q2 — What can go wrong?** + +For each key flow in the DFD: + +- Enumerate threats specific to that flow +- Map to: + - **STRIDE** category + - **OWASP** tag (Top 10 / ASVS control area / API Top 10 — whichever best fits) +- Include a short “Attack narrative” for the top risks (2–5 sentences) + +Also include: -## 📦 Output Format +- Abuse cases for privileged/admin pathways +- Supply chain threats if dependency/build pipeline evidence exists -Return the threat model as GitHub-flavored Markdown in chat (PR-comment ready) with the structure below. If your environment supports writing files, also write it to `./Threat Model Review - {{DATE}}.md` (project root): +### 3) **Q3 — What are we going to do about it?** -### Scope +For each threat: -- Components: +- Identify mitigations as **PRESENT / ABSENT / UNKNOWN** +- Provide evidence when PRESENT: + - exact file path + symbol + line range (when possible) +- If ABSENT/UNKNOWN: + - propose remediation options + - note expected effort (S/M/L) and blast-radius + +### 4) **Q4 — Did we do a good job?** + +Create a validation plan (no code changes) that includes: + +- 3–6 scenarios (prioritize highest-risk flows) +- Evidence to collect (logs, config proof, test results, screenshots, policy outputs) +- Owners (team/person/role) + +Include a final quality review checklist: + +- Coverage: do DFD flows map to threats/mitigations? +- Boundary crossings: are they analyzed? +- Unknowns: are they actionable questions with owners? +- Mermaid diagrams: did all pass validator? + +--- + +## 📦 Output Format (GitHub-Flavored Markdown) + +Return the threat model as PR-comment-ready Markdown in chat. + +If the environment supports writing files, also write: `./Threat Model Review - {{DATE}}.md` + +### 0. Executive summary + +- 5–10 bullets: top risks, what’s solid, what’s unknown, next actions + +### 1. Scope + +- In-scope components/containers: +- Out-of-scope: - Trust boundaries: -- Key dataflows: +- Key assets (with sensitivity: Public/Internal/Confidential/Restricted): + +### 2. Assumptions & Unknowns + +- **ASSUMPTION:** … +- **UNKNOWN:** … (include “Who can confirm” + question) + +### 3. Architecture & Data Flows (with tool-validated diagrams) + +#### 3.1 DFD Level 0 (Context) + +```mermaid +flowchart LR + %% (diagram content validated via Mermaid Chart tools) +``` + +**Evidence** + +- `path/to/file` (symbol: …, lines …) + +#### 3.2 DFD Level 1 (Subsystems / Containers) + +```mermaid +flowchart LR + %% (diagram content validated via Mermaid Chart tools) +``` + +**Evidence** + +- … + +#### 3.3 Supporting diagrams (as applicable) + +- Trust boundary view (if not already clear) +- Deployment topology (if discoverable) +- Identity/authorization model (if discoverable) + +### 4. Key Flows (ranked) + +For each flow: + +- Description +- Data elements involved (classify) +- Entry points and enforcement points +- Evidence links + +### 5. Threats + +Table: + +`ID | Flow | Summary | STRIDE | OWASP | Likelihood (L/M/H) | Impact (L/M/H) | Status (Open/Mitigated/Unknown) | Rationale` + +### 6. Mitigations + +Table: -### Assumptions +`Threat ID | Mitigation | Status (PRESENT/ABSENT/UNKNOWN) | Location/Evidence | Notes/Open questions` -- (bullets; include owner/questions where possible) +### 7. High-risk interaction sequences (top 2–3, tool-validated) -### Threats +Provide sequence diagrams for the riskiest flows: -Table: `ID | Summary | STRIDE | OWASP | Likelihood (L/M/H) | Impact (L/M/H) | Status | Rationale` +```mermaid +sequenceDiagram + %% (diagram content validated via Mermaid Chart tools) +``` -### Mitigations +**Evidence** -Table: `Threat ID | Mitigation | Status (PRESENT/ABSENT/UNKNOWN) | Location/Evidence | Notes/Open questions` +- … -### Validation plan (no code) +### 8. Validation plan (no code) -Provide **3 scenarios**: +Provide **3–6 scenarios**: - Intent - Preconditions - Steps - Expected result - Evidence to collect +- Owner -### Owners +### 9. Owners -- Who confirms assumptions -- Who drives mitigations +- Who confirms assumptions: +- Who drives mitigations: +- Who validates fixes: -### Open questions +### 10. Open questions -- Items needing confirmation +- Bullets; each includes an owner and where to look in the repo -## ✅ Quality checks +### ✅ Quality checks -- Every **PRESENT** mitigation includes a concrete code/config location when possible (path + line range). -- **UNKNOWN** is used when evidence is insufficient and includes a follow-up question. -- Threats are specific to described flows (avoid generic lists). -- Evidence vs assumptions are clearly separated and labeled. +- Every **PRESENT** mitigation includes concrete code/config location (path + lines when possible). +- **UNKNOWN** includes a follow-up question + owner. +- Threats are tied to DFD flows (no generic dump). +- Diagrams match actual repo components and are evidence-linked. +- Evidence vs. inference is clearly labeled. +- **All Mermaid diagrams were validated using `mermaid-diagram-validator`.** diff --git a/src/express_app.js b/src/express_app.js index e46cf20..bacbfa8 100644 --- a/src/express_app.js +++ b/src/express_app.js @@ -1,12 +1,27 @@ import express from 'express'; +import rateLimit from 'express-rate-limit'; import { logger } from 'copilot-instructions-mcp/core'; import { reqInfo } from 'copilot-instructions-mcp/middlewares'; import mcpServer from './mcp_server.js'; const app = express(); +app.disable('x-powered-by'); + +app.use(express.json({ + limit: '1mb', + type: ['application/json', 'application/*+json'], +})); + app.use(reqInfo); +const mcpRateLimit = rateLimit({ + windowMs: 60 * 1000, + limit: 120, + standardHeaders: true, + legacyHeaders: false, +}); + app.get('/', (req, res) => { logger.info('Received request for homepage', { source: 'express_app.get(/)', @@ -19,7 +34,7 @@ app.get('/', (req, res) => { app.get('/health', (req, res) => res.status(200).send('OK')); -app.get('/mcp', (req, res) => { +app.get('/mcp', mcpRateLimit, (req, res) => { // TODO: Log request details /** TODO: Implement MCP Server GET Specification Specification 1: The client MAY issue an HTTP GET to the MCP endpoint. This can be used to open an SSE stream, allowing the server to communicate to the client, without the client first sending data via HTTP POST. @@ -39,37 +54,60 @@ app.get('/mcp', (req, res) => { return res.status(405).send('Method Not Allowed'); }); -app.post('/mcp', async (req, res) => { - // TODO: Log request details - /** TODO: Send Request to `mcp_server` for proper handling - Every JSON-RPC message sent from the client MUST be a new HTTP POST request to the MCP endpoint. - 1. The client MUST use HTTP POST to send JSON-RPC messages to the MCP endpoint. - 2. The client MUST include an Accept header, listing both application/json and text/event-stream as supported content types. - 3. The body of the POST request MUST be a single JSON-RPC request, notification, or response. - 4. If the input is a JSON-RPC response or notification: - - If the server accepts the input, the server MUST return HTTP status code 202 Accepted with no body. - - If the server cannot accept the input, it MUST return an HTTP error status code (e.g., 400 Bad Request). The HTTP response body MAY comprise a JSON-RPC error response that has no id. - 5. If the input is a JSON-RPC request, the server MUST either return Content-Type: text/event-stream, to initiate an SSE stream, or Content-Type: application/json, to return one JSON object. The client MUST support both these cases. - 6. If the server initiates an SSE stream: - - The SSE stream SHOULD eventually include JSON-RPC response for the JSON-RPC request sent in the POST body. - - The server MAY send JSON-RPC requests and notifications before sending the JSON-RPC response. These messages SHOULD relate to the originating client request. - - The server SHOULD NOT close the SSE stream before sending the JSON-RPC response for the received JSON-RPC request, unless the session expires. - - After the JSON-RPC response has been sent, the server SHOULD close the SSE stream. - - Disconnection MAY occur at any time (e.g., due to network conditions). Therefore: - - Disconnection SHOULD NOT be interpreted as the client cancelling its request. - - To cancel, the client SHOULD explicitly send an MCP CancelledNotification. - - To avoid message loss due to disconnection, the server MAY make the stream resumable. - */ - // await server.connect(transport); +app.post('/mcp', mcpRateLimit, async (req, res) => { logger.info('Received MCP POST request', { source: 'express_app.post(/mcp)', reqInfo: req.info, }); - const mcp = mcpServer(); - await mcp.server.connect(mcp.transport); - await mcp.transport.handleRequest(req, res, req.body); - // return res.status(200).send('OK'); // Temporary response + try { + const mcp = mcpServer(); + await mcp.server.connect(mcp.transport); + return await mcp.transport.handleRequest(req, res, req.body); + } catch (error) { + logger.error('Unhandled error while processing MCP POST request', { + source: 'express_app.post(/mcp)', + reqInfo: req.info, + error: { + message: error?.message || String(error), + stack: error?.stack, + }, + }); + + if (!res.headersSent) { + return res.status(500).json({ + jsonrpc: '2.0', + error: { + code: -32603, + message: 'Internal error', + }, + id: null, + }); + } + } + return res.status(500).end(); +}); + +// Ensure JSON parse errors are returned consistently (and do not leak stacks) +// This runs after route handlers when express.json() throws. +app.use((err, req, res, next) => { + if (err?.type === 'entity.parse.failed') { + logger.warn('Invalid JSON body received', { + source: 'express_app.jsonParser', + reqInfo: req.info, + }); + + return res.status(400).json({ + jsonrpc: '2.0', + error: { + code: -32700, + message: 'Parse error: Invalid JSON', + }, + id: null, + }); + } + + return next(err); }); export default app; diff --git a/src/mcp_server.js b/src/mcp_server.js index 619f215..02dacd3 100644 --- a/src/mcp_server.js +++ b/src/mcp_server.js @@ -1,9 +1,7 @@ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js"; import mcpTools from 'copilot-instructions-mcp/mcp_tools'; - - - +import { prompts } from 'copilot-instructions-mcp/mcp_prompts'; function makeMCPServer() { const server = new McpServer({ @@ -18,6 +16,29 @@ function makeMCPServer() { mcpTools.list_prompts(server); mcpTools.get_prompt(server); + // Register MCP-native prompts for client compatibility (prompts/list + prompts/get) + for (const [promptName, promptText] of Object.entries(prompts)) { + server.registerPrompt( + promptName, + { + title: promptName, + description: `Prompt loaded from prompts/${promptName}`, + }, + async () => ({ + description: `Prompt loaded from prompts/${promptName}`, + messages: [ + { + role: 'user', + content: { + type: 'text', + text: promptText, + }, + }, + ], + }), + ); + } + const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: undefined, }); @@ -25,6 +46,4 @@ function makeMCPServer() { return { server, transport }; } - - export default makeMCPServer; diff --git a/src/mcp_tools/get_prompt.js b/src/mcp_tools/get_prompt.js index cf5511d..2a247df 100644 --- a/src/mcp_tools/get_prompt.js +++ b/src/mcp_tools/get_prompt.js @@ -27,6 +27,7 @@ function get_prompt(server) { type: 'text', text: `Prompt not found: ${args.promptName}`, }], + isError: true, }; } return { diff --git a/src/mcp_tools/list_prompts.js b/src/mcp_tools/list_prompts.js index cd4ecb8..20b3dbe 100644 --- a/src/mcp_tools/list_prompts.js +++ b/src/mcp_tools/list_prompts.js @@ -1,3 +1,4 @@ +import { z } from 'zod'; import { logger } from 'copilot-instructions-mcp/core'; import { prompts } from 'copilot-instructions-mcp/mcp_prompts'; diff --git a/src/middlewares/reqInfo.js b/src/middlewares/reqInfo.js index 84b8479..0055ab4 100644 --- a/src/middlewares/reqInfo.js +++ b/src/middlewares/reqInfo.js @@ -1,12 +1,36 @@ import { ulid } from 'ulid'; +function redactHeaders(headers) { + const redactedKeys = new Set([ + 'authorization', + 'proxy-authorization', + 'cookie', + 'set-cookie', + 'x-api-key', + 'x-auth-token', + 'x-forwarded-authorization', + ]); + + const safe = {}; + for (const [key, value] of Object.entries(headers || {})) { + if (redactedKeys.has(String(key).toLowerCase())) { + safe[key] = '[REDACTED]'; + continue; + } + + // Avoid huge header values in logs + const str = Array.isArray(value) ? value.join(',') : String(value); + safe[key] = str.length > 2048 ? `${str.slice(0, 2048)}...` : str; + } + return safe; +} + function reqInfoMiddleware(req, res, next) { req.info = { id: ulid(), - headers: req.headers, + headers: redactHeaders(req.headers), method: req.method, url: req.url, - body: req.body, timestamp: new Date().toISOString(), };