From 616af65004348ccc930afd4dff01f784fb9badd3 Mon Sep 17 00:00:00 2001 From: will Farrell Date: Thu, 7 May 2026 14:19:42 -0600 Subject: [PATCH 01/13] chore: dep update Signed-off-by: will Farrell --- package-lock.json | 45 ++++++++++++++------------------------------- 1 file changed, 14 insertions(+), 31 deletions(-) diff --git a/package-lock.json b/package-lock.json index 6926c63..fb80c0a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1111,9 +1111,9 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "25.6.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-25.6.0.tgz", - "integrity": "sha512-+qIYRKdNYJwY3vRCZMdJbPLJAtGjQBudzZzdzwQYkEPQd+PJGixUL5QfvCLDaULoLv+RhT3LDkwEfKaAkgSmNQ==", + "version": "25.6.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.6.1.tgz", + "integrity": "sha512-coJCN8O1q4AGyyqCAUSP06P+SrMTu18BkEj3NVAK07q6QUneD2wzj3CLv9+yP+BMeZQlMvneXqqvDe3w+xcq7g==", "dev": true, "license": "MIT", "dependencies": { @@ -1179,9 +1179,9 @@ } }, "node_modules/ajv-cmd": { - "version": "0.13.0", - "resolved": "https://registry.npmjs.org/ajv-cmd/-/ajv-cmd-0.13.0.tgz", - "integrity": "sha512-6x+LjpJOaY+wtWAoegctypfhUnbMIiiKES2q6NdXqb1nwPTW49RLhNuh/hcR6pdN+2DVbGv8TxXtOxf9CljyoA==", + "version": "0.13.3", + "resolved": "https://registry.npmjs.org/ajv-cmd/-/ajv-cmd-0.13.3.tgz", + "integrity": "sha512-H1LArE4kclZ5JdWLKUZd1ji3UjO6FSpCW1MZ6J0X89BqwT+5W70KD4TpfvhcM7isSoQvo6sslA/rZiCH1HzM6g==", "dev": true, "license": "MIT", "workspaces": [ @@ -1198,7 +1198,7 @@ "ajv-keywords": "5.1.0", "commander": "14.0.3", "esbuild": "^0.28.0", - "sast-json-schema": "0.2.3" + "sast-json-schema": "^0.4.0" }, "bin": { "ajv": "cli.js" @@ -1757,9 +1757,9 @@ } }, "node_modules/fast-uri": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz", - "integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.2.tgz", + "integrity": "sha512-rVjf7ArG3LTk+FS6Yw81V1DLuZl1bRbNrev6Tmd/9RaroeeRRJhAt7jg/6YFxbvAQXUCavSoZhPPj6oOx+5KjQ==", "funding": [ { "type": "github", @@ -2864,16 +2864,16 @@ } }, "node_modules/sast-json-schema": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/sast-json-schema/-/sast-json-schema-0.2.3.tgz", - "integrity": "sha512-84DyjPfOHYodSRPGeCXQEOSmUnByNLnqEkOlzWcsJyniVvjinVjDtSUZK3PtWz45e3dee0NZX3aJp2CA2l2XfQ==", + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/sast-json-schema/-/sast-json-schema-0.4.1.tgz", + "integrity": "sha512-GnOPf8rCTfysRtOzCJPoBZu8xKnY4LZ24jxCmcxsLUkmOTpK/DrbrJ97Xio4xkIH6USdzEZ5ZSdpTX8Gj03EuQ==", "dev": true, "license": "MIT", "workspaces": [ ".github" ], "dependencies": { - "ajv": "8.18.0", + "ajv": "8.20.0", "redos-detector": "6.1.4" }, "bin": { @@ -2887,23 +2887,6 @@ "url": "https://github.com/sponsors/willfarrell" } }, - "node_modules/sast-json-schema/node_modules/ajv": { - "version": "8.18.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", - "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", - "dev": true, - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.3", - "fast-uri": "^3.0.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, "node_modules/semver": { "version": "7.7.4", "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", From 913f8478cca98363c162888ba8a4e1a5ae6016cc Mon Sep 17 00:00:00 2001 From: will Farrell Date: Sat, 9 May 2026 05:04:31 -0600 Subject: [PATCH 02/13] chore: dep update Signed-off-by: will Farrell --- package-lock.json | 558 ++++++++++++++++++++++++++++------------------ package.json | 4 +- 2 files changed, 340 insertions(+), 222 deletions(-) diff --git a/package-lock.json b/package-lock.json index fb80c0a..e1e1a8a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -20,8 +20,8 @@ }, "devDependencies": { "@biomejs/biome": "^2.0.0", - "@commitlint/cli": "^20.0.0", - "@commitlint/config-conventional": "^20.0.0", + "@commitlint/cli": "^21.0.0", + "@commitlint/config-conventional": "^21.0.0", "ajv-cmd": "^0.13.0", "fast-check": "^4.0.0", "husky": "^9.0.0", @@ -264,134 +264,134 @@ } }, "node_modules/@commitlint/cli": { - "version": "20.5.3", - "resolved": "https://registry.npmjs.org/@commitlint/cli/-/cli-20.5.3.tgz", - "integrity": "sha512-OJdL0EXWD5y9LPa0nr/geOwzaS8BsdaybKkcloB0JgsguGxNv2R+hC2FTPqrAcprg35zF33KOQerY0x8W1aesA==", + "version": "21.0.0", + "resolved": "https://registry.npmjs.org/@commitlint/cli/-/cli-21.0.0.tgz", + "integrity": "sha512-p3y2oC0G2R45zaadMwBxCiSesS8digi5RDplP3Zrfpzm7xIgrgAj0W4fGzONjpHyg8obDVJDU45g5txzeMcblg==", "dev": true, "license": "MIT", "dependencies": { - "@commitlint/format": "^20.5.0", - "@commitlint/lint": "^20.5.3", - "@commitlint/load": "^20.5.3", - "@commitlint/read": "^20.5.0", - "@commitlint/types": "^20.5.0", + "@commitlint/format": "^21.0.0", + "@commitlint/lint": "^21.0.0", + "@commitlint/load": "^21.0.0", + "@commitlint/read": "^21.0.0", + "@commitlint/types": "^21.0.0", "tinyexec": "^1.0.0", - "yargs": "^17.0.0" + "yargs": "^18.0.0" }, "bin": { "commitlint": "cli.js" }, "engines": { - "node": ">=v18" + "node": ">=22.12.0" } }, "node_modules/@commitlint/config-conventional": { - "version": "20.5.3", - "resolved": "https://registry.npmjs.org/@commitlint/config-conventional/-/config-conventional-20.5.3.tgz", - "integrity": "sha512-j34Qqeaa152chJgz2ysyk0BCpHenJn1lV0Rx0VXf8k3ccQcED+48EZrzMvo9jLmJUyBrrBwvu89I+2er4gW7QQ==", + "version": "21.0.0", + "resolved": "https://registry.npmjs.org/@commitlint/config-conventional/-/config-conventional-21.0.0.tgz", + "integrity": "sha512-QJX/rPK4Yu3f5J4OCIBy5aXq2e0EEdwSDFZ3NQvFAXTm3gs12ipyZ+yjhZxm3hHn6DB8wuv3zhFTL1I2tYzUBA==", "dev": true, "license": "MIT", "dependencies": { - "@commitlint/types": "^20.5.0", + "@commitlint/types": "^21.0.0", "conventional-changelog-conventionalcommits": "^9.2.0" }, "engines": { - "node": ">=v18" + "node": ">=22.12.0" } }, "node_modules/@commitlint/config-validator": { - "version": "20.5.0", - "resolved": "https://registry.npmjs.org/@commitlint/config-validator/-/config-validator-20.5.0.tgz", - "integrity": "sha512-T/Uh6iJUzyx7j35GmHWdIiGRQB+ouZDk0pwAaYq4SXgB54KZhFdJ0vYmxiW6AMYICTIWuyMxDBl1jK74oFp/Gw==", + "version": "21.0.0", + "resolved": "https://registry.npmjs.org/@commitlint/config-validator/-/config-validator-21.0.0.tgz", + "integrity": "sha512-v0UplTYryNUB463X5WrelzKq5/qyYm9/iUNk38S7ZLnd56Uuk2T9awhYKGlgD2/4L5YuN2gsKkyy4EHpRPPz2Q==", "dev": true, "license": "MIT", "dependencies": { - "@commitlint/types": "^20.5.0", + "@commitlint/types": "^21.0.0", "ajv": "^8.11.0" }, "engines": { - "node": ">=v18" + "node": ">=22.12.0" } }, "node_modules/@commitlint/ensure": { - "version": "20.5.3", - "resolved": "https://registry.npmjs.org/@commitlint/ensure/-/ensure-20.5.3.tgz", - "integrity": "sha512-4i4AgNvH62owG9MwSiWKrle7HGNpBHHdLnWFIp5fTsHUYe5kRuh15t08L/0pdbbrRk8JKXQxxN4hZQcn+szkrw==", + "version": "21.0.0", + "resolved": "https://registry.npmjs.org/@commitlint/ensure/-/ensure-21.0.0.tgz", + "integrity": "sha512-n+OYs0Ws9GKC2WlmAeLNoPz9CUg6n/ZyYMkFF8rJ0aMn2kDTDTG0VqK/2Dco0EB4fhuF3JPIllJmU9/LKTl4aw==", "dev": true, "license": "MIT", "dependencies": { - "@commitlint/types": "^20.5.0", + "@commitlint/types": "^21.0.0", "es-toolkit": "^1.46.0" }, "engines": { - "node": ">=v18" + "node": ">=22.12.0" } }, "node_modules/@commitlint/execute-rule": { - "version": "20.0.0", - "resolved": "https://registry.npmjs.org/@commitlint/execute-rule/-/execute-rule-20.0.0.tgz", - "integrity": "sha512-xyCoOShoPuPL44gVa+5EdZsBVao/pNzpQhkzq3RdtlFdKZtjWcLlUFQHSWBuhk5utKYykeJPSz2i8ABHQA+ZZw==", + "version": "21.0.0", + "resolved": "https://registry.npmjs.org/@commitlint/execute-rule/-/execute-rule-21.0.0.tgz", + "integrity": "sha512-3OhTq2gQX1tEheMsbDNqxfcNHsAM6g9cub9plf05I9jCxtbNfn8Y+mhClKyUwhX4dbtmC4OLZ9i+HNmoL1aksA==", "dev": true, "license": "MIT", "engines": { - "node": ">=v18" + "node": ">=22.12.0" } }, "node_modules/@commitlint/format": { - "version": "20.5.0", - "resolved": "https://registry.npmjs.org/@commitlint/format/-/format-20.5.0.tgz", - "integrity": "sha512-TI9EwFU/qZWSK7a5qyXMpKPPv3qta7FO4tKW+Wt2al7sgMbLWTsAcDpX1cU8k16TRdsiiet9aOw0zpvRXNJu7Q==", + "version": "21.0.0", + "resolved": "https://registry.npmjs.org/@commitlint/format/-/format-21.0.0.tgz", + "integrity": "sha512-RTfGSrueEgofs1piqwi42U05d85wfxiMH2ncMCZnltx1XqPR3N2S48oACBtTy4xRAhWlf5XlHkK2RaDzEQu3dA==", "dev": true, "license": "MIT", "dependencies": { - "@commitlint/types": "^20.5.0", + "@commitlint/types": "^21.0.0", "picocolors": "^1.1.1" }, "engines": { - "node": ">=v18" + "node": ">=22.12.0" } }, "node_modules/@commitlint/is-ignored": { - "version": "20.5.0", - "resolved": "https://registry.npmjs.org/@commitlint/is-ignored/-/is-ignored-20.5.0.tgz", - "integrity": "sha512-JWLarAsurHJhPozbuAH6GbP4p/hdOCoqS9zJMfqwswne+/GPs5V0+rrsfOkP68Y8PSLphwtFXV0EzJ+GTXTTGg==", + "version": "21.0.0", + "resolved": "https://registry.npmjs.org/@commitlint/is-ignored/-/is-ignored-21.0.0.tgz", + "integrity": "sha512-K3SaaOTVY9VKhge7vl0R3ng7GENRzJQ9MPV43Tu53kAwEgSx/E0HF4US3AcVqdvlvsDUbF2yXvED95dhela83w==", "dev": true, "license": "MIT", "dependencies": { - "@commitlint/types": "^20.5.0", + "@commitlint/types": "^21.0.0", "semver": "^7.6.0" }, "engines": { - "node": ">=v18" + "node": ">=22.12.0" } }, "node_modules/@commitlint/lint": { - "version": "20.5.3", - "resolved": "https://registry.npmjs.org/@commitlint/lint/-/lint-20.5.3.tgz", - "integrity": "sha512-M7JbWBNr2gXKaPc4i/KipsuW1gkDHpj35KPjWtKy3Z+2AQw5wu1gBi1LIO0uoaij67CqY4K8PxPZSGens4evCw==", + "version": "21.0.0", + "resolved": "https://registry.npmjs.org/@commitlint/lint/-/lint-21.0.0.tgz", + "integrity": "sha512-dlUJA0Ka14R1YaR46JVRWE3m/8dOQAgE/D0heUfzYua5Jogtq/zzu2ITAIaB/u25DaKjtEO6kuvASzsFDyrPMw==", "dev": true, "license": "MIT", "dependencies": { - "@commitlint/is-ignored": "^20.5.0", - "@commitlint/parse": "^20.5.0", - "@commitlint/rules": "^20.5.3", - "@commitlint/types": "^20.5.0" + "@commitlint/is-ignored": "^21.0.0", + "@commitlint/parse": "^21.0.0", + "@commitlint/rules": "^21.0.0", + "@commitlint/types": "^21.0.0" }, "engines": { - "node": ">=v18" + "node": ">=22.12.0" } }, "node_modules/@commitlint/load": { - "version": "20.5.3", - "resolved": "https://registry.npmjs.org/@commitlint/load/-/load-20.5.3.tgz", - "integrity": "sha512-1FDZWuKyu98Myb8i7Tp31jPU2rZpOwAdYRyJcy2KoGg7Xk2A+bgHN8smhMaaNSNkmE8fwt53BokywZq8Gv/5XQ==", + "version": "21.0.0", + "resolved": "https://registry.npmjs.org/@commitlint/load/-/load-21.0.0.tgz", + "integrity": "sha512-l0nBfO/20PKcJXHZqDIgh7kw/TWVVwn8zZJOkVGBK/ig/h328jBu9jK7OiDl2oZr5mLphmKGjYDR2ffEyb2lIA==", "dev": true, "license": "MIT", "dependencies": { - "@commitlint/config-validator": "^20.5.0", - "@commitlint/execute-rule": "^20.0.0", - "@commitlint/resolve-extends": "^20.5.3", - "@commitlint/types": "^20.5.0", + "@commitlint/config-validator": "^21.0.0", + "@commitlint/execute-rule": "^21.0.0", + "@commitlint/resolve-extends": "^21.0.0", + "@commitlint/types": "^21.0.0", "cosmiconfig": "^9.0.1", "cosmiconfig-typescript-loader": "^6.1.0", "es-toolkit": "^1.46.0", @@ -399,112 +399,110 @@ "picocolors": "^1.1.1" }, "engines": { - "node": ">=v18" + "node": ">=22.12.0" } }, "node_modules/@commitlint/message": { - "version": "20.4.3", - "resolved": "https://registry.npmjs.org/@commitlint/message/-/message-20.4.3.tgz", - "integrity": "sha512-6akwCYrzcrFcTYz9GyUaWlhisY4lmQ3KvrnabmhoeAV8nRH4dXJAh4+EUQ3uArtxxKQkvxJS78hNX2EU3USgxQ==", + "version": "21.0.0", + "resolved": "https://registry.npmjs.org/@commitlint/message/-/message-21.0.0.tgz", + "integrity": "sha512-+daU92JaOHhI2En9KcH+2mvZGJ6D4YSxb/32QDwqkOwSj1Vanjio8PbAqX7dneACdg6B7RgQ7i3mpyYZAws4nw==", "dev": true, "license": "MIT", "engines": { - "node": ">=v18" + "node": ">=22.12.0" } }, "node_modules/@commitlint/parse": { - "version": "20.5.0", - "resolved": "https://registry.npmjs.org/@commitlint/parse/-/parse-20.5.0.tgz", - "integrity": "sha512-SeKWHBMk7YOTnnEWUhx+d1a9vHsjjuo6Uo1xRfPNfeY4bdYFasCH1dDpAv13Lyn+dDPOels+jP6D2GRZqzc5fA==", + "version": "21.0.0", + "resolved": "https://registry.npmjs.org/@commitlint/parse/-/parse-21.0.0.tgz", + "integrity": "sha512-1dbvFBcQK79aTbpc2QCrgEDc6/MMkQ0Mdz4gGmYkN4AHMnAK9HesSewTHqGTrW5mALrMlYSgcWyvKjloY2w19A==", "dev": true, "license": "MIT", "dependencies": { - "@commitlint/types": "^20.5.0", + "@commitlint/types": "^21.0.0", "conventional-changelog-angular": "^8.2.0", "conventional-commits-parser": "^6.3.0" }, "engines": { - "node": ">=v18" + "node": ">=22.12.0" } }, "node_modules/@commitlint/read": { - "version": "20.5.0", - "resolved": "https://registry.npmjs.org/@commitlint/read/-/read-20.5.0.tgz", - "integrity": "sha512-JDEIJ2+GnWpK8QqwfmW7O42h0aycJEWNqcdkJnyzLD11nf9dW2dWLTVEa8Wtlo4IZFGLPATjR5neA5QlOvIH1w==", + "version": "21.0.0", + "resolved": "https://registry.npmjs.org/@commitlint/read/-/read-21.0.0.tgz", + "integrity": "sha512-8VKLKLl2vBSKoTMm1LwcySsyxrBeotnqcT5qJi9pPuPfqSapdAD870Ckgh79c41UFywL6kMqtiyY+kxtfcqZGg==", "dev": true, "license": "MIT", "dependencies": { - "@commitlint/top-level": "^20.4.3", - "@commitlint/types": "^20.5.0", + "@commitlint/top-level": "^21.0.0", + "@commitlint/types": "^21.0.0", "git-raw-commits": "^5.0.0", - "minimist": "^1.2.8", "tinyexec": "^1.0.0" }, "engines": { - "node": ">=v18" + "node": ">=22.12.0" } }, "node_modules/@commitlint/resolve-extends": { - "version": "20.5.3", - "resolved": "https://registry.npmjs.org/@commitlint/resolve-extends/-/resolve-extends-20.5.3.tgz", - "integrity": "sha512-+ogW9v/u9JqpvAgTrLra/YTFo0KkjU6iNblF89pPsj4NebNc+DAWctsludwezI8YnsjBmfHpApSwcXprN/f/ew==", + "version": "21.0.0", + "resolved": "https://registry.npmjs.org/@commitlint/resolve-extends/-/resolve-extends-21.0.0.tgz", + "integrity": "sha512-hrJYSZRpmecmSoxYrpuJ/1Q4J9JHt4AVVtr5/Ac6upLO/jJ1DnIm2AjD+38gru3KGOec4aHCVqETuWWLJhydWw==", "dev": true, "license": "MIT", "dependencies": { - "@commitlint/config-validator": "^20.5.0", - "@commitlint/types": "^20.5.0", + "@commitlint/config-validator": "^21.0.0", + "@commitlint/types": "^21.0.0", "es-toolkit": "^1.46.0", "global-directory": "^5.0.0", - "import-meta-resolve": "^4.0.0", "resolve-from": "^5.0.0" }, "engines": { - "node": ">=v18" + "node": ">=22.12.0" } }, "node_modules/@commitlint/rules": { - "version": "20.5.3", - "resolved": "https://registry.npmjs.org/@commitlint/rules/-/rules-20.5.3.tgz", - "integrity": "sha512-MPlMnb9D3wbszYMp+1hPtuhtPJndRo6I6yfkZVA4+jR8w7Kqp0u2u/Y+gzbaItx5Lltq5rw7FSZQWJMoXUC4NQ==", + "version": "21.0.0", + "resolved": "https://registry.npmjs.org/@commitlint/rules/-/rules-21.0.0.tgz", + "integrity": "sha512-NgQhX1qENA+rbrMw5KKyvVZpZG4D/0wgK8Z4INtcwKbfKtVDFMbn0oNc/Rs8wdyBPBj7ue8Lo/GllUL2Mqjwkg==", "dev": true, "license": "MIT", "dependencies": { - "@commitlint/ensure": "^20.5.3", - "@commitlint/message": "^20.4.3", - "@commitlint/to-lines": "^20.0.0", - "@commitlint/types": "^20.5.0" + "@commitlint/ensure": "^21.0.0", + "@commitlint/message": "^21.0.0", + "@commitlint/to-lines": "^21.0.0", + "@commitlint/types": "^21.0.0" }, "engines": { - "node": ">=v18" + "node": ">=22.12.0" } }, "node_modules/@commitlint/to-lines": { - "version": "20.0.0", - "resolved": "https://registry.npmjs.org/@commitlint/to-lines/-/to-lines-20.0.0.tgz", - "integrity": "sha512-2l9gmwiCRqZNWgV+pX1X7z4yP0b3ex/86UmUFgoRt672Ez6cAM2lOQeHFRUTuE6sPpi8XBCGnd8Kh3bMoyHwJw==", + "version": "21.0.0", + "resolved": "https://registry.npmjs.org/@commitlint/to-lines/-/to-lines-21.0.0.tgz", + "integrity": "sha512-qMwvrJK/x3dPcXsIAtQAMKV5Q0wTioyqyHKR06vVN4wmBF4cCrrLq5x81FDeY3Ba+GWgDt0/P3Zw/IHGM8lwgg==", "dev": true, "license": "MIT", "engines": { - "node": ">=v18" + "node": ">=22.12.0" } }, "node_modules/@commitlint/top-level": { - "version": "20.4.3", - "resolved": "https://registry.npmjs.org/@commitlint/top-level/-/top-level-20.4.3.tgz", - "integrity": "sha512-qD9xfP6dFg5jQ3NMrOhG0/w5y3bBUsVGyJvXxdWEwBm8hyx4WOk3kKXw28T5czBYvyeCVJgJJ6aoJZUWDpaacQ==", + "version": "21.0.0", + "resolved": "https://registry.npmjs.org/@commitlint/top-level/-/top-level-21.0.0.tgz", + "integrity": "sha512-8jPqyWZueuN4hU6/ArKVsZ6i8xWtjIrbzHEOaLaTGUfjhhbZNBfXef/DGjzxy55hAv3yFNxHLINfI1bCJ0/MzA==", "dev": true, "license": "MIT", "dependencies": { "escalade": "^3.2.0" }, "engines": { - "node": ">=v18" + "node": ">=22.12.0" } }, "node_modules/@commitlint/types": { - "version": "20.5.0", - "resolved": "https://registry.npmjs.org/@commitlint/types/-/types-20.5.0.tgz", - "integrity": "sha512-ZJoS8oSq2CAZEpc/YI9SulLrdiIyXeHb/OGqGrkUP6Q7YV+0ouNAa7GjqRdXeQPncHQIDz/jbCTlHScvYvO/gA==", + "version": "21.0.0", + "resolved": "https://registry.npmjs.org/@commitlint/types/-/types-21.0.0.tgz", + "integrity": "sha512-6nEz+M7I90iix4sviA8NLwskOuyt0M98KUU2aYgiKbn46jMSxUm1l2ACtzRd9ec+y38aKyJhW4Fp6NW0z35kJQ==", "dev": true, "license": "MIT", "dependencies": { @@ -512,7 +510,7 @@ "picocolors": "^1.1.1" }, "engines": { - "node": ">=v18" + "node": ">=22.12.0" } }, "node_modules/@conventional-changelog/git-client": { @@ -1111,9 +1109,9 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "25.6.1", - "resolved": "https://registry.npmjs.org/@types/node/-/node-25.6.1.tgz", - "integrity": "sha512-coJCN8O1q4AGyyqCAUSP06P+SrMTu18BkEj3NVAK07q6QUneD2wzj3CLv9+yP+BMeZQlMvneXqqvDe3w+xcq7g==", + "version": "25.6.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.6.2.tgz", + "integrity": "sha512-sokuT28dxf9JT5Kady1fsXOvI4HVpjZa95NKT5y9PNTIrs2AsobR4GFAA90ZG8M+nxVRLysCXsVj6eGC7Vbrlw==", "dev": true, "license": "MIT", "dependencies": { @@ -1287,26 +1285,26 @@ } }, "node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", "dev": true, "license": "MIT", "engines": { - "node": ">=8" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" } }, "node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", "dev": true, "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, "engines": { - "node": ">=8" + "node": ">=12" }, "funding": { "url": "https://github.com/chalk/ansi-styles?sponsor=1" @@ -1395,37 +1393,34 @@ "license": "MIT" }, "node_modules/cliui": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", - "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-9.0.1.tgz", + "integrity": "sha512-k7ndgKhwoQveBL+/1tqGJYNz097I7WOvwbmmU2AR5+magtbjPWQTS1C5vzGkBC8Ym8UWRzfKUzUUqFLypY4Q+w==", "dev": true, "license": "ISC", "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.1", - "wrap-ansi": "^7.0.0" + "string-width": "^7.2.0", + "strip-ansi": "^7.1.0", + "wrap-ansi": "^9.0.0" }, "engines": { - "node": ">=12" + "node": ">=20" } }, "node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", "dev": true, "license": "MIT", "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" + "color-name": "1.1.3" } }, "node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", "dev": true, "license": "MIT" }, @@ -1607,9 +1602,9 @@ } }, "node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", + "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", "dev": true, "license": "MIT" }, @@ -1865,6 +1860,19 @@ "node": "6.* || 8.* || >= 10.*" } }, + "node_modules/get-east-asian-width": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.6.0.tgz", + "integrity": "sha512-QRbvDIbx6YklUe6RxeTeleMR0yv3cYH6PsPZHcnVn7xv7zO1BHN8r0XETu8n6Ye3Q+ahtSarc3WgtNWmehIBfA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/git-raw-commits": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/git-raw-commits/-/git-raw-commits-5.0.1.tgz", @@ -2041,17 +2049,6 @@ "node": ">=4" } }, - "node_modules/import-meta-resolve": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/import-meta-resolve/-/import-meta-resolve-4.2.0.tgz", - "integrity": "sha512-Iqv2fzaTQN28s/FwZAoFq0ZSs/7hMAHJVX+w8PZl3cY19Pxk6jFFalxQoIfW2826i/fDLXv8IiEZRIT0lDuWcg==", - "dev": true, - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, "node_modules/inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", @@ -2099,13 +2096,13 @@ } }, "node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w==", "dev": true, "license": "MIT", "engines": { - "node": ">=8" + "node": ">=4" } }, "node_modules/is-glob": { @@ -2281,23 +2278,6 @@ "wrap-ansi": "^5.1.0" } }, - "node_modules/license-check-and-add/node_modules/color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-name": "1.1.3" - } - }, - "node_modules/license-check-and-add/node_modules/color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", - "dev": true, - "license": "MIT" - }, "node_modules/license-check-and-add/node_modules/emoji-regex": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", @@ -2305,16 +2285,6 @@ "dev": true, "license": "MIT" }, - "node_modules/license-check-and-add/node_modules/is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, "node_modules/license-check-and-add/node_modules/string-width": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", @@ -2451,6 +2421,159 @@ "node": ">=16.0.0" } }, + "node_modules/lockfile-lint/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/lockfile-lint/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/lockfile-lint/node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/lockfile-lint/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/lockfile-lint/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/lockfile-lint/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/lockfile-lint/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/lockfile-lint/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/lockfile-lint/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/lockfile-lint/node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/lockfile-lint/node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/lockfile-lint/node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, "node_modules/meow": { "version": "13.2.0", "resolved": "https://registry.npmjs.org/meow/-/meow-13.2.0.tgz", @@ -2501,16 +2624,6 @@ "node": "*" } }, - "node_modules/minimist": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", - "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/moo": { "version": "0.5.3", "resolved": "https://registry.npmjs.org/moo/-/moo-0.5.3.tgz", @@ -2888,9 +3001,9 @@ } }, "node_modules/semver": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", - "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.0.tgz", + "integrity": "sha512-AcM7dV/5ul4EekoQ29Agm5vri8JNqRyj39o0qpX6vDF2GZrtutZl5RwgD1XnZjiTAfncsJhMI48QQH3sN87YNA==", "dev": true, "license": "ISC", "bin": { @@ -2938,31 +3051,37 @@ "license": "BSD-3-Clause" }, "node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", "dev": true, "license": "MIT", "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" }, "engines": { - "node": ">=8" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", "dev": true, "license": "MIT", "dependencies": { - "ansi-regex": "^5.0.1" + "ansi-regex": "^6.2.2" }, "engines": { - "node": ">=8" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" } }, "node_modules/tinybench": { @@ -3052,18 +3171,18 @@ "license": "ISC" }, "node_modules/wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz", + "integrity": "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==", "dev": true, "license": "MIT", "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" + "ansi-styles": "^6.2.1", + "string-width": "^7.0.0", + "strip-ansi": "^7.1.0" }, "engines": { - "node": ">=10" + "node": ">=18" }, "funding": { "url": "https://github.com/chalk/wrap-ansi?sponsor=1" @@ -3087,32 +3206,31 @@ } }, "node_modules/yargs": { - "version": "17.7.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", - "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "version": "18.0.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-18.0.0.tgz", + "integrity": "sha512-4UEqdc2RYGHZc7Doyqkrqiln3p9X2DZVxaGbwhn2pi7MrRagKaOcIKe8L3OxYcbhXLgLFUS3zAYuQjKBQgmuNg==", "dev": true, "license": "MIT", "dependencies": { - "cliui": "^8.0.1", + "cliui": "^9.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.3", + "string-width": "^7.2.0", "y18n": "^5.0.5", - "yargs-parser": "^21.1.1" + "yargs-parser": "^22.0.0" }, "engines": { - "node": ">=12" + "node": "^20.19.0 || ^22.12.0 || >=23" } }, "node_modules/yargs-parser": { - "version": "21.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", - "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "version": "22.0.0", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-22.0.0.tgz", + "integrity": "sha512-rwu/ClNdSMpkSrUb+d6BRsSkLUq1fmfsY6TOpYzTwvwkg1/NRG85KBy3kq++A8LKQwX6lsu+aWad+2khvuXrqw==", "dev": true, "license": "ISC", "engines": { - "node": ">=12" + "node": "^20.19.0 || ^22.12.0 || >=23" } } } diff --git a/package.json b/package.json index 746342e..935e053 100644 --- a/package.json +++ b/package.json @@ -86,8 +86,8 @@ }, "devDependencies": { "@biomejs/biome": "^2.0.0", - "@commitlint/cli": "^20.0.0", - "@commitlint/config-conventional": "^20.0.0", + "@commitlint/cli": "^21.0.0", + "@commitlint/config-conventional": "^21.0.0", "ajv-cmd": "^0.13.0", "fast-check": "^4.0.0", "husky": "^9.0.0", From 3e79a2fba3b7304dfb42ea3da7967cad407e12cc Mon Sep 17 00:00:00 2001 From: will Farrell Date: Sun, 10 May 2026 05:00:54 -0600 Subject: [PATCH 03/13] chore: dep update Signed-off-by: will Farrell --- biome.json | 2 +- package-lock.json | 82 +++++++++++++++++++++++++++-------------------- 2 files changed, 48 insertions(+), 36 deletions(-) diff --git a/biome.json b/biome.json index 388e00c..5873611 100644 --- a/biome.json +++ b/biome.json @@ -1,5 +1,5 @@ { - "$schema": "https://biomejs.dev/schemas/2.4.14/schema.json", + "$schema": "https://biomejs.dev/schemas/2.4.15/schema.json", "vcs": { "enabled": false, "clientKind": "git", diff --git a/package-lock.json b/package-lock.json index 1011f8a..efae58a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -89,9 +89,9 @@ } }, "node_modules/@biomejs/biome": { - "version": "2.4.14", - "resolved": "https://registry.npmjs.org/@biomejs/biome/-/biome-2.4.14.tgz", - "integrity": "sha512-TmAvxOEgrpLypzVGJ8FulIZnlyA9TxrO1hyqYrCz9r+bwma9xXxuLA5IuYnj55XQneFx460KjRbx6SWGLkg3bQ==", + "version": "2.4.15", + "resolved": "https://registry.npmjs.org/@biomejs/biome/-/biome-2.4.15.tgz", + "integrity": "sha512-j5VH3a/h/HXTKBM50MDMxRCzkeLv9S2XJcW2WgnZT1+xyisi+0bISrXR82gCX+8S9lvK0skEvHJRN+3Ktr2hlw==", "dev": true, "license": "MIT OR Apache-2.0", "bin": { @@ -105,20 +105,20 @@ "url": "https://opencollective.com/biome" }, "optionalDependencies": { - "@biomejs/cli-darwin-arm64": "2.4.14", - "@biomejs/cli-darwin-x64": "2.4.14", - "@biomejs/cli-linux-arm64": "2.4.14", - "@biomejs/cli-linux-arm64-musl": "2.4.14", - "@biomejs/cli-linux-x64": "2.4.14", - "@biomejs/cli-linux-x64-musl": "2.4.14", - "@biomejs/cli-win32-arm64": "2.4.14", - "@biomejs/cli-win32-x64": "2.4.14" + "@biomejs/cli-darwin-arm64": "2.4.15", + "@biomejs/cli-darwin-x64": "2.4.15", + "@biomejs/cli-linux-arm64": "2.4.15", + "@biomejs/cli-linux-arm64-musl": "2.4.15", + "@biomejs/cli-linux-x64": "2.4.15", + "@biomejs/cli-linux-x64-musl": "2.4.15", + "@biomejs/cli-win32-arm64": "2.4.15", + "@biomejs/cli-win32-x64": "2.4.15" } }, "node_modules/@biomejs/cli-darwin-arm64": { - "version": "2.4.14", - "resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-arm64/-/cli-darwin-arm64-2.4.14.tgz", - "integrity": "sha512-XvgoE9XOawUOQPdmvs4J7wPhi/DLwSCGks3AlPJDmh34O0awRTqCED1HRcRDdpf1Zrp4us4MGOOdIxNpbqNF5Q==", + "version": "2.4.15", + "resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-arm64/-/cli-darwin-arm64-2.4.15.tgz", + "integrity": "sha512-rF3PPqLq1yoST79zaQbDjVJwsuIeci/O+9bgNmC5QpgOqz6aqYuzA4abyAGx+mgyiDXn4A049xAN8gijbuR1Qg==", "cpu": [ "arm64" ], @@ -133,9 +133,9 @@ } }, "node_modules/@biomejs/cli-darwin-x64": { - "version": "2.4.14", - "resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-x64/-/cli-darwin-x64-2.4.14.tgz", - "integrity": "sha512-jE7hKBCFhOx3uUh+ZkWBfOHxAcILPfhFplNkuID/eZeSTLHzfZzoZxW8fbqY9xXRnPi7jGNAf1iPVR+0yWsM/Q==", + "version": "2.4.15", + "resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-x64/-/cli-darwin-x64-2.4.15.tgz", + "integrity": "sha512-/5KHXYMfSJs1fNXiX30xFtI8JcCFV6zaVVLxOa0M2sfqBKHkpQhRTv94yxQWxeTY2lzo2OuTlNvPC+hDQt2wcQ==", "cpu": [ "x64" ], @@ -150,13 +150,16 @@ } }, "node_modules/@biomejs/cli-linux-arm64": { - "version": "2.4.14", - "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64/-/cli-linux-arm64-2.4.14.tgz", - "integrity": "sha512-2TELhZnW5RSLL063l9rc5xLpA0ZIw0Ccwy/0q384rvNAgFw3yI76bd59547yxowdQr5MNPET/xDLrLuvgSeeWQ==", + "version": "2.4.15", + "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64/-/cli-linux-arm64-2.4.15.tgz", + "integrity": "sha512-owaAMZD/T4LrD0ELNCk0Km3qrRHuM0X6EAyVE1FSqGY0rbLoiDLrO4Us2tllm6cAeB2Ioa9C2C08NZPdr8+0Ug==", "cpu": [ "arm64" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT OR Apache-2.0", "optional": true, "os": [ @@ -167,13 +170,16 @@ } }, "node_modules/@biomejs/cli-linux-arm64-musl": { - "version": "2.4.14", - "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64-musl/-/cli-linux-arm64-musl-2.4.14.tgz", - "integrity": "sha512-/z+6gqAqqUQTHazwStxSXKHg9b8UvqBmDFRp+c4wYbq2KXhELQDon9EoC9RpmQ8JWkqQx/lIUy/cs+MhzDZp6A==", + "version": "2.4.15", + "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64-musl/-/cli-linux-arm64-musl-2.4.15.tgz", + "integrity": "sha512-ZPcxznxm0pogHBLZhYntyR3sR+MrZjqJIKEr7ZqVen0Rl+P/4upVmfYXjftizi9RoqZntg33fv/1fbdhbYXpEQ==", "cpu": [ "arm64" ], "dev": true, + "libc": [ + "musl" + ], "license": "MIT OR Apache-2.0", "optional": true, "os": [ @@ -184,13 +190,16 @@ } }, "node_modules/@biomejs/cli-linux-x64": { - "version": "2.4.14", - "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64/-/cli-linux-x64-2.4.14.tgz", - "integrity": "sha512-zHrlQZDBDUz4OLAraYpWKcnLS6HOewBFWYOzY91d1ZjdqZwibOyb6BEu6WuWLugyo0P3riCmsbV9UqV1cSXwQg==", + "version": "2.4.15", + "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64/-/cli-linux-x64-2.4.15.tgz", + "integrity": "sha512-0jj7THz12GbUOLmMibktK6DZjqz2zV64KFxyBtcFTKPiiOIY0a7vns1elpO1dERvxpsZ5ik0oFfz0oGwFde1+g==", "cpu": [ "x64" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT OR Apache-2.0", "optional": true, "os": [ @@ -201,13 +210,16 @@ } }, "node_modules/@biomejs/cli-linux-x64-musl": { - "version": "2.4.14", - "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64-musl/-/cli-linux-x64-musl-2.4.14.tgz", - "integrity": "sha512-R6BWgJdQOwW9ulJatuTVrQkjnODjqHZkKNOqb1sz++3Noe5LYd0i3PchnOBUCYAPHoPWHhjJqbdZlHEu0hpjdA==", + "version": "2.4.15", + "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64-musl/-/cli-linux-x64-musl-2.4.15.tgz", + "integrity": "sha512-CNq/9W38SYSH023lfcQ4KKU8K0YX8T//FZUhcgtMMRABDojx5XsMV7jlweAvGSl389wJQB29Qo6Zb/a+jdvt+w==", "cpu": [ "x64" ], "dev": true, + "libc": [ + "musl" + ], "license": "MIT OR Apache-2.0", "optional": true, "os": [ @@ -218,9 +230,9 @@ } }, "node_modules/@biomejs/cli-win32-arm64": { - "version": "2.4.14", - "resolved": "https://registry.npmjs.org/@biomejs/cli-win32-arm64/-/cli-win32-arm64-2.4.14.tgz", - "integrity": "sha512-M3EH5hqOI/F/FUA2u4xcLoUgmxd218mvuj/6JL7Hv2toQvr2/AdOvKSpGkoRuWFCtQPVa+ZqkEV3Q5xBA9+XSA==", + "version": "2.4.15", + "resolved": "https://registry.npmjs.org/@biomejs/cli-win32-arm64/-/cli-win32-arm64-2.4.15.tgz", + "integrity": "sha512-ouhkYdlhp/1GghEJPdWwD/Vi3gQ1nFxuSpMolWsbq3Lsq3QUR4jl6UdhhscdCugKU5vOEuMiJhvKj66O0OCq+w==", "cpu": [ "arm64" ], @@ -235,9 +247,9 @@ } }, "node_modules/@biomejs/cli-win32-x64": { - "version": "2.4.14", - "resolved": "https://registry.npmjs.org/@biomejs/cli-win32-x64/-/cli-win32-x64-2.4.14.tgz", - "integrity": "sha512-WL0EG5qE+EAKomGXbf2g6VnSKJhTL3tXC0QRzWRwA5VpjxNYa6H4P7ZWfymbGE4IhZZQi1KXQ2R0YjwInmz2fA==", + "version": "2.4.15", + "resolved": "https://registry.npmjs.org/@biomejs/cli-win32-x64/-/cli-win32-x64-2.4.15.tgz", + "integrity": "sha512-zBrGq5mx5wwpnow4+2BxUvleDM+GNd4sLbPaMapsSLQLD0NGRCquqPBTgN+7XkUteHvj7M+BstuI8tmnV7+HgQ==", "cpu": [ "x64" ], From b3d6b53cd11746e3a6326ac009f51345e27c57fc Mon Sep 17 00:00:00 2001 From: will Farrell Date: Tue, 12 May 2026 19:12:38 -0600 Subject: [PATCH 04/13] chore: dep update Signed-off-by: will Farrell --- package-lock.json | 176 +++++++++++++++++++++++----------------------- 1 file changed, 88 insertions(+), 88 deletions(-) diff --git a/package-lock.json b/package-lock.json index efae58a..44e451c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -264,17 +264,17 @@ } }, "node_modules/@commitlint/cli": { - "version": "21.0.0", - "resolved": "https://registry.npmjs.org/@commitlint/cli/-/cli-21.0.0.tgz", - "integrity": "sha512-p3y2oC0G2R45zaadMwBxCiSesS8digi5RDplP3Zrfpzm7xIgrgAj0W4fGzONjpHyg8obDVJDU45g5txzeMcblg==", + "version": "21.0.1", + "resolved": "https://registry.npmjs.org/@commitlint/cli/-/cli-21.0.1.tgz", + "integrity": "sha512-8vq10krmbJwBkvzXKhbs4o4JQEVscd3pqOlWuDUaDBwbeL694/P33UC29tZQFTAgPU9fVJ2+f2m3zw16yKWxHg==", "dev": true, "license": "MIT", "dependencies": { - "@commitlint/format": "^21.0.0", - "@commitlint/lint": "^21.0.0", - "@commitlint/load": "^21.0.0", - "@commitlint/read": "^21.0.0", - "@commitlint/types": "^21.0.0", + "@commitlint/format": "^21.0.1", + "@commitlint/lint": "^21.0.1", + "@commitlint/load": "^21.0.1", + "@commitlint/read": "^21.0.1", + "@commitlint/types": "^21.0.1", "tinyexec": "^1.0.0", "yargs": "^18.0.0" }, @@ -286,13 +286,13 @@ } }, "node_modules/@commitlint/config-conventional": { - "version": "21.0.0", - "resolved": "https://registry.npmjs.org/@commitlint/config-conventional/-/config-conventional-21.0.0.tgz", - "integrity": "sha512-QJX/rPK4Yu3f5J4OCIBy5aXq2e0EEdwSDFZ3NQvFAXTm3gs12ipyZ+yjhZxm3hHn6DB8wuv3zhFTL1I2tYzUBA==", + "version": "21.0.1", + "resolved": "https://registry.npmjs.org/@commitlint/config-conventional/-/config-conventional-21.0.1.tgz", + "integrity": "sha512-gRorrkfWOh/+V5X8GYWWbQvrzPczopGMS4CCNrQdHkK4xWElv82BDvIsDhJZWTlI7TazOlYea6VATufCsFs+sw==", "dev": true, "license": "MIT", "dependencies": { - "@commitlint/types": "^21.0.0", + "@commitlint/types": "^21.0.1", "conventional-changelog-conventionalcommits": "^9.2.0" }, "engines": { @@ -300,13 +300,13 @@ } }, "node_modules/@commitlint/config-validator": { - "version": "21.0.0", - "resolved": "https://registry.npmjs.org/@commitlint/config-validator/-/config-validator-21.0.0.tgz", - "integrity": "sha512-v0UplTYryNUB463X5WrelzKq5/qyYm9/iUNk38S7ZLnd56Uuk2T9awhYKGlgD2/4L5YuN2gsKkyy4EHpRPPz2Q==", + "version": "21.0.1", + "resolved": "https://registry.npmjs.org/@commitlint/config-validator/-/config-validator-21.0.1.tgz", + "integrity": "sha512-Zd2UFdndeMMaW2O96HK0tdfT4gOImUvidMpAd/pws2zZ4m1nrAZ/9b/v2JYuE8fs86GpXv9F7LNaIuCIWhY+pA==", "dev": true, "license": "MIT", "dependencies": { - "@commitlint/types": "^21.0.0", + "@commitlint/types": "^21.0.1", "ajv": "^8.11.0" }, "engines": { @@ -314,13 +314,13 @@ } }, "node_modules/@commitlint/ensure": { - "version": "21.0.0", - "resolved": "https://registry.npmjs.org/@commitlint/ensure/-/ensure-21.0.0.tgz", - "integrity": "sha512-n+OYs0Ws9GKC2WlmAeLNoPz9CUg6n/ZyYMkFF8rJ0aMn2kDTDTG0VqK/2Dco0EB4fhuF3JPIllJmU9/LKTl4aw==", + "version": "21.0.1", + "resolved": "https://registry.npmjs.org/@commitlint/ensure/-/ensure-21.0.1.tgz", + "integrity": "sha512-jJ1037967wU7YN/xkv+iRlOBlmaOXPhPO5KQSqya6GyXzBlwuLzELBFao16DVg9dZyqmNrhewzwZ3SAibetHBQ==", "dev": true, "license": "MIT", "dependencies": { - "@commitlint/types": "^21.0.0", + "@commitlint/types": "^21.0.1", "es-toolkit": "^1.46.0" }, "engines": { @@ -328,9 +328,9 @@ } }, "node_modules/@commitlint/execute-rule": { - "version": "21.0.0", - "resolved": "https://registry.npmjs.org/@commitlint/execute-rule/-/execute-rule-21.0.0.tgz", - "integrity": "sha512-3OhTq2gQX1tEheMsbDNqxfcNHsAM6g9cub9plf05I9jCxtbNfn8Y+mhClKyUwhX4dbtmC4OLZ9i+HNmoL1aksA==", + "version": "21.0.1", + "resolved": "https://registry.npmjs.org/@commitlint/execute-rule/-/execute-rule-21.0.1.tgz", + "integrity": "sha512-RifH+FmImozKBE6mozhF4K3r2RRKP7SMi/Q/zLCmExtp5e05lhHOUYqGBlFBAGNHaZxU/WYw1XuugYK9jQzqnA==", "dev": true, "license": "MIT", "engines": { @@ -338,13 +338,13 @@ } }, "node_modules/@commitlint/format": { - "version": "21.0.0", - "resolved": "https://registry.npmjs.org/@commitlint/format/-/format-21.0.0.tgz", - "integrity": "sha512-RTfGSrueEgofs1piqwi42U05d85wfxiMH2ncMCZnltx1XqPR3N2S48oACBtTy4xRAhWlf5XlHkK2RaDzEQu3dA==", + "version": "21.0.1", + "resolved": "https://registry.npmjs.org/@commitlint/format/-/format-21.0.1.tgz", + "integrity": "sha512-ksmG2+cHGtuDPQQbhBbC4unwm444+6TiPw0d1bKf67hntgZqZ8E0g1MuYKUuyT5IH4IMmXZhKq22/Z3jBvtQIw==", "dev": true, "license": "MIT", "dependencies": { - "@commitlint/types": "^21.0.0", + "@commitlint/types": "^21.0.1", "picocolors": "^1.1.1" }, "engines": { @@ -352,13 +352,13 @@ } }, "node_modules/@commitlint/is-ignored": { - "version": "21.0.0", - "resolved": "https://registry.npmjs.org/@commitlint/is-ignored/-/is-ignored-21.0.0.tgz", - "integrity": "sha512-K3SaaOTVY9VKhge7vl0R3ng7GENRzJQ9MPV43Tu53kAwEgSx/E0HF4US3AcVqdvlvsDUbF2yXvED95dhela83w==", + "version": "21.0.1", + "resolved": "https://registry.npmjs.org/@commitlint/is-ignored/-/is-ignored-21.0.1.tgz", + "integrity": "sha512-iNDP8SFdw8JEkM0CHZ2XFnhTN4Zg5jKUY2d8kBOSFrI2aA+3YJI7fcqVpfgbpJ9xtxFVYpi+DBATU5AvhoTq8g==", "dev": true, "license": "MIT", "dependencies": { - "@commitlint/types": "^21.0.0", + "@commitlint/types": "^21.0.1", "semver": "^7.6.0" }, "engines": { @@ -366,32 +366,32 @@ } }, "node_modules/@commitlint/lint": { - "version": "21.0.0", - "resolved": "https://registry.npmjs.org/@commitlint/lint/-/lint-21.0.0.tgz", - "integrity": "sha512-dlUJA0Ka14R1YaR46JVRWE3m/8dOQAgE/D0heUfzYua5Jogtq/zzu2ITAIaB/u25DaKjtEO6kuvASzsFDyrPMw==", + "version": "21.0.1", + "resolved": "https://registry.npmjs.org/@commitlint/lint/-/lint-21.0.1.tgz", + "integrity": "sha512-gF+iYtUw1gBG3HUH9z3VxwUjGg2R2G5j+nmvPs8aIeYkiB7TtneBu3wO85I0bUl93bYNsvsCNI9Nte2fmDUMww==", "dev": true, "license": "MIT", "dependencies": { - "@commitlint/is-ignored": "^21.0.0", - "@commitlint/parse": "^21.0.0", - "@commitlint/rules": "^21.0.0", - "@commitlint/types": "^21.0.0" + "@commitlint/is-ignored": "^21.0.1", + "@commitlint/parse": "^21.0.1", + "@commitlint/rules": "^21.0.1", + "@commitlint/types": "^21.0.1" }, "engines": { "node": ">=22.12.0" } }, "node_modules/@commitlint/load": { - "version": "21.0.0", - "resolved": "https://registry.npmjs.org/@commitlint/load/-/load-21.0.0.tgz", - "integrity": "sha512-l0nBfO/20PKcJXHZqDIgh7kw/TWVVwn8zZJOkVGBK/ig/h328jBu9jK7OiDl2oZr5mLphmKGjYDR2ffEyb2lIA==", + "version": "21.0.1", + "resolved": "https://registry.npmjs.org/@commitlint/load/-/load-21.0.1.tgz", + "integrity": "sha512-Btg1q1mKmiihN4W3x0EsPDrJMOQfMa9NIqlzlJyXAfxvsOGdGXOW5p3R3RcSxDCaY7JabY9flIl+Om1af3PSrw==", "dev": true, "license": "MIT", "dependencies": { - "@commitlint/config-validator": "^21.0.0", - "@commitlint/execute-rule": "^21.0.0", - "@commitlint/resolve-extends": "^21.0.0", - "@commitlint/types": "^21.0.0", + "@commitlint/config-validator": "^21.0.1", + "@commitlint/execute-rule": "^21.0.1", + "@commitlint/resolve-extends": "^21.0.1", + "@commitlint/types": "^21.0.1", "cosmiconfig": "^9.0.1", "cosmiconfig-typescript-loader": "^6.1.0", "es-toolkit": "^1.46.0", @@ -403,9 +403,9 @@ } }, "node_modules/@commitlint/message": { - "version": "21.0.0", - "resolved": "https://registry.npmjs.org/@commitlint/message/-/message-21.0.0.tgz", - "integrity": "sha512-+daU92JaOHhI2En9KcH+2mvZGJ6D4YSxb/32QDwqkOwSj1Vanjio8PbAqX7dneACdg6B7RgQ7i3mpyYZAws4nw==", + "version": "21.0.1", + "resolved": "https://registry.npmjs.org/@commitlint/message/-/message-21.0.1.tgz", + "integrity": "sha512-R3dVQeJQ0B6yqrZEjkUHD4r7UJYLV9Lvk2xs3PTOmtWk2G3mI6Xgc+YdRxL1PwcDfBiUjv2SkIkW4AUc976w1w==", "dev": true, "license": "MIT", "engines": { @@ -413,13 +413,13 @@ } }, "node_modules/@commitlint/parse": { - "version": "21.0.0", - "resolved": "https://registry.npmjs.org/@commitlint/parse/-/parse-21.0.0.tgz", - "integrity": "sha512-1dbvFBcQK79aTbpc2QCrgEDc6/MMkQ0Mdz4gGmYkN4AHMnAK9HesSewTHqGTrW5mALrMlYSgcWyvKjloY2w19A==", + "version": "21.0.1", + "resolved": "https://registry.npmjs.org/@commitlint/parse/-/parse-21.0.1.tgz", + "integrity": "sha512-oh/nCSOqdoeQNA1tO8aAmxkq5EBo8/NzcFQRvv66AWc9HpED28sL2iSicCKU6hPintWuscL6BJEWi77Wq1LPMQ==", "dev": true, "license": "MIT", "dependencies": { - "@commitlint/types": "^21.0.0", + "@commitlint/types": "^21.0.1", "conventional-changelog-angular": "^8.2.0", "conventional-commits-parser": "^6.3.0" }, @@ -428,14 +428,14 @@ } }, "node_modules/@commitlint/read": { - "version": "21.0.0", - "resolved": "https://registry.npmjs.org/@commitlint/read/-/read-21.0.0.tgz", - "integrity": "sha512-8VKLKLl2vBSKoTMm1LwcySsyxrBeotnqcT5qJi9pPuPfqSapdAD870Ckgh79c41UFywL6kMqtiyY+kxtfcqZGg==", + "version": "21.0.1", + "resolved": "https://registry.npmjs.org/@commitlint/read/-/read-21.0.1.tgz", + "integrity": "sha512-pMEu4lbpC8W0ZgKJj2U6WaobXIZWdFlULpIEewYhkPXx+WZcnoO53YrVPc7QErQuNolq2Me8dP58Wu7YAVXVOA==", "dev": true, "license": "MIT", "dependencies": { - "@commitlint/top-level": "^21.0.0", - "@commitlint/types": "^21.0.0", + "@commitlint/top-level": "^21.0.1", + "@commitlint/types": "^21.0.1", "git-raw-commits": "^5.0.0", "tinyexec": "^1.0.0" }, @@ -444,14 +444,14 @@ } }, "node_modules/@commitlint/resolve-extends": { - "version": "21.0.0", - "resolved": "https://registry.npmjs.org/@commitlint/resolve-extends/-/resolve-extends-21.0.0.tgz", - "integrity": "sha512-hrJYSZRpmecmSoxYrpuJ/1Q4J9JHt4AVVtr5/Ac6upLO/jJ1DnIm2AjD+38gru3KGOec4aHCVqETuWWLJhydWw==", + "version": "21.0.1", + "resolved": "https://registry.npmjs.org/@commitlint/resolve-extends/-/resolve-extends-21.0.1.tgz", + "integrity": "sha512-0DhjYWL6uYrY16Efa032fYk3woGJDU4AGWiG1XXltT9AMUNYKyb5cIZU2ivbaMZ3+kKFqUjikD2cjh66Sbh/Sg==", "dev": true, "license": "MIT", "dependencies": { - "@commitlint/config-validator": "^21.0.0", - "@commitlint/types": "^21.0.0", + "@commitlint/config-validator": "^21.0.1", + "@commitlint/types": "^21.0.1", "es-toolkit": "^1.46.0", "global-directory": "^5.0.0", "resolve-from": "^5.0.0" @@ -461,25 +461,25 @@ } }, "node_modules/@commitlint/rules": { - "version": "21.0.0", - "resolved": "https://registry.npmjs.org/@commitlint/rules/-/rules-21.0.0.tgz", - "integrity": "sha512-NgQhX1qENA+rbrMw5KKyvVZpZG4D/0wgK8Z4INtcwKbfKtVDFMbn0oNc/Rs8wdyBPBj7ue8Lo/GllUL2Mqjwkg==", + "version": "21.0.1", + "resolved": "https://registry.npmjs.org/@commitlint/rules/-/rules-21.0.1.tgz", + "integrity": "sha512-VMooYpz4nJg7xlaUso6CCOWEz8D/ChkvsvZUMARcoJ1ZpfKPyFCGrHNha2tbsETNAb6ErgiRuCr2DvghrvPDYQ==", "dev": true, "license": "MIT", "dependencies": { - "@commitlint/ensure": "^21.0.0", - "@commitlint/message": "^21.0.0", - "@commitlint/to-lines": "^21.0.0", - "@commitlint/types": "^21.0.0" + "@commitlint/ensure": "^21.0.1", + "@commitlint/message": "^21.0.1", + "@commitlint/to-lines": "^21.0.1", + "@commitlint/types": "^21.0.1" }, "engines": { "node": ">=22.12.0" } }, "node_modules/@commitlint/to-lines": { - "version": "21.0.0", - "resolved": "https://registry.npmjs.org/@commitlint/to-lines/-/to-lines-21.0.0.tgz", - "integrity": "sha512-qMwvrJK/x3dPcXsIAtQAMKV5Q0wTioyqyHKR06vVN4wmBF4cCrrLq5x81FDeY3Ba+GWgDt0/P3Zw/IHGM8lwgg==", + "version": "21.0.1", + "resolved": "https://registry.npmjs.org/@commitlint/to-lines/-/to-lines-21.0.1.tgz", + "integrity": "sha512-bd1BFII7p1EQZre9Kaj+kKaMFP3cFCdt21K7DItVux9XP5WjLgJ0/Uy1pJJh9aPwVJ6SKg62PxqlZaHI8hQAXw==", "dev": true, "license": "MIT", "engines": { @@ -487,9 +487,9 @@ } }, "node_modules/@commitlint/top-level": { - "version": "21.0.0", - "resolved": "https://registry.npmjs.org/@commitlint/top-level/-/top-level-21.0.0.tgz", - "integrity": "sha512-8jPqyWZueuN4hU6/ArKVsZ6i8xWtjIrbzHEOaLaTGUfjhhbZNBfXef/DGjzxy55hAv3yFNxHLINfI1bCJ0/MzA==", + "version": "21.0.1", + "resolved": "https://registry.npmjs.org/@commitlint/top-level/-/top-level-21.0.1.tgz", + "integrity": "sha512-4esUYqzY7K0FCgcJ/1xWEZekV7Ch4yZT1+xjEb7KzqbJ05XEkxHVsTfC8ADKNNtlCE2pj98KEbPGZWw9WwEnVw==", "dev": true, "license": "MIT", "dependencies": { @@ -500,9 +500,9 @@ } }, "node_modules/@commitlint/types": { - "version": "21.0.0", - "resolved": "https://registry.npmjs.org/@commitlint/types/-/types-21.0.0.tgz", - "integrity": "sha512-6nEz+M7I90iix4sviA8NLwskOuyt0M98KUU2aYgiKbn46jMSxUm1l2ACtzRd9ec+y38aKyJhW4Fp6NW0z35kJQ==", + "version": "21.0.1", + "resolved": "https://registry.npmjs.org/@commitlint/types/-/types-21.0.1.tgz", + "integrity": "sha512-4u7w8jcoCUFWhjWnASYzZHAP34OqOtuFBN87nQmFvqda03YU0T6z+yB4w0gSAMpekiRqqGk5rt+qSlW+a2vSEg==", "dev": true, "license": "MIT", "dependencies": { @@ -1109,13 +1109,13 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "25.6.2", - "resolved": "https://registry.npmjs.org/@types/node/-/node-25.6.2.tgz", - "integrity": "sha512-sokuT28dxf9JT5Kady1fsXOvI4HVpjZa95NKT5y9PNTIrs2AsobR4GFAA90ZG8M+nxVRLysCXsVj6eGC7Vbrlw==", + "version": "25.7.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.7.0.tgz", + "integrity": "sha512-z+pdZyxE+RTQE9AcboAZCb4otwcrvgHD+GlBpPgn0emDVt0ohrTMhAwlr2Wd9nZ+nihhYFxO2pThz3C5qSu2Eg==", "dev": true, "license": "MIT", "dependencies": { - "undici-types": "~7.19.0" + "undici-types": "~7.21.0" } }, "node_modules/@willfarrell/sast-json-schema-github-workflows": { @@ -1706,9 +1706,9 @@ } }, "node_modules/fast-check": { - "version": "4.7.0", - "resolved": "https://registry.npmjs.org/fast-check/-/fast-check-4.7.0.tgz", - "integrity": "sha512-NsZRtqvSSoCP0HbNjUD+r1JH8zqZalyp6gLY9e7OYs7NK9b6AHOs2baBFeBG7bVNsuoukh89x2Yg3rPsul8ziQ==", + "version": "4.8.0", + "resolved": "https://registry.npmjs.org/fast-check/-/fast-check-4.8.0.tgz", + "integrity": "sha512-GOJ158CUMnN6cSahsv4+ExARvIDuzzinFjkp0E9WtiBa5zcVeLozVkWaE4IzFcc+Y48Wp1EDlUZsXRyAztQcSg==", "dev": true, "funding": [ { @@ -3140,9 +3140,9 @@ } }, "node_modules/undici-types": { - "version": "7.19.2", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.19.2.tgz", - "integrity": "sha512-qYVnV5OEm2AW8cJMCpdV20CDyaN3g0AjDlOGf1OW4iaDEx8MwdtChUp4zu4H0VP3nDRF/8RKWH+IPp9uW0YGZg==", + "version": "7.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.21.0.tgz", + "integrity": "sha512-w9IMgQrz4O0YN1LtB7K5P63vhlIOvC7opSmouCJ+ZywlPAlO9gIkJ+otk6LvGpAs2wg4econaCz3TvQ9xPoyuQ==", "dev": true, "license": "MIT" }, From e7d003b18cc9d169dc07c911b1ff6869001260bb Mon Sep 17 00:00:00 2001 From: will Farrell Date: Thu, 21 May 2026 20:00:10 -0600 Subject: [PATCH 05/13] ci: improve coverage Signed-off-by: will Farrell --- .github/workflows/ossf-scorecard.yml | 5 ++ .github/workflows/release.yml | 32 +++++++- .github/workflows/test-dast.yml | 5 ++ .github/workflows/test-dco.yml | 5 ++ .github/workflows/test-lint.yml | 5 ++ .github/workflows/test-perf.yml | 5 ++ .github/workflows/test-sast.yml | 108 ++++++++++++++++++++++++++- .github/workflows/test-unit.yml | 5 ++ package-lock.json | 26 +++---- package.json | 15 +++- 10 files changed, 191 insertions(+), 20 deletions(-) diff --git a/.github/workflows/ossf-scorecard.yml b/.github/workflows/ossf-scorecard.yml index 89d0bd3..f6b532f 100644 --- a/.github/workflows/ossf-scorecard.yml +++ b/.github/workflows/ossf-scorecard.yml @@ -17,6 +17,11 @@ jobs: security-events: write id-token: write steps: + - name: Harden runner + uses: step-security/harden-runner@a5ad31d6a139d249332a2605b85202e8c0b78450 # v2.19.1 + with: + egress-policy: audit + disable-telemetry: true - name: 'Checkout code' uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 05f6243..58f86cc 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -23,6 +23,11 @@ jobs: id-token: write attestations: write steps: + - name: Harden runner + uses: step-security/harden-runner@a5ad31d6a139d249332a2605b85202e8c0b78450 # v2.19.1 + with: + egress-policy: audit + disable-telemetry: true - name: Checkout repository uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: @@ -37,10 +42,13 @@ jobs: run: | tag=$(npm pkg get version | xargs) echo "tag=${tag}" >> "$GITHUB_ENV" - echo "prerelease=$([ ${tag##*-*} ] && echo false || echo true)" >> "$GITHUB_ENV" + echo "prerelease=$([ "${tag##*-*}" ] && echo false || echo true)" >> "$GITHUB_ENV" - name: Install dependencies run: | npm ci --ignore-scripts + - name: Verify dependency signatures + run: | + npm audit signatures - name: Build run: | npm run build --if-present @@ -75,8 +83,13 @@ jobs: permissions: contents: write steps: + - name: Harden runner + uses: step-security/harden-runner@a5ad31d6a139d249332a2605b85202e8c0b78450 # v2.19.1 + with: + egress-policy: audit + disable-telemetry: true - name: Setup Node.js ${{ env.NODE_VERSION }} - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 # zizmor: ignore[cache-poisoning] no cache configured (no `cache:` input); reconsider if caching is added with: node-version: ${{ env.NODE_VERSION }} registry-url: https://registry.npmjs.org @@ -85,7 +98,7 @@ jobs: with: name: ${{ needs.build.outputs.tag }} - name: Release - uses: softprops/action-gh-release@b4309332981a82ec1c5618f44dd2e27cc8bfbfda # v3.0.0 + uses: softprops/action-gh-release@b4309332981a82ec1c5618f44dd2e27cc8bfbfda # v3.0.0 # zizmor: ignore[superfluous-actions] prefer maintained action over inline `gh release` script with: draft: true prerelease: ${{ needs.build.outputs.prerelease }} @@ -103,8 +116,14 @@ jobs: runs-on: ubuntu-latest permissions: contents: read - id-token: write + id-token: write # npm publish + attestations: read # gh attestation verify steps: + - name: Harden runner + uses: step-security/harden-runner@a5ad31d6a139d249332a2605b85202e8c0b78450 # v2.19.1 + with: + egress-policy: audit + disable-telemetry: true - name: Setup Node.js ${{ env.NODE_VERSION }} uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 with: @@ -114,6 +133,11 @@ jobs: uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # 8.0.1 with: name: ${{ needs.release.outputs.tag }} + - name: Verify build provenance attestation + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + find . -name '*.tgz' -print -exec gh attestation verify {} --repo ${{ github.repository }} \; - name: npm publish (next) if: ${{ needs.release.outputs.prerelease == 'true' }} run: | diff --git a/.github/workflows/test-dast.yml b/.github/workflows/test-dast.yml index ebd1072..3023f06 100644 --- a/.github/workflows/test-dast.yml +++ b/.github/workflows/test-dast.yml @@ -15,6 +15,11 @@ jobs: runs-on: ubuntu-latest steps: + - name: Harden runner + uses: step-security/harden-runner@a5ad31d6a139d249332a2605b85202e8c0b78450 # v2.19.1 + with: + egress-policy: audit + disable-telemetry: true - name: Checkout repository uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: diff --git a/.github/workflows/test-dco.yml b/.github/workflows/test-dco.yml index e9c11ef..6d84066 100644 --- a/.github/workflows/test-dco.yml +++ b/.github/workflows/test-dco.yml @@ -11,5 +11,10 @@ jobs: name: Tests (dco) runs-on: ubuntu-latest steps: + - name: Harden runner + uses: step-security/harden-runner@a5ad31d6a139d249332a2605b85202e8c0b78450 # v2.19.1 + with: + egress-policy: audit + disable-telemetry: true - name: Check for Developer Certificate of Origin (DCO) compliance uses: KineticCafe/actions-dco@6e1652ef3027ce128e65e6edd215ae053350bd16 # v2.1.1 diff --git a/.github/workflows/test-lint.yml b/.github/workflows/test-lint.yml index c5c70cc..790583b 100644 --- a/.github/workflows/test-lint.yml +++ b/.github/workflows/test-lint.yml @@ -15,6 +15,11 @@ jobs: runs-on: ubuntu-latest steps: + - name: Harden runner + uses: step-security/harden-runner@a5ad31d6a139d249332a2605b85202e8c0b78450 # v2.19.1 + with: + egress-policy: audit + disable-telemetry: true - name: Checkout repository uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: diff --git a/.github/workflows/test-perf.yml b/.github/workflows/test-perf.yml index 2b2b9fd..d069b8d 100644 --- a/.github/workflows/test-perf.yml +++ b/.github/workflows/test-perf.yml @@ -15,6 +15,11 @@ jobs: runs-on: ubuntu-latest steps: + - name: Harden runner + uses: step-security/harden-runner@a5ad31d6a139d249332a2605b85202e8c0b78450 # v2.19.1 + with: + egress-policy: audit + disable-telemetry: true - name: Checkout repository uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: diff --git a/.github/workflows/test-sast.yml b/.github/workflows/test-sast.yml index 7590c93..9a3c015 100644 --- a/.github/workflows/test-sast.yml +++ b/.github/workflows/test-sast.yml @@ -18,6 +18,11 @@ jobs: runs-on: ubuntu-latest if: (github.actor != 'dependabot[bot]') steps: + - name: Harden runner + uses: step-security/harden-runner@a5ad31d6a139d249332a2605b85202e8c0b78450 # v2.19.1 + with: + egress-policy: audit + disable-telemetry: true - name: Checkout repository uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: @@ -38,6 +43,11 @@ jobs: runs-on: ubuntu-latest if: (github.actor != 'dependabot[bot]') steps: + - name: Harden runner + uses: step-security/harden-runner@a5ad31d6a139d249332a2605b85202e8c0b78450 # v2.19.1 + with: + egress-policy: audit + disable-telemetry: true - name: Checkout repository uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: @@ -66,6 +76,11 @@ jobs: runs-on: ubuntu-latest if: (github.actor != 'dependabot[bot]') steps: + - name: Harden runner + uses: step-security/harden-runner@a5ad31d6a139d249332a2605b85202e8c0b78450 # v2.19.1 + with: + egress-policy: audit + disable-telemetry: true - name: Checkout repository uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: @@ -93,8 +108,13 @@ jobs: strategy: fail-fast: false matrix: - language: [javascript] + language: [javascript, actions] steps: + - name: Harden runner + uses: step-security/harden-runner@a5ad31d6a139d249332a2605b85202e8c0b78450 # v2.19.1 + with: + egress-policy: audit + disable-telemetry: true - name: Checkout uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: @@ -116,6 +136,11 @@ jobs: runs-on: ubuntu-latest if: (github.actor != 'dependabot[bot]') steps: + - name: Harden runner + uses: step-security/harden-runner@a5ad31d6a139d249332a2605b85202e8c0b78450 # v2.19.1 + with: + egress-policy: audit + disable-telemetry: true - name: Checkout repository uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: @@ -140,9 +165,90 @@ jobs: image: semgrep/semgrep:1.111.0@sha256:3e6e5065d9e68abffddffdb536a8db2d79a8fa92dc424daa48d3a4b7d9bc65d0 if: (github.actor != 'dependabot[bot]') steps: + # harden-runner skipped: this job runs in a container; harden-runner patches the host runner. - name: Checkout repository uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: persist-credentials: false - name: semgrep run: semgrep ci + + # https://github.com/raven-actions/actionlint + actionlint: + name: "actionlint: GitHub Actions lint" + runs-on: ubuntu-latest + steps: + - name: Harden runner + uses: step-security/harden-runner@a5ad31d6a139d249332a2605b85202e8c0b78450 # v2.19.1 + with: + egress-policy: audit + disable-telemetry: true + - name: Checkout repository + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + - name: actionlint + uses: raven-actions/actionlint@205b530c5d9fa8f44ae9ed59f341a0db994aa6f8 # v2.1.2 + + # https://github.com/zizmorcore/zizmor-action + zizmor: + name: "zizmor: GitHub Actions SAST" + runs-on: ubuntu-latest + permissions: + contents: read + actions: read + security-events: write + steps: + - name: Harden runner + uses: step-security/harden-runner@a5ad31d6a139d249332a2605b85202e8c0b78450 # v2.19.1 + with: + egress-policy: audit + disable-telemetry: true + - name: Checkout repository + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + - name: zizmor + uses: zizmorcore/zizmor-action@5f14fd08f7cf1cb1609c1e344975f152c7ee938d # v0.5.6 + + # https://github.com/trufflesecurity/trufflehog + trufflehog: + name: 'TruffleHog: secrets' + runs-on: ubuntu-latest + if: (github.actor != 'dependabot[bot]') + steps: + - name: Harden runner + uses: step-security/harden-runner@a5ad31d6a139d249332a2605b85202e8c0b78450 # v2.19.1 + with: + egress-policy: audit + disable-telemetry: true + - name: Checkout repository + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + fetch-depth: 0 + - name: TruffleHog + uses: trufflesecurity/trufflehog@17456f8c7d042d8c82c9a8ca9e937231f9f42e26 # v3.95.2 + with: + extra_args: --only-verified --results=verified,unknown + + # https://github.com/gitleaks/gitleaks-action + gitleaks: + name: 'gitleaks: secrets' + runs-on: ubuntu-latest + if: (github.actor != 'dependabot[bot]') + steps: + - name: Harden runner + uses: step-security/harden-runner@a5ad31d6a139d249332a2605b85202e8c0b78450 # v2.19.1 + with: + egress-policy: audit + disable-telemetry: true + - name: Checkout repository + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + fetch-depth: 0 + - name: gitleaks + uses: gitleaks/gitleaks-action@ff98106e4c7b2bc287b24eaf42907196329070c7 # v2.3.9 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/test-unit.yml b/.github/workflows/test-unit.yml index e1043f6..444018c 100644 --- a/.github/workflows/test-unit.yml +++ b/.github/workflows/test-unit.yml @@ -15,6 +15,11 @@ jobs: runs-on: ubuntu-latest steps: + - name: Harden runner + uses: step-security/harden-runner@a5ad31d6a139d249332a2605b85202e8c0b78450 # v2.19.1 + with: + egress-policy: audit + disable-telemetry: true - name: Checkout repository uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: diff --git a/package-lock.json b/package-lock.json index 44e451c..e0bb956 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1109,13 +1109,13 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "25.7.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-25.7.0.tgz", - "integrity": "sha512-z+pdZyxE+RTQE9AcboAZCb4otwcrvgHD+GlBpPgn0emDVt0ohrTMhAwlr2Wd9nZ+nihhYFxO2pThz3C5qSu2Eg==", + "version": "25.9.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.9.1.tgz", + "integrity": "sha512-xfrlY7UD5rMJk3ZVJP8BNzS28J36YJg+xp+LPXV1TdWxr8uMH5A860QNxYDGQe/ylDSgjxE52Q9VnO7p75tJxg==", "dev": true, "license": "MIT", "dependencies": { - "undici-types": "~7.21.0" + "undici-types": ">=7.24.0 <7.24.7" } }, "node_modules/@willfarrell/sast-json-schema-github-workflows": { @@ -3001,9 +3001,9 @@ } }, "node_modules/semver": { - "version": "7.8.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.0.tgz", - "integrity": "sha512-AcM7dV/5ul4EekoQ29Agm5vri8JNqRyj39o0qpX6vDF2GZrtutZl5RwgD1XnZjiTAfncsJhMI48QQH3sN87YNA==", + "version": "7.8.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.1.tgz", + "integrity": "sha512-rkVq3IXh+4FDGch+KwzX3aV9W3kO54GyEgpvBzSyctDA6Xtd7RJQV1xmXbeQp5v7+VzLOfVqiutSE6GICgPFvg==", "dev": true, "license": "ISC", "bin": { @@ -3085,9 +3085,9 @@ } }, "node_modules/tinybench": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-6.0.1.tgz", - "integrity": "sha512-cMdWsxmysdg8mNWf1pujiWl3TW0cU6m8QuNw55QlnP3I6N96Grb0wnu5N0syHIu3LbiVZCNqlfWzWDq84HZphA==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-6.0.2.tgz", + "integrity": "sha512-FlHoQpcFvCzeXK5kVPvV7IVgW/hs/B36QWTz876iSdeJguBDfdTSRQmYmaHX+fQNt4hp+gEFB2XXw+8hT4/y8A==", "dev": true, "license": "MIT", "engines": { @@ -3140,9 +3140,9 @@ } }, "node_modules/undici-types": { - "version": "7.21.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.21.0.tgz", - "integrity": "sha512-w9IMgQrz4O0YN1LtB7K5P63vhlIOvC7opSmouCJ+ZywlPAlO9gIkJ+otk6LvGpAs2wg4econaCz3TvQ9xPoyuQ==", + "version": "7.24.6", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.24.6.tgz", + "integrity": "sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg==", "dev": true, "license": "MIT" }, diff --git a/package.json b/package.json index 935e053..653c2cd 100644 --- a/package.json +++ b/package.json @@ -50,8 +50,19 @@ "test:perf": "node --test --test-concurrency=1 ./tests/*.perf.js", "test:dast": "npm run test:dast:fuzz", "test:dast:fuzz": "node --test ./tests/*.fuzz.js", - "test:sast": "npm run test:sast:license", - "test:sast:license": "license-check-and-add check -f .license.config.json" + "rm": "npm run rm:macos && npm run rm:node_modules && npm run rm:lock", + "rm:macos": "find . -name '.DS_Store' -type f -delete", + "rm:lock": "find . -name 'package-lock.json' -type f -delete", + "rm:node_modules": "find . -name 'node_modules' -type d -prune -exec rm -rf '{}' +", + "test:sast": "npm run test:sast:license && npm run test:sast:lockfile && npm run test:sast:semgrep && npm run test:sast:trufflehog && npm run test:sast:gitleaks && npm run test:sast:actionlint && npm run test:sast:zizmor && npm run test:sast:trivy", + "test:sast:actionlint": "actionlint", + "test:sast:gitleaks": "gitleaks dir --no-banner .", + "test:sast:license": "license-check-and-add check -f .license.config.json", + "test:sast:lockfile": "lockfile-lint --path package-lock.json --type npm --allowed-hosts npm --validate-https", + "test:sast:semgrep": "semgrep scan --config auto", + "test:sast:trivy": "trivy fs --scanners vuln,license --include-dev-deps --ignored-licenses 0BSD,Apache-2.0,BSD-1-Clause,BSD-2-Clause,BSD-3-Clause,CC0-1.0,CC-BY-4.0,ISC,MIT,Python-2.0 --exit-code 1 --disable-telemetry .", + "test:sast:trufflehog": "trufflehog filesystem --only-verified --log-level=-1 ./", + "test:sast:zizmor": "zizmor .github/workflows/" }, "keywords": [ "JSON", From 25475c10b39f9a691e02e786c9fd8c6e7b85c0c0 Mon Sep 17 00:00:00 2001 From: will Farrell Date: Sun, 31 May 2026 11:13:39 -0600 Subject: [PATCH 06/13] test: add in mutation testing Signed-off-by: will Farrell --- .github/dependabot.yml | 1 + .github/package.json | 2 +- .github/workflows/release.yml | 12 +- .github/workflows/test-mutation.yml | 38 + .license.config.json | 1 + README.md | 23 +- cli.js | 239 ++- package-lock.json | 3066 ++++++++++++++++++++++----- package.json | 12 +- src/.npmignore | 6 + stryker.config.json | 18 + tests/cli.analyze.test.js | 290 ++- tests/cli.crawl.test.js | 90 + tests/cli.ip.test.js | 8 + tests/cli.test.js | 166 ++ 15 files changed, 3443 insertions(+), 529 deletions(-) create mode 100644 .github/workflows/test-mutation.yml create mode 100644 src/.npmignore create mode 100644 stryker.config.json diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 34bfca4..be3a3f2 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -10,6 +10,7 @@ updates: patterns: - "*" - package-ecosystem: npm + target-branch: develop directory: "/" schedule: interval: "weekly" diff --git a/.github/package.json b/.github/package.json index fc3ef4f..d7f8051 100644 --- a/.github/package.json +++ b/.github/package.json @@ -3,7 +3,7 @@ "version": "0.0.1", "private": true, "engines": { - "node": ">=22.0" + "node": ">=24" }, "devDependencies": { "lockfile-lint": "5.0.0" diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 58f86cc..645f88e 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -41,8 +41,9 @@ jobs: - name: Set env run: | tag=$(npm pkg get version | xargs) - echo "tag=${tag}" >> "$GITHUB_ENV" - echo "prerelease=$([ "${tag##*-*}" ] && echo false || echo true)" >> "$GITHUB_ENV" + printf 'tag=%s\n' "$tag" >> "$GITHUB_ENV" + if [ "${tag##*-*}" ]; then prerelease=false; else prerelease=true; fi + printf 'prerelease=%s\n' "$prerelease" >> "$GITHUB_ENV" - name: Install dependencies run: | npm ci --ignore-scripts @@ -64,14 +65,15 @@ jobs: uses: actions/attest-build-provenance@a2bbfa25375fe432b6a289bc6b6cd05ecd0c4c32 # v4.1.0 with: subject-path: | - **/*.tgz + *.tgz + # upload-artifact is independently versioned from download-artifact; v7.0.1 is the latest major (no v8.x exists) - name: Upload artifact uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: ${{ env.tag }} path: | - **/package.json - **/*.tgz + package.json + *.tgz outputs: tag: ${{ env.tag }} prerelease: ${{ env.prerelease }} diff --git a/.github/workflows/test-mutation.yml b/.github/workflows/test-mutation.yml new file mode 100644 index 0000000..5038cec --- /dev/null +++ b/.github/workflows/test-mutation.yml @@ -0,0 +1,38 @@ +name: Tests (mutation) + +on: + pull_request: + +env: + NODE_VERSION: 24.x + +permissions: + contents: read + +jobs: + mutation: + name: Tests (mutation) + runs-on: ubuntu-latest + timeout-minutes: 30 + + steps: + - name: Harden runner + uses: step-security/harden-runner@a5ad31d6a139d249332a2605b85202e8c0b78450 # v2.19.1 + with: + egress-policy: audit + disable-telemetry: true + - name: Checkout repository + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + - name: Setup Node.js ${{ env.NODE_VERSION }} + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 + with: + node-version: ${{ env.NODE_VERSION }} + registry-url: https://registry.npmjs.org + - name: Install dependencies + run: | + npm ci --ignore-scripts + - name: Mutation tests + run: | + npm run test:mutation diff --git a/.license.config.json b/.license.config.json index 0437987..db08634 100644 --- a/.license.config.json +++ b/.license.config.json @@ -15,6 +15,7 @@ "LICENSE", ".license.template", "**/.gitignore", + "**/.npmignore", "**/*.fuzz.js", "**/*.perf.js", "**/*.test.js", diff --git a/README.md b/README.md index 01b3096..6532e21 100644 --- a/README.md +++ b/README.md @@ -66,7 +66,13 @@ if (!isSchemaSecure(schema)) { } ``` -Per-draft entry points are also exported: `sast-json-schema/2020-12`, `/2019-09`, `/draft-07`, `/draft-06`, `/draft-04`. Each meta-schema is identified by a `urn:willfarrell:sast-json-schema:` URN. Shared primitives (`safePattern`, `safeUrl`, etc.) are available via `sast-json-schema/$defs`. +Per-draft entry points are also exported: `sast-json-schema/2020-12`, `/2019-09`, `/draft-07`, `/draft-06`, `/draft-04`. These are JSON exports, so they require an import attribute: + +```javascript +import schema2020 from "sast-json-schema/2020-12" with { type: "json" } +``` + +Each meta-schema is identified by a `urn:willfarrell:sast-json-schema:` URN. Shared primitives (`safePattern`, `safeUrl`, etc.) are available via `sast-json-schema/$defs`. ### CLI @@ -78,10 +84,15 @@ Options: - `--override-max-depth `: Override max depth limit (default: 32) - `--override-max-items `: Override max items limit (default: 1024) - `--override-max-properties `: Override max properties limit (default: 1024) -- `--ignore `: Suppress errors by instancePath or instancePath:keyword (repeatable). Paths use [RFC 6901](https://datatracker.ietf.org/doc/html/rfc6901) JSON Pointer encoding (`~` to `~0`, `/` to `~1`) +- `--ignore `: Suppress errors by instancePath or instancePath:keyword (repeatable). Paths use [RFC 6901](https://datatracker.ietf.org/doc/html/rfc6901) JSON Pointer encoding (`~` to `~0`, `/` to `~1`). Depth-exceeded and timeout findings cannot be suppressed, since they indicate the schema was not fully analyzed - `--offline`: Skip SSRF DNS resolution for remote `$ref` URLs (useful in airgapped CI) +- `-r, --ref-schema-files `: Load a reference schema; its `$id` hostname is treated as safe and skipped during SSRF DNS checks (repeatable) - `--lang `: Downstream language whose deserialization-vector names to deny in property keys. Default is `default` (union of every named language). See [language coverage](#language-coverage) below - `--format `: Output format. `json` emits a JSON array of error objects on stdout; `sarif` emits a [SARIF 2.1.0](https://docs.oasis-open.org/sarif/sarif/v2.1.0/sarif-v2.1.0.html) log for GitHub code-scanning, SonarQube, Semgrep and other security pipelines; `human` is the default +- `--max-schema-size `: Maximum serialized schema size in bytes. Default 67108864 (64 MiB). Larger schemas are rejected as a tool error (exit 2) +- `--analysis-timeout-ms `: Wall-clock budget for the schema crawl. Default 60000. Exceeding it produces a `timeout` finding (exit 1) +- `--max-ssrf-hostnames `: Maximum distinct remote `$ref` hostnames resolved during SSRF checks. Default 256. Above this, DNS resolution is refused and reported as a finding +- `--dns-total-timeout-ms `: Total wall-clock budget for all SSRF DNS lookups. Default 30000. Hosts not checked within the budget are reported (fail-closed) - `-v, --version`: Show version - `-h, --help`: Show this help @@ -90,8 +101,12 @@ Options: | Code | Meaning | |------|---------| | `0` | No issues found | -| `1` | Schema has security issues | -| `2` | Usage/tool error (bad args, unreadable file, invalid JSON, unsupported `$schema`) | +| `1` | Schema has security findings, including depth-exceeded, analysis timeout, and SSRF hostname-cap / DNS-budget conditions (a schema too expensive or unsafe to fully analyze is itself a finding) | +| `2` | Usage/tool error: bad args, unreadable file, invalid JSON, unsupported `$schema`, an oversized schema (over `--max-schema-size`), or a non-JSON-serializable (circular) schema | + +Exit 1 means a problem was found in the schema, including the resource-limit conditions above; exit 2 means the tool could not analyze the input at all. + +The `--max-schema-size`, `--analysis-timeout-ms`, `--max-ssrf-hostnames`, and `--dns-total-timeout-ms` flags, together with the existing `--override-max-*` limits, are the tool's resource budgets. Also available via [`ajv-cmd`](https://github.com/willfarrell/ajv-cmd): diff --git a/cli.js b/cli.js index 251afb8..17cdd1b 100755 --- a/cli.js +++ b/cli.js @@ -24,6 +24,8 @@ const DEFAULT_VERSION = "2020-12"; export const DNS_TIMEOUT_MS = 5_000; export const DNS_CONCURRENCY = 10; +export const MAX_SSRF_HOSTNAMES = 256; +export const DNS_TOTAL_TIMEOUT_MS = 30_000; // Pre-compiled SAST meta-schema validators, keyed by draft version. Compiled // once at module load so every sast() / analyze() call reuses the same @@ -65,6 +67,7 @@ export const MAX_SCHEMA_SIZE = 64 * 1024 * 1024; // 64 MiB // fail-closed (reported as unsafe with reason "timedOut") to keep total // scan time bounded on adversarial input. export const REDOS_TIMEOUT_MS = 1_000; +export const ANALYSIS_TIMEOUT_MS = 60_000; // Property names that act as deserialization / type-confusion vectors in // each downstream language ecosystem. Selected at the analyze() / CLI layer @@ -361,6 +364,8 @@ const checkNumericRange = (current, path) => { }; }; +const INSTANCE_DATA_KEYS = new Set(["const", "enum", "default", "examples"]); + // RFC 6901 JSON Pointer token escaping: ~ → ~0, / → ~1. // https://datatracker.ietf.org/doc/html/rfc6901#section-3 const escapeJsonPointer = (token) => @@ -373,9 +378,17 @@ const escapeJsonPointer = (token) => // properties, a, properties, b). With MAX_DEPTH=32 this corresponds to roughly // 16 levels of real schema nesting. export const crawlSchema = (obj, maxDepth = MAX_DEPTH, options = {}) => { - const result = { depth: 0, depthExceeded: false, errors: [], refs: [] }; + const result = { + depth: 0, + depthExceeded: false, + timedOut: false, + errors: [], + refs: [], + }; if (typeof obj !== "object" || obj === null) return result; + const deadline = options.deadline; + const denylist = resolveDangerousNames(options.lang); const denySet = new Set(denylist); @@ -385,6 +398,18 @@ export const crawlSchema = (obj, maxDepth = MAX_DEPTH, options = {}) => { const stack = [[obj, "", 1]]; while (stack.length > 0) { + if (deadline != null && Date.now() > deadline) { + result.errors.push({ + instancePath: "", + schemaPath: "#/timeout", + keyword: "timeout", + params: {}, + message: "schema analysis exceeded time budget", + }); + result.timedOut = true; + return result; + } + const [current, path, currentDepth] = stack.pop(); const currentType = current.type; @@ -574,7 +599,29 @@ export const crawlSchema = (obj, maxDepth = MAX_DEPTH, options = {}) => { !Array.isArray(current.patternProperties) ) { for (const patternKey of Object.keys(current.patternProperties)) { + let patternSafe = true; try { + const patternResult = isSafePattern(patternKey, { + timeout: REDOS_TIMEOUT_MS, + }); + if (!patternResult.safe) { + patternSafe = false; + result.errors.push({ + instancePath: `${path}/patternProperties/${escapeJsonPointer(patternKey)}`, + schemaPath: "#/redos", + keyword: "patternProperties", + params: { + pattern: patternKey, + reason: patternResult.error ?? "hitMaxScore", + }, + message: `patternProperties key "${patternKey}" is vulnerable to ReDoS`, + }); + } + } catch { + } + if (!patternSafe) continue; + try { + // nosemgrep: javascript.lang.security.audit.detect-non-literal-regexp.detect-non-literal-regexp const re = new RegExp(patternKey); const matches = denylist.filter((n) => re.test(n)); if (matches.length > 0) { @@ -617,7 +664,7 @@ export const crawlSchema = (obj, maxDepth = MAX_DEPTH, options = {}) => { } for (const key in current) { - if (Object.hasOwn(current, key)) { + if (Object.hasOwn(current, key) && !INSTANCE_DATA_KEYS.has(key)) { const value = current[key]; if ( typeof value === "object" && @@ -642,10 +689,10 @@ export const crawlSchema = (obj, maxDepth = MAX_DEPTH, options = {}) => { // RFC 1918 + loopback + link-local + CGN + TEST-NETs + multicast + reserved. // Used to block $ref URLs whose hostname resolves to an internal/private IP. -// Fail-closed: malformed IPv6 (e.g. invalid hex groups, wrong group count) -// returns false, but the upstream DNS lookup will already have rejected such -// addresses; we only see well-formed IPs here. Tests in cli.ip.test.js pin -// the boundary cases. +// Fail-closed: malformed IPv6 (e.g. invalid hex groups, wrong group count) is +// treated as private (returns true) so it is blocked rather than allowed +// through as a forged public address. Tests in cli.ip.test.js pin the +// boundary cases. export const isPrivateIP = (ip) => { const parts = ip.split(".").map(Number); if ( @@ -709,6 +756,9 @@ export const isPrivateIP = (ip) => { if (normalized.startsWith("0:0:0:0:0:ffff:")) { const hi = Number.parseInt(groups[6], 16); const lo = Number.parseInt(groups[7], 16); + // Fail-closed: invalid hex parses as NaN, and NaN bit-math would + // forge 0.0.0.0-style public-looking IPv4. Block instead. + if (Number.isNaN(hi) || Number.isNaN(lo)) return true; const mappedIP = `${hi >> 8}.${hi & 0xff}.${lo >> 8}.${lo & 0xff}`; return isPrivateIP(mappedIP); } @@ -756,9 +806,44 @@ export const resolveSSRFRefs = async (refs, options = {}) => { hostnameMap.get(entry.hostname).push(entry); } + const maxHostnames = options.maxHostnames ?? MAX_SSRF_HOSTNAMES; + if (hostnameMap.size > maxHostnames) { + return [ + { + instancePath: "", + schemaPath: "#/ssrf", + keyword: "ssrf", + params: { hostnames: hostnameMap.size, limit: maxHostnames }, + message: `too many distinct remote $ref hostnames (${hostnameMap.size}); refusing SSRF DNS resolution above ${maxHostnames}`, + }, + ]; + } + + const totalMs = + options.dnsTotalTimeoutMs != null + ? Number(options.dnsTotalTimeoutMs) + : DNS_TOTAL_TIMEOUT_MS; + const overallDeadline = totalMs <= 0 ? 0 : Date.now() + totalMs; + const results = []; const batches = [...hostnameMap.entries()]; for (let i = 0; i < batches.length; i += concurrency) { + if (Date.now() > overallDeadline) { + for (const [hostname, entries] of batches.slice(i)) { + for (const { ref, path } of entries) { + results.push([ + { + instancePath: path, + schemaPath: "#/ssrf", + keyword: "ssrf", + params: { ref, hostname }, + message: `$ref hostname "${hostname}" not checked: SSRF DNS budget exceeded`, + }, + ]); + } + } + break; + } const batch = batches.slice(i, i + concurrency); const batchResults = await Promise.all( batch.map(([hostname, entries]) => @@ -770,7 +855,7 @@ export const resolveSSRFRefs = async (refs, options = {}) => { return results.flat(); }; -const resolveInstancePath = (obj, pointer) => { +export const resolveInstancePath = (obj, pointer) => { if (typeof obj !== "object" || obj === null) return undefined; if (!pointer) return obj; const parts = pointer @@ -781,6 +866,12 @@ const resolveInstancePath = (obj, pointer) => { for (const part of parts) { if (typeof current !== "object" || current === null) return undefined; if (!Object.hasOwn(current, part)) return undefined; + // Read-only walk: this never assigns INTO current, and the Object.hasOwn + // guard above keeps it on own properties, so inherited prototype keys are + // never traversed. Prototype pollution requires a write; there is none. + // Resolution of own keys named constructor/__proto__ is covered by the + // "own-property read" tests in tests/cli.analyze.test.js. + // nosemgrep: javascript.lang.security.audit.prototype-pollution.prototype-pollution-loop.prototype-pollution-loop current = current[part]; } return current; @@ -809,6 +900,61 @@ export const analyze = async (schema, options = {}) => { ); } } + if (options.maxSchemaSize != null) { + const n = Number(options.maxSchemaSize); + if (!Number.isFinite(n) || n < 0 || !Number.isInteger(n)) { + throw new TypeError("maxSchemaSize must be a non-negative integer"); + } + } + if (options.analysisTimeoutMs != null) { + const n = Number(options.analysisTimeoutMs); + if (!Number.isFinite(n) || n < 0 || !Number.isInteger(n)) { + throw new TypeError("analysisTimeoutMs must be a non-negative integer"); + } + } + if (options.maxHostnames != null) { + const n = Number(options.maxHostnames); + if (!Number.isFinite(n) || n < 0 || !Number.isInteger(n)) { + throw new TypeError("maxHostnames must be a non-negative integer"); + } + } + if (options.dnsTotalTimeoutMs != null) { + const n = Number(options.dnsTotalTimeoutMs); + if (!Number.isFinite(n) || n < 0 || !Number.isInteger(n)) { + throw new TypeError("dnsTotalTimeoutMs must be a non-negative integer"); + } + } + + const applyIgnore = (errs) => { + if (Array.isArray(options.ignore) && options.ignore.length && errs.length) { + const ignore = new Set(options.ignore); + return errs.filter( + (err) => + !ignore.has(err.instancePath) && + !ignore.has(`${err.instancePath}:${err.keyword}`), + ); + } + return errs; + }; + + const sizeLimit = + options.maxSchemaSize != null + ? Number(options.maxSchemaSize) + : MAX_SCHEMA_SIZE; + let serialized; + try { + serialized = JSON.stringify(schema); + } catch (err) { + throw new TypeError( + `schema must be JSON-serializable (circular reference?): ${err.message}`, + ); + } + if ( + typeof serialized === "string" && + Buffer.byteLength(serialized) > sizeLimit + ) { + throw new RangeError(`schema exceeds ${sizeLimit} byte size limit`); + } const maxDepth = options.overrideMaxDepth != null @@ -817,8 +963,20 @@ export const analyze = async (schema, options = {}) => { resolveDangerousNames(options.lang); // throws on unknown lang - const crawl = crawlSchema(schema, maxDepth, { lang: options.lang }); + let deadline; + if (options.analysisTimeoutMs != null) { + const ms = Number(options.analysisTimeoutMs); + deadline = ms <= 0 ? 0 : Date.now() + ms; + } else { + deadline = Date.now() + ANALYSIS_TIMEOUT_MS; + } + + const crawl = crawlSchema(schema, maxDepth, { lang: options.lang, deadline }); + // Depth and timeout signal INCOMPLETE analysis: the crawl bailed early and + // AJV validation plus SSRF checks were skipped. They are deliberately NOT + // passed through applyIgnore, because suppressing them would falsely report + // a partially-analyzed schema as clean (empty errors, exit 0). if (crawl.depthExceeded) { return [ { @@ -831,6 +989,10 @@ export const analyze = async (schema, options = {}) => { ]; } + if (crawl.timedOut) { + return crawl.errors; + } + let errors = []; const validate = sast(schema); validate(schema); @@ -842,6 +1004,8 @@ export const analyze = async (schema, options = {}) => { dnsTimeoutMs: options.dnsTimeoutMs, dnsConcurrency: options.dnsConcurrency, safeHostnames: options.safeHostnames, + maxHostnames: options.maxHostnames, + dnsTotalTimeoutMs: options.dnsTotalTimeoutMs, }); errors.push(...ssrfErrors); } @@ -861,22 +1025,16 @@ export const analyze = async (schema, options = {}) => { errors = errors.filter((err) => { if (err.schemaPath === SCHEMA_PATH_MAX_PROPERTIES) { const obj = resolveInstancePath(schema, err.instancePath); - if (typeof obj !== "object" || obj === null) return true; - return Object.keys(obj).length > limit; + return ( + typeof obj !== "object" || + obj === null || + Object.keys(obj).length > limit + ); } return true; }); } - if (Array.isArray(options.ignore) && options.ignore.length && errors.length) { - const ignore = new Set(options.ignore); - errors = errors.filter((err) => { - const pathKey = err.instancePath; - const keywordKey = `${err.instancePath}:${err.keyword}`; - return !ignore.has(pathKey) && !ignore.has(keywordKey); - }); - } - - return errors; + return applyIgnore(errors); }; // Maps the analyze() error array to SARIF 2.1.0. Designed for GitHub @@ -978,6 +1136,10 @@ if (process.argv[1] && resolve(process.argv[1]) === import.meta.filename) { "override-max-items": { type: "string" }, "override-max-depth": { type: "string" }, "override-max-properties": { type: "string" }, + "max-schema-size": { type: "string" }, + "analysis-timeout-ms": { type: "string" }, + "max-ssrf-hostnames": { type: "string" }, + "dns-total-timeout-ms": { type: "string" }, ignore: { type: "string", multiple: true }, offline: { type: "boolean", default: false }, lang: { type: "string", default: DEFAULT_LANG }, @@ -998,7 +1160,12 @@ Options: --override-max-items Override max items limit (default: 1024) --override-max-depth Override max depth limit (default: 32) --override-max-properties Override max properties limit (default: 1024) - --ignore Suppress errors by instancePath or instancePath:keyword (repeatable) + --max-schema-size Max serialized schema size in bytes (default: 67108864 = 64 MiB) + --analysis-timeout-ms Wall-clock budget for the schema crawl (default: 60000) + --max-ssrf-hostnames Max distinct remote $ref hostnames resolved for SSRF (default: 256) + --dns-total-timeout-ms Total budget for all SSRF DNS lookups (default: 30000) + --ignore Suppress errors by instancePath or instancePath:keyword (repeatable). + Depth and timeout findings cannot be suppressed (they mean analysis was incomplete) --offline Skip SSRF DNS resolution for remote $ref URLs -r, --ref-schema-files Load a reference schema; its $id hostname is treated as safe and skipped during SSRF DNS checks (repeatable) @@ -1013,8 +1180,8 @@ Options: Exit codes: 0 no issues found - 1 schema has issues - 2 usage / tool error`); + 1 schema has issues, including depth-exceeded, analysis timeout, and SSRF hostname-cap / DNS-budget findings + 2 usage / tool error: bad args, unreadable file, invalid JSON, unsupported $schema, oversized schema, or non-JSON-serializable (circular) schema`); process.exit(0); } @@ -1047,8 +1214,22 @@ Exit codes: } catch (err) { die(`cannot read file "${input}": ${err.message}`); } - if (fileStat.size > MAX_SCHEMA_SIZE) { - die(`schema file exceeds ${MAX_SCHEMA_SIZE} byte limit: "${input}"`); + // Only enforce --max-schema-size at the file gate when it parses to a valid + // non-negative integer. For invalid values (e.g. 3.5 or a negative), fall + // back to the default here and let analyze() raise the proper validation + // error instead of a misleading "file exceeds N byte" message. + const parsedMaxSchemaSize = + values["max-schema-size"] != null + ? Number(values["max-schema-size"]) + : null; + const fileSizeLimit = + parsedMaxSchemaSize != null && + Number.isInteger(parsedMaxSchemaSize) && + parsedMaxSchemaSize >= 0 + ? parsedMaxSchemaSize + : MAX_SCHEMA_SIZE; + if (fileStat.size > fileSizeLimit) { + die(`schema file exceeds ${fileSizeLimit} byte size limit: "${input}"`); } const schema = await readJsonFile(filePath, `file "${input}"`); @@ -1075,6 +1256,14 @@ Exit codes: options.overrideMaxDepth = values["override-max-depth"]; if (values["override-max-properties"] != null) options.overrideMaxProperties = values["override-max-properties"]; + if (values["max-schema-size"] != null) + options.maxSchemaSize = values["max-schema-size"]; + if (values["analysis-timeout-ms"] != null) + options.analysisTimeoutMs = values["analysis-timeout-ms"]; + if (values["max-ssrf-hostnames"] != null) + options.maxHostnames = values["max-ssrf-hostnames"]; + if (values["dns-total-timeout-ms"] != null) + options.dnsTotalTimeoutMs = values["dns-total-timeout-ms"]; if (values.ignore) options.ignore = values.ignore; options.safeHostnames = safeHostnames; diff --git a/package-lock.json b/package-lock.json index e0bb956..98fdad4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -22,6 +22,7 @@ "@biomejs/biome": "^2.0.0", "@commitlint/cli": "^21.0.0", "@commitlint/config-conventional": "^21.0.0", + "@stryker-mutator/core": "^9.0.0", "ajv-cmd": "^0.13.0", "fast-check": "^4.0.0", "husky": "^9.0.0", @@ -44,7 +45,7 @@ "lockfile-lint": "5.0.0" }, "engines": { - "node": ">=22.0" + "node": ">=24" } }, "node_modules/@apidevtools/json-schema-ref-parser": { @@ -64,13 +65,13 @@ } }, "node_modules/@babel/code-frame": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", - "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz", + "integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-validator-identifier": "^7.28.5", + "@babel/helper-validator-identifier": "^7.29.7", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" }, @@ -78,582 +79,1088 @@ "node": ">=6.9.0" } }, - "node_modules/@babel/helper-validator-identifier": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", - "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "node_modules/@babel/compat-data": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.7.tgz", + "integrity": "sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==", "dev": true, "license": "MIT", "engines": { "node": ">=6.9.0" } }, - "node_modules/@biomejs/biome": { - "version": "2.4.15", - "resolved": "https://registry.npmjs.org/@biomejs/biome/-/biome-2.4.15.tgz", - "integrity": "sha512-j5VH3a/h/HXTKBM50MDMxRCzkeLv9S2XJcW2WgnZT1+xyisi+0bISrXR82gCX+8S9lvK0skEvHJRN+3Ktr2hlw==", + "node_modules/@babel/core": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.7.tgz", + "integrity": "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==", "dev": true, - "license": "MIT OR Apache-2.0", - "bin": { - "biome": "bin/biome" + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.7", + "@babel/helper-compilation-targets": "^7.29.7", + "@babel/helper-module-transforms": "^7.29.7", + "@babel/helpers": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/template": "^7.29.7", + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" }, "engines": { - "node": ">=14.21.3" + "node": ">=6.9.0" }, "funding": { "type": "opencollective", - "url": "https://opencollective.com/biome" - }, - "optionalDependencies": { - "@biomejs/cli-darwin-arm64": "2.4.15", - "@biomejs/cli-darwin-x64": "2.4.15", - "@biomejs/cli-linux-arm64": "2.4.15", - "@biomejs/cli-linux-arm64-musl": "2.4.15", - "@biomejs/cli-linux-x64": "2.4.15", - "@biomejs/cli-linux-x64-musl": "2.4.15", - "@biomejs/cli-win32-arm64": "2.4.15", - "@biomejs/cli-win32-x64": "2.4.15" + "url": "https://opencollective.com/babel" } }, - "node_modules/@biomejs/cli-darwin-arm64": { - "version": "2.4.15", - "resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-arm64/-/cli-darwin-arm64-2.4.15.tgz", - "integrity": "sha512-rF3PPqLq1yoST79zaQbDjVJwsuIeci/O+9bgNmC5QpgOqz6aqYuzA4abyAGx+mgyiDXn4A049xAN8gijbuR1Qg==", - "cpu": [ - "arm64" - ], + "node_modules/@babel/core/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true, - "license": "MIT OR Apache-2.0", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=14.21.3" + "license": "ISC", + "bin": { + "semver": "bin/semver.js" } }, - "node_modules/@biomejs/cli-darwin-x64": { - "version": "2.4.15", - "resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-x64/-/cli-darwin-x64-2.4.15.tgz", - "integrity": "sha512-/5KHXYMfSJs1fNXiX30xFtI8JcCFV6zaVVLxOa0M2sfqBKHkpQhRTv94yxQWxeTY2lzo2OuTlNvPC+hDQt2wcQ==", - "cpu": [ - "x64" - ], + "node_modules/@babel/generator": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.7.tgz", + "integrity": "sha512-DkXD5OJQaAQIdZ1bt3UZdEnHAn9Imd3IVBdX03UFe+ony9Ojw5pzr9YVKGDY1jt+Gcn/FnGkNf8r+Vj5NOJWtQ==", "dev": true, - "license": "MIT OR Apache-2.0", - "optional": true, - "os": [ - "darwin" - ], + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, "engines": { - "node": ">=14.21.3" + "node": ">=6.9.0" } }, - "node_modules/@biomejs/cli-linux-arm64": { - "version": "2.4.15", - "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64/-/cli-linux-arm64-2.4.15.tgz", - "integrity": "sha512-owaAMZD/T4LrD0ELNCk0Km3qrRHuM0X6EAyVE1FSqGY0rbLoiDLrO4Us2tllm6cAeB2Ioa9C2C08NZPdr8+0Ug==", - "cpu": [ - "arm64" - ], + "node_modules/@babel/helper-annotate-as-pure": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.29.7.tgz", + "integrity": "sha512-OoK6239jHPuSQOoS0kfTVKn0b/rVTk0seKq4Gd2UMLtmOVLjDC0ki3e+c90Trqv2gMfvJFqkiljrr568+qddiw==", "dev": true, - "libc": [ - "glibc" - ], - "license": "MIT OR Apache-2.0", - "optional": true, - "os": [ - "linux" - ], + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.7" + }, "engines": { - "node": ">=14.21.3" + "node": ">=6.9.0" } }, - "node_modules/@biomejs/cli-linux-arm64-musl": { - "version": "2.4.15", - "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64-musl/-/cli-linux-arm64-musl-2.4.15.tgz", - "integrity": "sha512-ZPcxznxm0pogHBLZhYntyR3sR+MrZjqJIKEr7ZqVen0Rl+P/4upVmfYXjftizi9RoqZntg33fv/1fbdhbYXpEQ==", - "cpu": [ - "arm64" - ], + "node_modules/@babel/helper-compilation-targets": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.29.7.tgz", + "integrity": "sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==", "dev": true, - "libc": [ - "musl" - ], - "license": "MIT OR Apache-2.0", - "optional": true, - "os": [ - "linux" - ], + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.29.7", + "@babel/helper-validator-option": "^7.29.7", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, "engines": { - "node": ">=14.21.3" + "node": ">=6.9.0" } }, - "node_modules/@biomejs/cli-linux-x64": { - "version": "2.4.15", - "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64/-/cli-linux-x64-2.4.15.tgz", - "integrity": "sha512-0jj7THz12GbUOLmMibktK6DZjqz2zV64KFxyBtcFTKPiiOIY0a7vns1elpO1dERvxpsZ5ik0oFfz0oGwFde1+g==", - "cpu": [ - "x64" - ], + "node_modules/@babel/helper-compilation-targets/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true, - "libc": [ - "glibc" - ], - "license": "MIT OR Apache-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=14.21.3" + "license": "ISC", + "bin": { + "semver": "bin/semver.js" } }, - "node_modules/@biomejs/cli-linux-x64-musl": { - "version": "2.4.15", - "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64-musl/-/cli-linux-x64-musl-2.4.15.tgz", - "integrity": "sha512-CNq/9W38SYSH023lfcQ4KKU8K0YX8T//FZUhcgtMMRABDojx5XsMV7jlweAvGSl389wJQB29Qo6Zb/a+jdvt+w==", - "cpu": [ - "x64" - ], + "node_modules/@babel/helper-create-class-features-plugin": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.29.7.tgz", + "integrity": "sha512-IY3ZD9Tmooqr3TUhc3DUWxiuo8xx1DWLhd5M7hQ+ZWJamqM2BbalrBJb2MisSLoYorOj75U03qULCxQTY9r3hg==", "dev": true, - "libc": [ - "musl" - ], - "license": "MIT OR Apache-2.0", - "optional": true, - "os": [ - "linux" - ], + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.29.7", + "@babel/helper-member-expression-to-functions": "^7.29.7", + "@babel/helper-optimise-call-expression": "^7.29.7", + "@babel/helper-replace-supers": "^7.29.7", + "@babel/helper-skip-transparent-expression-wrappers": "^7.29.7", + "@babel/traverse": "^7.29.7", + "semver": "^6.3.1" + }, "engines": { - "node": ">=14.21.3" + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" } }, - "node_modules/@biomejs/cli-win32-arm64": { - "version": "2.4.15", - "resolved": "https://registry.npmjs.org/@biomejs/cli-win32-arm64/-/cli-win32-arm64-2.4.15.tgz", - "integrity": "sha512-ouhkYdlhp/1GghEJPdWwD/Vi3gQ1nFxuSpMolWsbq3Lsq3QUR4jl6UdhhscdCugKU5vOEuMiJhvKj66O0OCq+w==", - "cpu": [ - "arm64" - ], + "node_modules/@babel/helper-create-class-features-plugin/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true, - "license": "MIT OR Apache-2.0", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=14.21.3" + "license": "ISC", + "bin": { + "semver": "bin/semver.js" } }, - "node_modules/@biomejs/cli-win32-x64": { - "version": "2.4.15", - "resolved": "https://registry.npmjs.org/@biomejs/cli-win32-x64/-/cli-win32-x64-2.4.15.tgz", - "integrity": "sha512-zBrGq5mx5wwpnow4+2BxUvleDM+GNd4sLbPaMapsSLQLD0NGRCquqPBTgN+7XkUteHvj7M+BstuI8tmnV7+HgQ==", - "cpu": [ - "x64" - ], + "node_modules/@babel/helper-globals": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.29.7.tgz", + "integrity": "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==", "dev": true, - "license": "MIT OR Apache-2.0", - "optional": true, - "os": [ - "win32" - ], + "license": "MIT", "engines": { - "node": ">=14.21.3" + "node": ">=6.9.0" } }, - "node_modules/@commitlint/cli": { - "version": "21.0.1", - "resolved": "https://registry.npmjs.org/@commitlint/cli/-/cli-21.0.1.tgz", - "integrity": "sha512-8vq10krmbJwBkvzXKhbs4o4JQEVscd3pqOlWuDUaDBwbeL694/P33UC29tZQFTAgPU9fVJ2+f2m3zw16yKWxHg==", + "node_modules/@babel/helper-member-expression-to-functions": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.29.7.tgz", + "integrity": "sha512-j+7JYmk1JYDtACIGj0QJqqWZjoUpMoEikQGADMaHgCMCSDqd2+P32rfcibUNrGOMWrlzK1WJBdxrB3JJQZwWtg==", "dev": true, "license": "MIT", "dependencies": { - "@commitlint/format": "^21.0.1", - "@commitlint/lint": "^21.0.1", - "@commitlint/load": "^21.0.1", - "@commitlint/read": "^21.0.1", - "@commitlint/types": "^21.0.1", - "tinyexec": "^1.0.0", - "yargs": "^18.0.0" - }, - "bin": { - "commitlint": "cli.js" + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7" }, "engines": { - "node": ">=22.12.0" + "node": ">=6.9.0" } }, - "node_modules/@commitlint/config-conventional": { - "version": "21.0.1", - "resolved": "https://registry.npmjs.org/@commitlint/config-conventional/-/config-conventional-21.0.1.tgz", - "integrity": "sha512-gRorrkfWOh/+V5X8GYWWbQvrzPczopGMS4CCNrQdHkK4xWElv82BDvIsDhJZWTlI7TazOlYea6VATufCsFs+sw==", + "node_modules/@babel/helper-module-imports": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.29.7.tgz", + "integrity": "sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==", "dev": true, "license": "MIT", "dependencies": { - "@commitlint/types": "^21.0.1", - "conventional-changelog-conventionalcommits": "^9.2.0" + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7" }, "engines": { - "node": ">=22.12.0" + "node": ">=6.9.0" } }, - "node_modules/@commitlint/config-validator": { - "version": "21.0.1", - "resolved": "https://registry.npmjs.org/@commitlint/config-validator/-/config-validator-21.0.1.tgz", - "integrity": "sha512-Zd2UFdndeMMaW2O96HK0tdfT4gOImUvidMpAd/pws2zZ4m1nrAZ/9b/v2JYuE8fs86GpXv9F7LNaIuCIWhY+pA==", + "node_modules/@babel/helper-module-transforms": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.29.7.tgz", + "integrity": "sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==", "dev": true, "license": "MIT", "dependencies": { - "@commitlint/types": "^21.0.1", - "ajv": "^8.11.0" + "@babel/helper-module-imports": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7", + "@babel/traverse": "^7.29.7" }, "engines": { - "node": ">=22.12.0" + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" } }, - "node_modules/@commitlint/ensure": { - "version": "21.0.1", - "resolved": "https://registry.npmjs.org/@commitlint/ensure/-/ensure-21.0.1.tgz", - "integrity": "sha512-jJ1037967wU7YN/xkv+iRlOBlmaOXPhPO5KQSqya6GyXzBlwuLzELBFao16DVg9dZyqmNrhewzwZ3SAibetHBQ==", + "node_modules/@babel/helper-optimise-call-expression": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.29.7.tgz", + "integrity": "sha512-+kmGVjcT9RGYzoDwdwEqEvGgKe3BYq+O1iGzjFubaNgZHwYHP6lsF2Yghf4kEuv9BV7tYDZ913aBW9am6YKong==", "dev": true, "license": "MIT", "dependencies": { - "@commitlint/types": "^21.0.1", - "es-toolkit": "^1.46.0" + "@babel/types": "^7.29.7" }, "engines": { - "node": ">=22.12.0" + "node": ">=6.9.0" } }, - "node_modules/@commitlint/execute-rule": { - "version": "21.0.1", - "resolved": "https://registry.npmjs.org/@commitlint/execute-rule/-/execute-rule-21.0.1.tgz", - "integrity": "sha512-RifH+FmImozKBE6mozhF4K3r2RRKP7SMi/Q/zLCmExtp5e05lhHOUYqGBlFBAGNHaZxU/WYw1XuugYK9jQzqnA==", + "node_modules/@babel/helper-plugin-utils": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.29.7.tgz", + "integrity": "sha512-G7sHYigPY17oO5SYWnfD/0MTBwVR781S/JI643e/JhUYgVgWE/61SoW3NH9KWUKyKq5LVh3npif99Wkt6j86Jw==", "dev": true, "license": "MIT", "engines": { - "node": ">=22.12.0" + "node": ">=6.9.0" } }, - "node_modules/@commitlint/format": { - "version": "21.0.1", - "resolved": "https://registry.npmjs.org/@commitlint/format/-/format-21.0.1.tgz", - "integrity": "sha512-ksmG2+cHGtuDPQQbhBbC4unwm444+6TiPw0d1bKf67hntgZqZ8E0g1MuYKUuyT5IH4IMmXZhKq22/Z3jBvtQIw==", + "node_modules/@babel/helper-replace-supers": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.29.7.tgz", + "integrity": "sha512-atfGXWSeCiF4DnKZIfmJfQRkSw9b9gNNXR1kqKjbhG4pGYCOnkp8OcTB8E3NXjBu8NpheSnOeNKz8KT7UNFTmQ==", "dev": true, "license": "MIT", "dependencies": { - "@commitlint/types": "^21.0.1", - "picocolors": "^1.1.1" + "@babel/helper-member-expression-to-functions": "^7.29.7", + "@babel/helper-optimise-call-expression": "^7.29.7", + "@babel/traverse": "^7.29.7" }, "engines": { - "node": ">=22.12.0" + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" } }, - "node_modules/@commitlint/is-ignored": { - "version": "21.0.1", - "resolved": "https://registry.npmjs.org/@commitlint/is-ignored/-/is-ignored-21.0.1.tgz", - "integrity": "sha512-iNDP8SFdw8JEkM0CHZ2XFnhTN4Zg5jKUY2d8kBOSFrI2aA+3YJI7fcqVpfgbpJ9xtxFVYpi+DBATU5AvhoTq8g==", + "node_modules/@babel/helper-skip-transparent-expression-wrappers": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.29.7.tgz", + "integrity": "sha512-brcMGQaVzIeUb+6/bs1Av0f8YuNNjKY2JyvfRCsFuFsdKccEQ5Ges2y74D74NZ1Rz8lKJ9ksJkfqwQFJ/iNEyQ==", "dev": true, "license": "MIT", "dependencies": { - "@commitlint/types": "^21.0.1", - "semver": "^7.6.0" + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7" }, "engines": { - "node": ">=22.12.0" + "node": ">=6.9.0" } }, - "node_modules/@commitlint/lint": { - "version": "21.0.1", - "resolved": "https://registry.npmjs.org/@commitlint/lint/-/lint-21.0.1.tgz", - "integrity": "sha512-gF+iYtUw1gBG3HUH9z3VxwUjGg2R2G5j+nmvPs8aIeYkiB7TtneBu3wO85I0bUl93bYNsvsCNI9Nte2fmDUMww==", + "node_modules/@babel/helper-string-parser": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz", + "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==", "dev": true, "license": "MIT", - "dependencies": { - "@commitlint/is-ignored": "^21.0.1", - "@commitlint/parse": "^21.0.1", - "@commitlint/rules": "^21.0.1", - "@commitlint/types": "^21.0.1" - }, "engines": { - "node": ">=22.12.0" + "node": ">=6.9.0" } }, - "node_modules/@commitlint/load": { - "version": "21.0.1", - "resolved": "https://registry.npmjs.org/@commitlint/load/-/load-21.0.1.tgz", - "integrity": "sha512-Btg1q1mKmiihN4W3x0EsPDrJMOQfMa9NIqlzlJyXAfxvsOGdGXOW5p3R3RcSxDCaY7JabY9flIl+Om1af3PSrw==", + "node_modules/@babel/helper-validator-identifier": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", + "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.29.7.tgz", + "integrity": "sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.7.tgz", + "integrity": "sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==", "dev": true, "license": "MIT", "dependencies": { - "@commitlint/config-validator": "^21.0.1", - "@commitlint/execute-rule": "^21.0.1", - "@commitlint/resolve-extends": "^21.0.1", - "@commitlint/types": "^21.0.1", - "cosmiconfig": "^9.0.1", - "cosmiconfig-typescript-loader": "^6.1.0", - "es-toolkit": "^1.46.0", - "is-plain-obj": "^4.1.0", - "picocolors": "^1.1.1" + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.7" }, "engines": { - "node": ">=22.12.0" + "node": ">=6.9.0" } }, - "node_modules/@commitlint/message": { - "version": "21.0.1", - "resolved": "https://registry.npmjs.org/@commitlint/message/-/message-21.0.1.tgz", - "integrity": "sha512-R3dVQeJQ0B6yqrZEjkUHD4r7UJYLV9Lvk2xs3PTOmtWk2G3mI6Xgc+YdRxL1PwcDfBiUjv2SkIkW4AUc976w1w==", + "node_modules/@babel/parser": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.7.tgz", + "integrity": "sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==", "dev": true, "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.7" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, "engines": { - "node": ">=22.12.0" + "node": ">=6.0.0" } }, - "node_modules/@commitlint/parse": { - "version": "21.0.1", - "resolved": "https://registry.npmjs.org/@commitlint/parse/-/parse-21.0.1.tgz", - "integrity": "sha512-oh/nCSOqdoeQNA1tO8aAmxkq5EBo8/NzcFQRvv66AWc9HpED28sL2iSicCKU6hPintWuscL6BJEWi77Wq1LPMQ==", + "node_modules/@babel/plugin-proposal-decorators": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-decorators/-/plugin-proposal-decorators-7.29.7.tgz", + "integrity": "sha512-EtU0Hi3GvrTqD56xKmZvV/uCXK2ZbwVNPNLAquVItcAZpUhkXwWlo3Fmj0c2LxgSf2I8IDULeAepwNP1OefLXg==", "dev": true, "license": "MIT", "dependencies": { - "@commitlint/types": "^21.0.1", - "conventional-changelog-angular": "^8.2.0", - "conventional-commits-parser": "^6.3.0" + "@babel/helper-create-class-features-plugin": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/plugin-syntax-decorators": "^7.29.7" }, "engines": { - "node": ">=22.12.0" + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@commitlint/read": { - "version": "21.0.1", - "resolved": "https://registry.npmjs.org/@commitlint/read/-/read-21.0.1.tgz", - "integrity": "sha512-pMEu4lbpC8W0ZgKJj2U6WaobXIZWdFlULpIEewYhkPXx+WZcnoO53YrVPc7QErQuNolq2Me8dP58Wu7YAVXVOA==", + "node_modules/@babel/plugin-syntax-decorators": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-decorators/-/plugin-syntax-decorators-7.29.7.tgz", + "integrity": "sha512-9MTTLbF39X6sqM92JPEsoI7++26hjZvzkxKZy64aMhWLH2mPkJ/Q3AV4QLmls3R14FpSpkOwQQfUh962JGQxxg==", "dev": true, "license": "MIT", "dependencies": { - "@commitlint/top-level": "^21.0.1", - "@commitlint/types": "^21.0.1", - "git-raw-commits": "^5.0.0", - "tinyexec": "^1.0.0" + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { - "node": ">=22.12.0" + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@commitlint/resolve-extends": { - "version": "21.0.1", - "resolved": "https://registry.npmjs.org/@commitlint/resolve-extends/-/resolve-extends-21.0.1.tgz", - "integrity": "sha512-0DhjYWL6uYrY16Efa032fYk3woGJDU4AGWiG1XXltT9AMUNYKyb5cIZU2ivbaMZ3+kKFqUjikD2cjh66Sbh/Sg==", + "node_modules/@babel/plugin-syntax-jsx": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.29.7.tgz", + "integrity": "sha512-TSu8+mHCoEaaCDEZ0I3+6mvTBYR4PCxQwf2z9/r5Tbztv6NaLR3B9thGTTxX2WGuGHJqRiAbKPeGTJ5XWXVg6A==", "dev": true, "license": "MIT", "dependencies": { - "@commitlint/config-validator": "^21.0.1", - "@commitlint/types": "^21.0.1", - "es-toolkit": "^1.46.0", - "global-directory": "^5.0.0", - "resolve-from": "^5.0.0" + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { - "node": ">=22.12.0" + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@commitlint/rules": { - "version": "21.0.1", - "resolved": "https://registry.npmjs.org/@commitlint/rules/-/rules-21.0.1.tgz", - "integrity": "sha512-VMooYpz4nJg7xlaUso6CCOWEz8D/ChkvsvZUMARcoJ1ZpfKPyFCGrHNha2tbsETNAb6ErgiRuCr2DvghrvPDYQ==", + "node_modules/@babel/plugin-syntax-typescript": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.29.7.tgz", + "integrity": "sha512-ngr+82Sh0xMz25TPCZi+nC2iTzjfCdWS2ONXTp/PtSCHCgaCNBpdMqgvJ2ccdLlClVZ7sisIgB914j/JFe+RZA==", "dev": true, "license": "MIT", "dependencies": { - "@commitlint/ensure": "^21.0.1", - "@commitlint/message": "^21.0.1", - "@commitlint/to-lines": "^21.0.1", - "@commitlint/types": "^21.0.1" + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { - "node": ">=22.12.0" + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@commitlint/to-lines": { - "version": "21.0.1", - "resolved": "https://registry.npmjs.org/@commitlint/to-lines/-/to-lines-21.0.1.tgz", - "integrity": "sha512-bd1BFII7p1EQZre9Kaj+kKaMFP3cFCdt21K7DItVux9XP5WjLgJ0/Uy1pJJh9aPwVJ6SKg62PxqlZaHI8hQAXw==", + "node_modules/@babel/plugin-transform-destructuring": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.29.7.tgz", + "integrity": "sha512-iPX8aD6H9zV5s7ZsqTdNocPN/MGQ5sSMnElKrktxjJRMnB2jN/1p2+R7GkfD6CAYoVFqy5A4XnSIUeGgJzIWpg==", "dev": true, "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/traverse": "^7.29.7" + }, "engines": { - "node": ">=22.12.0" + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@commitlint/top-level": { - "version": "21.0.1", - "resolved": "https://registry.npmjs.org/@commitlint/top-level/-/top-level-21.0.1.tgz", - "integrity": "sha512-4esUYqzY7K0FCgcJ/1xWEZekV7Ch4yZT1+xjEb7KzqbJ05XEkxHVsTfC8ADKNNtlCE2pj98KEbPGZWw9WwEnVw==", + "node_modules/@babel/plugin-transform-explicit-resource-management": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-explicit-resource-management/-/plugin-transform-explicit-resource-management-7.29.7.tgz", + "integrity": "sha512-Rstj7coNz8sE+7Ju7ihpHLI564lsK5pUpNNlvptCIC/16E/S5hbl6n3kESPKdNRmqEWlpn5xpS5Q2dvXBsySLw==", "dev": true, "license": "MIT", "dependencies": { - "escalade": "^3.2.0" + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/plugin-transform-destructuring": "^7.29.7" }, "engines": { - "node": ">=22.12.0" + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@commitlint/types": { - "version": "21.0.1", - "resolved": "https://registry.npmjs.org/@commitlint/types/-/types-21.0.1.tgz", - "integrity": "sha512-4u7w8jcoCUFWhjWnASYzZHAP34OqOtuFBN87nQmFvqda03YU0T6z+yB4w0gSAMpekiRqqGk5rt+qSlW+a2vSEg==", + "node_modules/@babel/plugin-transform-modules-commonjs": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.29.7.tgz", + "integrity": "sha512-j0vCldybPC5b5dwCQOJ21uKtHzt7hxLygJTg9eF1ScfaikEDNfzn94XoW5Fi+seBR0nCyL23xaBFFkq7dTM8XQ==", "dev": true, "license": "MIT", "dependencies": { - "conventional-commits-parser": "^6.3.0", - "picocolors": "^1.1.1" + "@babel/helper-module-transforms": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { - "node": ">=22.12.0" + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@conventional-changelog/git-client": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/@conventional-changelog/git-client/-/git-client-2.7.0.tgz", - "integrity": "sha512-j7A8/LBEQ+3rugMzPXoKYzyUPpw/0CBQCyvtTR7Lmu4olG4yRC/Tfkq79Mr3yuPs0SUitlO2HwGP3gitMJnRFw==", + "node_modules/@babel/plugin-transform-typescript": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.29.7.tgz", + "integrity": "sha512-jK52h8LaLc7JarhQV2ofeFMts4H7vnOXnqZNA6fYglBTZewRBE51KWt3BUltW1P+KoPsYkHoJeXePuz4zo2LMw==", "dev": true, "license": "MIT", "dependencies": { - "@simple-libs/child-process-utils": "^1.0.0", - "@simple-libs/stream-utils": "^1.2.0", - "semver": "^7.5.2" + "@babel/helper-annotate-as-pure": "^7.29.7", + "@babel/helper-create-class-features-plugin": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/helper-skip-transparent-expression-wrappers": "^7.29.7", + "@babel/plugin-syntax-typescript": "^7.29.7" }, "engines": { - "node": ">=18" + "node": ">=6.9.0" }, "peerDependencies": { - "conventional-commits-filter": "^5.0.0", - "conventional-commits-parser": "^6.4.0" - }, - "peerDependenciesMeta": { - "conventional-commits-filter": { - "optional": true - }, - "conventional-commits-parser": { - "optional": true - } + "@babel/core": "^7.0.0-0" } }, - "node_modules/@esbuild/aix-ppc64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.0.tgz", - "integrity": "sha512-lhRUCeuOyJQURhTxl4WkpFTjIsbDayJHih5kZC1giwE+MhIzAb7mEsQMqMf18rHLsrb5qI1tafG20mLxEWcWlA==", - "cpu": [ - "ppc64" - ], + "node_modules/@babel/preset-typescript": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.28.5.tgz", + "integrity": "sha512-+bQy5WOI2V6LJZpPVxY+yp66XdZ2yifu0Mc1aP5CQKgjn4QM5IN2i5fAZ4xKop47pr8rpVhiAeu+nDQa12C8+g==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "aix" - ], + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-validator-option": "^7.27.1", + "@babel/plugin-syntax-jsx": "^7.27.1", + "@babel/plugin-transform-modules-commonjs": "^7.27.1", + "@babel/plugin-transform-typescript": "^7.28.5" + }, "engines": { - "node": ">=18" + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@esbuild/android-arm": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.0.tgz", - "integrity": "sha512-wqh0ByljabXLKHeWXYLqoJ5jKC4XBaw6Hk08OfMrCRd2nP2ZQ5eleDZC41XHyCNgktBGYMbqnrJKq/K/lzPMSQ==", - "cpu": [ - "arm" - ], + "node_modules/@babel/template": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.29.7.tgz", + "integrity": "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "android" - ], + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7" + }, "engines": { - "node": ">=18" + "node": ">=6.9.0" } }, - "node_modules/@esbuild/android-arm64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.0.tgz", - "integrity": "sha512-+WzIXQOSaGs33tLEgYPYe/yQHf0WTU0X42Jca3y8NWMbUVhp7rUnw+vAsRC/QiDrdD31IszMrZy+qwPOPjd+rw==", - "cpu": [ - "arm64" - ], + "node_modules/@babel/traverse": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.7.tgz", + "integrity": "sha512-EhlfNQtZ+NK22w5BM61ciuiq1m58ed33Wr1Xan//ZRTy6hgjnwyCffRYwzsGXdASJSUJ1guZILsErh1eQcl+zw==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "android" - ], + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.7", + "@babel/helper-globals": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.7", + "debug": "^4.3.1" + }, "engines": { - "node": ">=18" + "node": ">=6.9.0" } }, - "node_modules/@esbuild/android-x64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.0.tgz", - "integrity": "sha512-+VJggoaKhk2VNNqVL7f6S189UzShHC/mR9EE8rDdSkdpN0KflSwWY/gWjDrNxxisg8Fp1ZCD9jLMo4m0OUfeUA==", - "cpu": [ - "x64" - ], + "node_modules/@babel/types": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.7.tgz", + "integrity": "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "android" - ], + "dependencies": { + "@babel/helper-string-parser": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7" + }, "engines": { - "node": ">=18" + "node": ">=6.9.0" } }, - "node_modules/@esbuild/darwin-arm64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.0.tgz", - "integrity": "sha512-0T+A9WZm+bZ84nZBtk1ckYsOvyA3x7e2Acj1KdVfV4/2tdG4fzUp91YHx+GArWLtwqp77pBXVCPn2We7Letr0Q==", + "node_modules/@biomejs/biome": { + "version": "2.4.15", + "resolved": "https://registry.npmjs.org/@biomejs/biome/-/biome-2.4.15.tgz", + "integrity": "sha512-j5VH3a/h/HXTKBM50MDMxRCzkeLv9S2XJcW2WgnZT1+xyisi+0bISrXR82gCX+8S9lvK0skEvHJRN+3Ktr2hlw==", + "dev": true, + "license": "MIT OR Apache-2.0", + "bin": { + "biome": "bin/biome" + }, + "engines": { + "node": ">=14.21.3" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/biome" + }, + "optionalDependencies": { + "@biomejs/cli-darwin-arm64": "2.4.15", + "@biomejs/cli-darwin-x64": "2.4.15", + "@biomejs/cli-linux-arm64": "2.4.15", + "@biomejs/cli-linux-arm64-musl": "2.4.15", + "@biomejs/cli-linux-x64": "2.4.15", + "@biomejs/cli-linux-x64-musl": "2.4.15", + "@biomejs/cli-win32-arm64": "2.4.15", + "@biomejs/cli-win32-x64": "2.4.15" + } + }, + "node_modules/@biomejs/cli-darwin-arm64": { + "version": "2.4.15", + "resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-arm64/-/cli-darwin-arm64-2.4.15.tgz", + "integrity": "sha512-rF3PPqLq1yoST79zaQbDjVJwsuIeci/O+9bgNmC5QpgOqz6aqYuzA4abyAGx+mgyiDXn4A049xAN8gijbuR1Qg==", "cpu": [ "arm64" ], "dev": true, - "license": "MIT", + "license": "MIT OR Apache-2.0", "optional": true, "os": [ "darwin" ], "engines": { - "node": ">=18" + "node": ">=14.21.3" } }, - "node_modules/@esbuild/darwin-x64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.0.tgz", - "integrity": "sha512-fyzLm/DLDl/84OCfp2f/XQ4flmORsjU7VKt8HLjvIXChJoFFOIL6pLJPH4Yhd1n1gGFF9mPwtlN5Wf82DZs+LQ==", + "node_modules/@biomejs/cli-darwin-x64": { + "version": "2.4.15", + "resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-x64/-/cli-darwin-x64-2.4.15.tgz", + "integrity": "sha512-/5KHXYMfSJs1fNXiX30xFtI8JcCFV6zaVVLxOa0M2sfqBKHkpQhRTv94yxQWxeTY2lzo2OuTlNvPC+hDQt2wcQ==", "cpu": [ "x64" ], "dev": true, - "license": "MIT", + "license": "MIT OR Apache-2.0", "optional": true, "os": [ "darwin" ], "engines": { - "node": ">=18" + "node": ">=14.21.3" } }, - "node_modules/@esbuild/freebsd-arm64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.0.tgz", - "integrity": "sha512-l9GeW5UZBT9k9brBYI+0WDffcRxgHQD8ShN2Ur4xWq/NFzUKm3k5lsH4PdaRgb2w7mI9u61nr2gI2mLI27Nh3Q==", + "node_modules/@biomejs/cli-linux-arm64": { + "version": "2.4.15", + "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64/-/cli-linux-arm64-2.4.15.tgz", + "integrity": "sha512-owaAMZD/T4LrD0ELNCk0Km3qrRHuM0X6EAyVE1FSqGY0rbLoiDLrO4Us2tllm6cAeB2Ioa9C2C08NZPdr8+0Ug==", "cpu": [ "arm64" ], "dev": true, - "license": "MIT", + "libc": [ + "glibc" + ], + "license": "MIT OR Apache-2.0", "optional": true, "os": [ - "freebsd" + "linux" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@biomejs/cli-linux-arm64-musl": { + "version": "2.4.15", + "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64-musl/-/cli-linux-arm64-musl-2.4.15.tgz", + "integrity": "sha512-ZPcxznxm0pogHBLZhYntyR3sR+MrZjqJIKEr7ZqVen0Rl+P/4upVmfYXjftizi9RoqZntg33fv/1fbdhbYXpEQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@biomejs/cli-linux-x64": { + "version": "2.4.15", + "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64/-/cli-linux-x64-2.4.15.tgz", + "integrity": "sha512-0jj7THz12GbUOLmMibktK6DZjqz2zV64KFxyBtcFTKPiiOIY0a7vns1elpO1dERvxpsZ5ik0oFfz0oGwFde1+g==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@biomejs/cli-linux-x64-musl": { + "version": "2.4.15", + "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64-musl/-/cli-linux-x64-musl-2.4.15.tgz", + "integrity": "sha512-CNq/9W38SYSH023lfcQ4KKU8K0YX8T//FZUhcgtMMRABDojx5XsMV7jlweAvGSl389wJQB29Qo6Zb/a+jdvt+w==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@biomejs/cli-win32-arm64": { + "version": "2.4.15", + "resolved": "https://registry.npmjs.org/@biomejs/cli-win32-arm64/-/cli-win32-arm64-2.4.15.tgz", + "integrity": "sha512-ouhkYdlhp/1GghEJPdWwD/Vi3gQ1nFxuSpMolWsbq3Lsq3QUR4jl6UdhhscdCugKU5vOEuMiJhvKj66O0OCq+w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@biomejs/cli-win32-x64": { + "version": "2.4.15", + "resolved": "https://registry.npmjs.org/@biomejs/cli-win32-x64/-/cli-win32-x64-2.4.15.tgz", + "integrity": "sha512-zBrGq5mx5wwpnow4+2BxUvleDM+GNd4sLbPaMapsSLQLD0NGRCquqPBTgN+7XkUteHvj7M+BstuI8tmnV7+HgQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@commitlint/cli": { + "version": "21.0.1", + "resolved": "https://registry.npmjs.org/@commitlint/cli/-/cli-21.0.1.tgz", + "integrity": "sha512-8vq10krmbJwBkvzXKhbs4o4JQEVscd3pqOlWuDUaDBwbeL694/P33UC29tZQFTAgPU9fVJ2+f2m3zw16yKWxHg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@commitlint/format": "^21.0.1", + "@commitlint/lint": "^21.0.1", + "@commitlint/load": "^21.0.1", + "@commitlint/read": "^21.0.1", + "@commitlint/types": "^21.0.1", + "tinyexec": "^1.0.0", + "yargs": "^18.0.0" + }, + "bin": { + "commitlint": "cli.js" + }, + "engines": { + "node": ">=22.12.0" + } + }, + "node_modules/@commitlint/config-conventional": { + "version": "21.0.1", + "resolved": "https://registry.npmjs.org/@commitlint/config-conventional/-/config-conventional-21.0.1.tgz", + "integrity": "sha512-gRorrkfWOh/+V5X8GYWWbQvrzPczopGMS4CCNrQdHkK4xWElv82BDvIsDhJZWTlI7TazOlYea6VATufCsFs+sw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@commitlint/types": "^21.0.1", + "conventional-changelog-conventionalcommits": "^9.2.0" + }, + "engines": { + "node": ">=22.12.0" + } + }, + "node_modules/@commitlint/config-validator": { + "version": "21.0.1", + "resolved": "https://registry.npmjs.org/@commitlint/config-validator/-/config-validator-21.0.1.tgz", + "integrity": "sha512-Zd2UFdndeMMaW2O96HK0tdfT4gOImUvidMpAd/pws2zZ4m1nrAZ/9b/v2JYuE8fs86GpXv9F7LNaIuCIWhY+pA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@commitlint/types": "^21.0.1", + "ajv": "^8.11.0" + }, + "engines": { + "node": ">=22.12.0" + } + }, + "node_modules/@commitlint/ensure": { + "version": "21.0.1", + "resolved": "https://registry.npmjs.org/@commitlint/ensure/-/ensure-21.0.1.tgz", + "integrity": "sha512-jJ1037967wU7YN/xkv+iRlOBlmaOXPhPO5KQSqya6GyXzBlwuLzELBFao16DVg9dZyqmNrhewzwZ3SAibetHBQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@commitlint/types": "^21.0.1", + "es-toolkit": "^1.46.0" + }, + "engines": { + "node": ">=22.12.0" + } + }, + "node_modules/@commitlint/execute-rule": { + "version": "21.0.1", + "resolved": "https://registry.npmjs.org/@commitlint/execute-rule/-/execute-rule-21.0.1.tgz", + "integrity": "sha512-RifH+FmImozKBE6mozhF4K3r2RRKP7SMi/Q/zLCmExtp5e05lhHOUYqGBlFBAGNHaZxU/WYw1XuugYK9jQzqnA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=22.12.0" + } + }, + "node_modules/@commitlint/format": { + "version": "21.0.1", + "resolved": "https://registry.npmjs.org/@commitlint/format/-/format-21.0.1.tgz", + "integrity": "sha512-ksmG2+cHGtuDPQQbhBbC4unwm444+6TiPw0d1bKf67hntgZqZ8E0g1MuYKUuyT5IH4IMmXZhKq22/Z3jBvtQIw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@commitlint/types": "^21.0.1", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=22.12.0" + } + }, + "node_modules/@commitlint/is-ignored": { + "version": "21.0.1", + "resolved": "https://registry.npmjs.org/@commitlint/is-ignored/-/is-ignored-21.0.1.tgz", + "integrity": "sha512-iNDP8SFdw8JEkM0CHZ2XFnhTN4Zg5jKUY2d8kBOSFrI2aA+3YJI7fcqVpfgbpJ9xtxFVYpi+DBATU5AvhoTq8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@commitlint/types": "^21.0.1", + "semver": "^7.6.0" + }, + "engines": { + "node": ">=22.12.0" + } + }, + "node_modules/@commitlint/lint": { + "version": "21.0.1", + "resolved": "https://registry.npmjs.org/@commitlint/lint/-/lint-21.0.1.tgz", + "integrity": "sha512-gF+iYtUw1gBG3HUH9z3VxwUjGg2R2G5j+nmvPs8aIeYkiB7TtneBu3wO85I0bUl93bYNsvsCNI9Nte2fmDUMww==", + "dev": true, + "license": "MIT", + "dependencies": { + "@commitlint/is-ignored": "^21.0.1", + "@commitlint/parse": "^21.0.1", + "@commitlint/rules": "^21.0.1", + "@commitlint/types": "^21.0.1" + }, + "engines": { + "node": ">=22.12.0" + } + }, + "node_modules/@commitlint/load": { + "version": "21.0.1", + "resolved": "https://registry.npmjs.org/@commitlint/load/-/load-21.0.1.tgz", + "integrity": "sha512-Btg1q1mKmiihN4W3x0EsPDrJMOQfMa9NIqlzlJyXAfxvsOGdGXOW5p3R3RcSxDCaY7JabY9flIl+Om1af3PSrw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@commitlint/config-validator": "^21.0.1", + "@commitlint/execute-rule": "^21.0.1", + "@commitlint/resolve-extends": "^21.0.1", + "@commitlint/types": "^21.0.1", + "cosmiconfig": "^9.0.1", + "cosmiconfig-typescript-loader": "^6.1.0", + "es-toolkit": "^1.46.0", + "is-plain-obj": "^4.1.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=22.12.0" + } + }, + "node_modules/@commitlint/message": { + "version": "21.0.1", + "resolved": "https://registry.npmjs.org/@commitlint/message/-/message-21.0.1.tgz", + "integrity": "sha512-R3dVQeJQ0B6yqrZEjkUHD4r7UJYLV9Lvk2xs3PTOmtWk2G3mI6Xgc+YdRxL1PwcDfBiUjv2SkIkW4AUc976w1w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=22.12.0" + } + }, + "node_modules/@commitlint/parse": { + "version": "21.0.1", + "resolved": "https://registry.npmjs.org/@commitlint/parse/-/parse-21.0.1.tgz", + "integrity": "sha512-oh/nCSOqdoeQNA1tO8aAmxkq5EBo8/NzcFQRvv66AWc9HpED28sL2iSicCKU6hPintWuscL6BJEWi77Wq1LPMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@commitlint/types": "^21.0.1", + "conventional-changelog-angular": "^8.2.0", + "conventional-commits-parser": "^6.3.0" + }, + "engines": { + "node": ">=22.12.0" + } + }, + "node_modules/@commitlint/read": { + "version": "21.0.1", + "resolved": "https://registry.npmjs.org/@commitlint/read/-/read-21.0.1.tgz", + "integrity": "sha512-pMEu4lbpC8W0ZgKJj2U6WaobXIZWdFlULpIEewYhkPXx+WZcnoO53YrVPc7QErQuNolq2Me8dP58Wu7YAVXVOA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@commitlint/top-level": "^21.0.1", + "@commitlint/types": "^21.0.1", + "git-raw-commits": "^5.0.0", + "tinyexec": "^1.0.0" + }, + "engines": { + "node": ">=22.12.0" + } + }, + "node_modules/@commitlint/resolve-extends": { + "version": "21.0.1", + "resolved": "https://registry.npmjs.org/@commitlint/resolve-extends/-/resolve-extends-21.0.1.tgz", + "integrity": "sha512-0DhjYWL6uYrY16Efa032fYk3woGJDU4AGWiG1XXltT9AMUNYKyb5cIZU2ivbaMZ3+kKFqUjikD2cjh66Sbh/Sg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@commitlint/config-validator": "^21.0.1", + "@commitlint/types": "^21.0.1", + "es-toolkit": "^1.46.0", + "global-directory": "^5.0.0", + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=22.12.0" + } + }, + "node_modules/@commitlint/rules": { + "version": "21.0.1", + "resolved": "https://registry.npmjs.org/@commitlint/rules/-/rules-21.0.1.tgz", + "integrity": "sha512-VMooYpz4nJg7xlaUso6CCOWEz8D/ChkvsvZUMARcoJ1ZpfKPyFCGrHNha2tbsETNAb6ErgiRuCr2DvghrvPDYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@commitlint/ensure": "^21.0.1", + "@commitlint/message": "^21.0.1", + "@commitlint/to-lines": "^21.0.1", + "@commitlint/types": "^21.0.1" + }, + "engines": { + "node": ">=22.12.0" + } + }, + "node_modules/@commitlint/to-lines": { + "version": "21.0.1", + "resolved": "https://registry.npmjs.org/@commitlint/to-lines/-/to-lines-21.0.1.tgz", + "integrity": "sha512-bd1BFII7p1EQZre9Kaj+kKaMFP3cFCdt21K7DItVux9XP5WjLgJ0/Uy1pJJh9aPwVJ6SKg62PxqlZaHI8hQAXw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=22.12.0" + } + }, + "node_modules/@commitlint/top-level": { + "version": "21.0.1", + "resolved": "https://registry.npmjs.org/@commitlint/top-level/-/top-level-21.0.1.tgz", + "integrity": "sha512-4esUYqzY7K0FCgcJ/1xWEZekV7Ch4yZT1+xjEb7KzqbJ05XEkxHVsTfC8ADKNNtlCE2pj98KEbPGZWw9WwEnVw==", + "dev": true, + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0" + }, + "engines": { + "node": ">=22.12.0" + } + }, + "node_modules/@commitlint/types": { + "version": "21.0.1", + "resolved": "https://registry.npmjs.org/@commitlint/types/-/types-21.0.1.tgz", + "integrity": "sha512-4u7w8jcoCUFWhjWnASYzZHAP34OqOtuFBN87nQmFvqda03YU0T6z+yB4w0gSAMpekiRqqGk5rt+qSlW+a2vSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "conventional-commits-parser": "^6.3.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=22.12.0" + } + }, + "node_modules/@conventional-changelog/git-client": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/@conventional-changelog/git-client/-/git-client-2.7.0.tgz", + "integrity": "sha512-j7A8/LBEQ+3rugMzPXoKYzyUPpw/0CBQCyvtTR7Lmu4olG4yRC/Tfkq79Mr3yuPs0SUitlO2HwGP3gitMJnRFw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@simple-libs/child-process-utils": "^1.0.0", + "@simple-libs/stream-utils": "^1.2.0", + "semver": "^7.5.2" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "conventional-commits-filter": "^5.0.0", + "conventional-commits-parser": "^6.4.0" + }, + "peerDependenciesMeta": { + "conventional-commits-filter": { + "optional": true + }, + "conventional-commits-parser": { + "optional": true + } + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.0.tgz", + "integrity": "sha512-lhRUCeuOyJQURhTxl4WkpFTjIsbDayJHih5kZC1giwE+MhIzAb7mEsQMqMf18rHLsrb5qI1tafG20mLxEWcWlA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.0.tgz", + "integrity": "sha512-wqh0ByljabXLKHeWXYLqoJ5jKC4XBaw6Hk08OfMrCRd2nP2ZQ5eleDZC41XHyCNgktBGYMbqnrJKq/K/lzPMSQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.0.tgz", + "integrity": "sha512-+WzIXQOSaGs33tLEgYPYe/yQHf0WTU0X42Jca3y8NWMbUVhp7rUnw+vAsRC/QiDrdD31IszMrZy+qwPOPjd+rw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.0.tgz", + "integrity": "sha512-+VJggoaKhk2VNNqVL7f6S189UzShHC/mR9EE8rDdSkdpN0KflSwWY/gWjDrNxxisg8Fp1ZCD9jLMo4m0OUfeUA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.0.tgz", + "integrity": "sha512-0T+A9WZm+bZ84nZBtk1ckYsOvyA3x7e2Acj1KdVfV4/2tdG4fzUp91YHx+GArWLtwqp77pBXVCPn2We7Letr0Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.0.tgz", + "integrity": "sha512-fyzLm/DLDl/84OCfp2f/XQ4flmORsjU7VKt8HLjvIXChJoFFOIL6pLJPH4Yhd1n1gGFF9mPwtlN5Wf82DZs+LQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.0.tgz", + "integrity": "sha512-l9GeW5UZBT9k9brBYI+0WDffcRxgHQD8ShN2Ur4xWq/NFzUKm3k5lsH4PdaRgb2w7mI9u61nr2gI2mLI27Nh3Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" ], "engines": { "node": ">=18" @@ -744,78 +1251,180 @@ "node": ">=18" } }, - "node_modules/@esbuild/linux-mips64el": { + "node_modules/@esbuild/linux-mips64el": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.0.tgz", + "integrity": "sha512-2jIfP6mmjkdmeTlsX/9vmdmhBmKADrWqN7zcdtHIeNSCH1SqIoNI63cYsjQR8J+wGa4Y5izRcSHSm8K3QWmk3w==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.0.tgz", + "integrity": "sha512-bc0FE9wWeC0WBm49IQMPSPILRocGTQt3j5KPCA8os6VprfuJ7KD+5PzESSrJ6GmPIPJK965ZJHTUlSA6GNYEhg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.0.tgz", + "integrity": "sha512-SQPZOwoTTT/HXFXQJG/vBX8sOFagGqvZyXcgLA3NhIqcBv1BJU1d46c0rGcrij2B56Z2rNiSLaZOYW5cUk7yLQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.0.tgz", + "integrity": "sha512-SCfR0HN8CEEjnYnySJTd2cw0k9OHB/YFzt5zgJEwa+wL/T/raGWYMBqwDNAC6dqFKmJYZoQBRfHjgwLHGSrn3Q==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.0.tgz", + "integrity": "sha512-us0dSb9iFxIi8srnpl931Nvs65it/Jd2a2K3qs7fz2WfGPHqzfzZTfec7oxZJRNPXPnNYZtanmRc4AL/JwVzHQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.0.tgz", + "integrity": "sha512-CR/RYotgtCKwtftMwJlUU7xCVNg3lMYZ0RzTmAHSfLCXw3NtZtNpswLEj/Kkf6kEL3Gw+BpOekRX0BYCtklhUw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.0.tgz", - "integrity": "sha512-2jIfP6mmjkdmeTlsX/9vmdmhBmKADrWqN7zcdtHIeNSCH1SqIoNI63cYsjQR8J+wGa4Y5izRcSHSm8K3QWmk3w==", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.0.tgz", + "integrity": "sha512-nU1yhmYutL+fQ71Kxnhg8uEOdC0pwEW9entHykTgEbna2pw2dkbFSMeqjjyHZoCmt8SBkOSvV+yNmm94aUrrqw==", "cpu": [ - "mips64el" + "x64" ], "dev": true, "license": "MIT", "optional": true, "os": [ - "linux" + "netbsd" ], "engines": { "node": ">=18" } }, - "node_modules/@esbuild/linux-ppc64": { + "node_modules/@esbuild/openbsd-arm64": { "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.0.tgz", - "integrity": "sha512-bc0FE9wWeC0WBm49IQMPSPILRocGTQt3j5KPCA8os6VprfuJ7KD+5PzESSrJ6GmPIPJK965ZJHTUlSA6GNYEhg==", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.0.tgz", + "integrity": "sha512-cXb5vApOsRsxsEl4mcZ1XY3D4DzcoMxR/nnc4IyqYs0rTI8ZKmW6kyyg+11Z8yvgMfAEldKzP7AdP64HnSC/6g==", "cpu": [ - "ppc64" + "arm64" ], "dev": true, "license": "MIT", "optional": true, "os": [ - "linux" + "openbsd" ], "engines": { "node": ">=18" } }, - "node_modules/@esbuild/linux-riscv64": { + "node_modules/@esbuild/openbsd-x64": { "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.0.tgz", - "integrity": "sha512-SQPZOwoTTT/HXFXQJG/vBX8sOFagGqvZyXcgLA3NhIqcBv1BJU1d46c0rGcrij2B56Z2rNiSLaZOYW5cUk7yLQ==", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.0.tgz", + "integrity": "sha512-8wZM2qqtv9UP3mzy7HiGYNH/zjTA355mpeuA+859TyR+e+Tc08IHYpLJuMsfpDJwoLo1ikIJI8jC3GFjnRClzA==", "cpu": [ - "riscv64" + "x64" ], "dev": true, "license": "MIT", "optional": true, "os": [ - "linux" + "openbsd" ], "engines": { "node": ">=18" } }, - "node_modules/@esbuild/linux-s390x": { + "node_modules/@esbuild/openharmony-arm64": { "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.0.tgz", - "integrity": "sha512-SCfR0HN8CEEjnYnySJTd2cw0k9OHB/YFzt5zgJEwa+wL/T/raGWYMBqwDNAC6dqFKmJYZoQBRfHjgwLHGSrn3Q==", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.0.tgz", + "integrity": "sha512-FLGfyizszcef5C3YtoyQDACyg95+dndv79i2EekILBofh5wpCa1KuBqOWKrEHZg3zrL3t5ouE5jgr94vA+Wb2w==", "cpu": [ - "s390x" + "arm64" ], "dev": true, "license": "MIT", "optional": true, "os": [ - "linux" + "openharmony" ], "engines": { "node": ">=18" } }, - "node_modules/@esbuild/linux-x64": { + "node_modules/@esbuild/sunos-x64": { "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.0.tgz", - "integrity": "sha512-us0dSb9iFxIi8srnpl931Nvs65it/Jd2a2K3qs7fz2WfGPHqzfzZTfec7oxZJRNPXPnNYZtanmRc4AL/JwVzHQ==", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.0.tgz", + "integrity": "sha512-1ZgjUoEdHZZl/YlV76TSCz9Hqj9h9YmMGAgAPYd+q4SicWNX3G5GCyx9uhQWSLcbvPW8Ni7lj4gDa1T40akdlw==", "cpu": [ "x64" ], @@ -823,16 +1432,16 @@ "license": "MIT", "optional": true, "os": [ - "linux" + "sunos" ], "engines": { "node": ">=18" } }, - "node_modules/@esbuild/netbsd-arm64": { + "node_modules/@esbuild/win32-arm64": { "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.0.tgz", - "integrity": "sha512-CR/RYotgtCKwtftMwJlUU7xCVNg3lMYZ0RzTmAHSfLCXw3NtZtNpswLEj/Kkf6kEL3Gw+BpOekRX0BYCtklhUw==", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.0.tgz", + "integrity": "sha512-Q9StnDmQ/enxnpxCCLSg0oo4+34B9TdXpuyPeTedN/6+iXBJ4J+zwfQI28u/Jl40nOYAxGoNi7mFP40RUtkmUA==", "cpu": [ "arm64" ], @@ -840,157 +1449,449 @@ "license": "MIT", "optional": true, "os": [ - "netbsd" + "win32" ], "engines": { "node": ">=18" } }, - "node_modules/@esbuild/netbsd-x64": { + "node_modules/@esbuild/win32-ia32": { "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.0.tgz", - "integrity": "sha512-nU1yhmYutL+fQ71Kxnhg8uEOdC0pwEW9entHykTgEbna2pw2dkbFSMeqjjyHZoCmt8SBkOSvV+yNmm94aUrrqw==", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.0.tgz", + "integrity": "sha512-zF3ag/gfiCe6U2iczcRzSYJKH1DCI+ByzSENHlM2FcDbEeo5Zd2C86Aq0tKUYAJJ1obRP84ymxIAksZUcdztHA==", "cpu": [ - "x64" + "ia32" ], "dev": true, "license": "MIT", "optional": true, "os": [ - "netbsd" + "win32" ], "engines": { "node": ">=18" } }, - "node_modules/@esbuild/openbsd-arm64": { + "node_modules/@esbuild/win32-x64": { "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.0.tgz", - "integrity": "sha512-cXb5vApOsRsxsEl4mcZ1XY3D4DzcoMxR/nnc4IyqYs0rTI8ZKmW6kyyg+11Z8yvgMfAEldKzP7AdP64HnSC/6g==", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.0.tgz", + "integrity": "sha512-pEl1bO9mfAmIC+tW5btTmrKaujg3zGtUmWNdCw/xs70FBjwAL3o9OEKNHvNmnyylD6ubxUERiEhdsL0xBQ9efw==", "cpu": [ - "arm64" + "x64" ], "dev": true, "license": "MIT", "optional": true, "os": [ - "openbsd" + "win32" ], "engines": { "node": ">=18" } }, - "node_modules/@esbuild/openbsd-x64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.0.tgz", - "integrity": "sha512-8wZM2qqtv9UP3mzy7HiGYNH/zjTA355mpeuA+859TyR+e+Tc08IHYpLJuMsfpDJwoLo1ikIJI8jC3GFjnRClzA==", - "cpu": [ - "x64" - ], + "node_modules/@fluent/syntax": { + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/@fluent/syntax/-/syntax-0.19.0.tgz", + "integrity": "sha512-5D2qVpZrgpjtqU4eNOcWGp1gnUCgjfM+vKGE2y03kKN6z5EBhtx0qdRFbg8QuNNj8wXNoX93KJoYb+NqoxswmQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=14.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/@inquirer/ansi": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@inquirer/ansi/-/ansi-2.0.6.tgz", + "integrity": "sha512-I/INw4sHGlVZ/afZOckpLiDP9SmbMl1g/GCqeHjLw1Afw/0PlRs2tRFgTGWmdI0hoNuWZn3y2iHNmG1vyECyQQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0" + } + }, + "node_modules/@inquirer/checkbox": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@inquirer/checkbox/-/checkbox-5.2.0.tgz", + "integrity": "sha512-1HJt+3fqxblp/GQjdntSyoSHYBc0e3CzXVgjFpKA6qFLd9FHBBqwN8Co0xYH6t2JVUZrtFwZ4bBiwptkiLxyOg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/ansi": "^2.0.6", + "@inquirer/core": "^11.2.0", + "@inquirer/figures": "^2.0.6", + "@inquirer/type": "^4.0.6" + }, + "engines": { + "node": ">=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/confirm": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@inquirer/confirm/-/confirm-6.1.0.tgz", + "integrity": "sha512-USpeB76eqK7yGricDlGAupxWlp4a59qpeZOoNWaxO/nJln7agpJveyNkQ1d5u8YXG6TOqxZtQpKPORQQDrdVsA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/core": "^11.2.0", + "@inquirer/type": "^4.0.6" + }, + "engines": { + "node": ">=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/core": { + "version": "11.2.0", + "resolved": "https://registry.npmjs.org/@inquirer/core/-/core-11.2.0.tgz", + "integrity": "sha512-joR1YS2sI0us+9d0I8ViqFbrRLONO8CFTuyvBX4ZVBSch+VsZiugUABdrhBXXJR1VyEzvpz5SQCix3keETQ58g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/ansi": "^2.0.6", + "@inquirer/figures": "^2.0.6", + "@inquirer/type": "^4.0.6", + "cli-width": "^4.1.0", + "fast-wrap-ansi": "^0.2.0", + "mute-stream": "^4.0.0", + "signal-exit": "^4.1.0" + }, + "engines": { + "node": ">=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/editor": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@inquirer/editor/-/editor-5.2.0.tgz", + "integrity": "sha512-/m+sgRmzSdK6HDtVnl3PmI6MnZC4O+LLezedoJcrX7mINhTjjb0hlC7aEDGZXkFTB4b5uQ0q59AhYTah88KbNg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/core": "^11.2.0", + "@inquirer/external-editor": "^3.0.1", + "@inquirer/type": "^4.0.6" + }, + "engines": { + "node": ">=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/expand": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@inquirer/expand/-/expand-5.1.0.tgz", + "integrity": "sha512-fR7g4BVnIcs+4NApF6C5byflNM/EULxSxsv/2Jvg+gmop0R6eBIPvZqE6RYnTy1tQTFnf9wyHkwNoQSZbofaGA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/core": "^11.2.0", + "@inquirer/type": "^4.0.6" + }, + "engines": { + "node": ">=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/external-editor": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@inquirer/external-editor/-/external-editor-3.0.1.tgz", + "integrity": "sha512-tam+Gwjsxg2sx3iUVPkAnhKT/yrk2rd2NAa7XJU/J8OYpU0ifXsnp12xlvzp/DCpWBXVv+vLQsqnpAWwUcWD5Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "chardet": "^2.1.1", + "iconv-lite": "^0.7.2" + }, + "engines": { + "node": ">=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/figures": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@inquirer/figures/-/figures-2.0.6.tgz", + "integrity": "sha512-dsZgQtH2t5Q6ah3aPbZbeEZAxsD9qQu0DXf01AltuEfRTm+NoLN6+rLVbr+4edeEbNCp/wBNM6mALRWtsQpfkw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0" + } + }, + "node_modules/@inquirer/input": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@inquirer/input/-/input-5.1.0.tgz", + "integrity": "sha512-sVZCz6P6e8tW5g2bSFel1oLpa6jK/u7BexFfrgTqR8syIdnHqy+iopnlSbYBZMsCK52chLjhGNBxt0eRqhsghw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/core": "^11.2.0", + "@inquirer/type": "^4.0.6" + }, + "engines": { + "node": ">=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/number": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@inquirer/number/-/number-4.1.0.tgz", + "integrity": "sha512-VMXB/XejCbaSTf9Xucl7dqjzzsaGsrs6XwSYXPbGZ2QbSuq/Gz8XamhSi9ClRubNXZlGry9xVg1tKkJdTDgCtQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/core": "^11.2.0", + "@inquirer/type": "^4.0.6" + }, + "engines": { + "node": ">=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/password": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@inquirer/password/-/password-5.1.0.tgz", + "integrity": "sha512-5tqRuKCDIUxdPxTI/CuLnh914kz+WMPmURHKnZgui9gk43ebudEsdu4EwSn1CPSi5R+17YpBG+ba/YqTnRAcJA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/ansi": "^2.0.6", + "@inquirer/core": "^11.2.0", + "@inquirer/type": "^4.0.6" + }, + "engines": { + "node": ">=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/prompts": { + "version": "8.5.0", + "resolved": "https://registry.npmjs.org/@inquirer/prompts/-/prompts-8.5.0.tgz", + "integrity": "sha512-pLjXOnY4y3R1mgyHP3pXD/8eXejp+L/dde/0N2NLKgKfMstqhNZrpvs7Wkzbl9FYFQh10LRQ7QZwq+cz9rrhyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/checkbox": "^5.2.0", + "@inquirer/confirm": "^6.1.0", + "@inquirer/editor": "^5.2.0", + "@inquirer/expand": "^5.1.0", + "@inquirer/input": "^5.1.0", + "@inquirer/number": "^4.1.0", + "@inquirer/password": "^5.1.0", + "@inquirer/rawlist": "^5.3.0", + "@inquirer/search": "^4.2.0", + "@inquirer/select": "^5.2.0" + }, + "engines": { + "node": ">=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/rawlist": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/@inquirer/rawlist/-/rawlist-5.3.0.tgz", + "integrity": "sha512-p+vAeTAD+cGXjGleP1F5LXrX2ISxNDZm+lqeBpnJausNLSZskZZkcggwhomqP8Igx9oIjnoeOrw98xvdFvdm2w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/core": "^11.2.0", + "@inquirer/type": "^4.0.6" + }, + "engines": { + "node": ">=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/search": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@inquirer/search/-/search-4.2.0.tgz", + "integrity": "sha512-ByURoSGIaSl5O5Q0AmYmVmUsXbMUcBGNoA3FRL7TOyiA22IeFHymJKRkuILbOIlJwqnBk7AnPpseodyFUBzg+g==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], + "dependencies": { + "@inquirer/core": "^11.2.0", + "@inquirer/figures": "^2.0.6", + "@inquirer/type": "^4.0.6" + }, "engines": { - "node": ">=18" + "node": ">=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } } }, - "node_modules/@esbuild/openharmony-arm64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.0.tgz", - "integrity": "sha512-FLGfyizszcef5C3YtoyQDACyg95+dndv79i2EekILBofh5wpCa1KuBqOWKrEHZg3zrL3t5ouE5jgr94vA+Wb2w==", - "cpu": [ - "arm64" - ], + "node_modules/@inquirer/select": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@inquirer/select/-/select-5.2.0.tgz", + "integrity": "sha512-6IzkcmEbEXfgVbxZ2d1UyJFbCBoc6dTofulFmrYuomIp88HXiVqRbqbg4/mbfZhvnNo6xYmnYo2AEmDof6fQkg==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ], + "dependencies": { + "@inquirer/ansi": "^2.0.6", + "@inquirer/core": "^11.2.0", + "@inquirer/figures": "^2.0.6", + "@inquirer/type": "^4.0.6" + }, "engines": { - "node": ">=18" + "node": ">=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } } }, - "node_modules/@esbuild/sunos-x64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.0.tgz", - "integrity": "sha512-1ZgjUoEdHZZl/YlV76TSCz9Hqj9h9YmMGAgAPYd+q4SicWNX3G5GCyx9uhQWSLcbvPW8Ni7lj4gDa1T40akdlw==", - "cpu": [ - "x64" - ], + "node_modules/@inquirer/type": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/@inquirer/type/-/type-4.0.6.tgz", + "integrity": "sha512-J+9tdxOskuYuGjsvGaq00AamhDgjR7anhEW2dP4QdQpFCMPngCeC/bCYWQ5NsMWZRdsy53is7kAHb/+7cwDk2g==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "sunos" - ], "engines": { - "node": ">=18" + "node": ">=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } } }, - "node_modules/@esbuild/win32-arm64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.0.tgz", - "integrity": "sha512-Q9StnDmQ/enxnpxCCLSg0oo4+34B9TdXpuyPeTedN/6+iXBJ4J+zwfQI28u/Jl40nOYAxGoNi7mFP40RUtkmUA==", - "cpu": [ - "arm64" - ], + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" } }, - "node_modules/@esbuild/win32-ia32": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.0.tgz", - "integrity": "sha512-zF3ag/gfiCe6U2iczcRzSYJKH1DCI+ByzSENHlM2FcDbEeo5Zd2C86Aq0tKUYAJJ1obRP84ymxIAksZUcdztHA==", - "cpu": [ - "ia32" - ], + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" } }, - "node_modules/@esbuild/win32-x64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.0.tgz", - "integrity": "sha512-pEl1bO9mfAmIC+tW5btTmrKaujg3zGtUmWNdCw/xs70FBjwAL3o9OEKNHvNmnyylD6ubxUERiEhdsL0xBQ9efw==", - "cpu": [ - "x64" - ], + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "win32" - ], "engines": { - "node": ">=18" + "node": ">=6.0.0" } }, - "node_modules/@fluent/syntax": { - "version": "0.19.0", - "resolved": "https://registry.npmjs.org/@fluent/syntax/-/syntax-0.19.0.tgz", - "integrity": "sha512-5D2qVpZrgpjtqU4eNOcWGp1gnUCgjfM+vKGE2y03kKN6z5EBhtx0qdRFbg8QuNNj8wXNoX93KJoYb+NqoxswmQ==", + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=14.0.0", - "npm": ">=7.0.0" + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" } }, "node_modules/@nodelib/fs.scandir": { @@ -1031,6 +1932,13 @@ "node": ">= 8" } }, + "node_modules/@sec-ant/readable-stream": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@sec-ant/readable-stream/-/readable-stream-0.4.1.tgz", + "integrity": "sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg==", + "dev": true, + "license": "MIT" + }, "node_modules/@silverbucket/ajv-formats-draft2019": { "version": "1.6.5", "resolved": "https://registry.npmjs.org/@silverbucket/ajv-formats-draft2019/-/ajv-formats-draft2019-1.6.5.tgz", @@ -1082,6 +1990,176 @@ "url": "https://ko-fi.com/dangreen" } }, + "node_modules/@sindresorhus/merge-streams": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/merge-streams/-/merge-streams-4.0.0.tgz", + "integrity": "sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@stryker-mutator/api": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/@stryker-mutator/api/-/api-9.6.1.tgz", + "integrity": "sha512-g8VNoFWQWbx0pdal3Vt8jVCZW+v3sc3gi94iI0GVtVgUGTqphAjJF6EAruPTx0lqvtonsaAxn5TD36hcG1d6Wg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "mutation-testing-metrics": "3.7.3", + "mutation-testing-report-schema": "3.7.3", + "tslib": "~2.8.0", + "typed-inject": "~5.0.0" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@stryker-mutator/core": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/@stryker-mutator/core/-/core-9.6.1.tgz", + "integrity": "sha512-WMgnvf+Wyh/yiruhNZwc8w8DlzmmjXhPjSn5MR8RhAXzlnWji8TQrUYgBUkHk9bEgSaIlB3KZHm37iiU5Q2cLQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@inquirer/prompts": "^8.0.0", + "@stryker-mutator/api": "9.6.1", + "@stryker-mutator/instrumenter": "9.6.1", + "@stryker-mutator/util": "9.6.1", + "ajv": "~8.18.0", + "chalk": "~5.6.0", + "commander": "~14.0.0", + "diff-match-patch": "1.0.5", + "emoji-regex": "~10.6.0", + "execa": "~9.6.0", + "json-rpc-2.0": "^1.7.0", + "lodash.groupby": "~4.6.0", + "minimatch": "~10.2.4", + "mutation-server-protocol": "~0.4.0", + "mutation-testing-elements": "3.7.3", + "mutation-testing-metrics": "3.7.3", + "mutation-testing-report-schema": "3.7.3", + "npm-run-path": "~6.0.0", + "progress": "~2.0.3", + "rxjs": "~7.8.1", + "semver": "^7.6.3", + "source-map": "~0.7.4", + "tree-kill": "~1.2.2", + "tslib": "2.8.1", + "typed-inject": "~5.0.0", + "typed-rest-client": "~2.3.0" + }, + "bin": { + "stryker": "bin/stryker.js" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@stryker-mutator/core/node_modules/ajv": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", + "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/@stryker-mutator/core/node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/@stryker-mutator/core/node_modules/brace-expansion": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.6.tgz", + "integrity": "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/@stryker-mutator/core/node_modules/minimatch": { + "version": "10.2.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", + "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.5" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@stryker-mutator/instrumenter": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/@stryker-mutator/instrumenter/-/instrumenter-9.6.1.tgz", + "integrity": "sha512-5K8wH4Pthly25c2uKKik4Dfcoeou7sbJdFS6u3QIYHlulgFVDJwtEMWTZGkZfs7IiUEXIDNa0keRACq5jn5AvA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@babel/core": "~7.29.0", + "@babel/generator": "~7.29.0", + "@babel/parser": "~7.29.0", + "@babel/plugin-proposal-decorators": "~7.29.0", + "@babel/plugin-transform-explicit-resource-management": "^7.28.0", + "@babel/preset-typescript": "~7.28.0", + "@stryker-mutator/api": "9.6.1", + "@stryker-mutator/util": "9.6.1", + "angular-html-parser": "~10.4.0", + "semver": "~7.7.0", + "tslib": "2.8.1", + "weapon-regex": "~1.3.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@stryker-mutator/instrumenter/node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@stryker-mutator/util": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/@stryker-mutator/util/-/util-9.6.1.tgz", + "integrity": "sha512-Lk/ALVctJjFv1vvwR+CFoKzDCWvsBlq7flDUnmnpuwTrGbm156EdZD1Jjq4o8KdOap0ezUZqQNE9OAI1m2+pUQ==", + "dev": true, + "license": "Apache-2.0" + }, "node_modules/@types/glob": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.2.0.tgz", @@ -1284,6 +2362,16 @@ "ajv": "^8.8.2" } }, + "node_modules/angular-html-parser": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/angular-html-parser/-/angular-html-parser-10.4.0.tgz", + "integrity": "sha512-++nLNyZwRfHqFh7akH5Gw/JYizoFlMRz0KRigfwfsLqV8ZqlcVRb1LkPEWdYvEKDnbktknM2J4BXaYUGrQZPww==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, "node_modules/ansi-regex": { "version": "6.2.2", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", @@ -1341,6 +2429,19 @@ "dev": true, "license": "MIT" }, + "node_modules/baseline-browser-mapping": { + "version": "2.10.32", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.32.tgz", + "integrity": "sha512-wbPvpyjJPC0zdfdKXxqEL3Ea+bOMD/87X4lftiJkkaBiuG6ALQy1SLmEd7BSmVCuwCQsBrCamgBoLyfFDD1EPg==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/brace-expansion": { "version": "1.1.14", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz", @@ -1365,6 +2466,71 @@ "node": ">=8" } }, + "node_modules/browserslist": { + "version": "4.28.2", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.2.tgz", + "integrity": "sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "baseline-browser-mapping": "^2.10.12", + "caniuse-lite": "^1.0.30001782", + "electron-to-chromium": "^1.5.328", + "node-releases": "^2.0.36", + "update-browserslist-db": "^1.2.3" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", @@ -1385,6 +2551,40 @@ "node": ">=6" } }, + "node_modules/caniuse-lite": { + "version": "1.0.30001793", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001793.tgz", + "integrity": "sha512-iwSsYWaCOoh26cV8NwNRViHlrfUvYsHDfRVcbtmw0Kg6PJIZZXwMkj1442FYLBGkeUf1juAsU3DTfxW579mrPA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/chalk": { + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", + "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, "node_modules/change-case": { "version": "5.4.4", "resolved": "https://registry.npmjs.org/change-case/-/change-case-5.4.4.tgz", @@ -1392,6 +2592,23 @@ "dev": true, "license": "MIT" }, + "node_modules/chardet": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/chardet/-/chardet-2.1.1.tgz", + "integrity": "sha512-PsezH1rqdV9VvyNhxxOW32/d75r01NY7TQCmOqomRo15ZSOKbpTFVsfjghxo6JloQUCGnH4k1LGu0R4yCLlWQQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/cli-width": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-4.1.0.tgz", + "integrity": "sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">= 12" + } + }, "node_modules/cliui": { "version": "9.0.1", "resolved": "https://registry.npmjs.org/cliui/-/cliui-9.0.1.tgz", @@ -1495,6 +2712,13 @@ "node": ">=18" } }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, "node_modules/cosmiconfig": { "version": "9.0.1", "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.1.tgz", @@ -1540,6 +2764,21 @@ "typescript": ">=5" } }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, "node_modules/debug": { "version": "4.4.3", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", @@ -1568,6 +2807,24 @@ "node": ">=0.10.0" } }, + "node_modules/des.js": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/des.js/-/des.js-1.1.0.tgz", + "integrity": "sha512-r17GxjhUCjSRy8aiJpr8/UadFIzMzJGexI3Nmz4ADi9LYSFx4gTBp80+NaX/YsXWWLhpZ7v/v/ubEc/bCNfKwg==", + "dev": true, + "license": "MIT", + "dependencies": { + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0" + } + }, + "node_modules/diff-match-patch": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/diff-match-patch/-/diff-match-patch-1.0.5.tgz", + "integrity": "sha512-IayShXAgj/QMXgB0IWmKx+rOPuGMhqm5w6jvFxmVenXKIzRqTAAsbBPT3kWQeGANj3jGgvcvv4yK6SxqYmikgw==", + "dev": true, + "license": "Apache-2.0" + }, "node_modules/dir-glob": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", @@ -1601,6 +2858,28 @@ "node": ">=8" } }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.364", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.364.tgz", + "integrity": "sha512-G/dYE3+AYhyHwzTwg8UbnXf7zqMERYh7l2jJ3QujhFsH8agSYwtnGAR2aZ7f0AakIKJXd5En/Hre4igIUrdlYw==", + "dev": true, + "license": "ISC" + }, "node_modules/emoji-regex": { "version": "10.6.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", @@ -1628,6 +2907,39 @@ "is-arrayish": "^0.2.1" } }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz", + "integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/es-toolkit": { "version": "1.46.1", "resolved": "https://registry.npmjs.org/es-toolkit/-/es-toolkit-1.46.1.tgz", @@ -1705,6 +3017,33 @@ "node": ">=4" } }, + "node_modules/execa": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-9.6.1.tgz", + "integrity": "sha512-9Be3ZoN4LmYR90tUoVu2te2BsbzHfhJyfEiAVfz7N5/zv+jduIfLrV2xdQXOHbaD6KgpGdO9PRPM1Y4Q9QkPkA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sindresorhus/merge-streams": "^4.0.0", + "cross-spawn": "^7.0.6", + "figures": "^6.1.0", + "get-stream": "^9.0.0", + "human-signals": "^8.0.1", + "is-plain-obj": "^4.1.0", + "is-stream": "^4.0.1", + "npm-run-path": "^6.0.0", + "pretty-ms": "^9.2.0", + "signal-exit": "^4.1.0", + "strip-final-newline": "^4.0.0", + "yoctocolors": "^2.1.1" + }, + "engines": { + "node": "^18.19.0 || >=20.5.0" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, "node_modules/fast-check": { "version": "4.8.0", "resolved": "https://registry.npmjs.org/fast-check/-/fast-check-4.8.0.tgz", @@ -1751,6 +3090,23 @@ "node": ">=8.6.0" } }, + "node_modules/fast-string-truncated-width": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/fast-string-truncated-width/-/fast-string-truncated-width-3.0.3.tgz", + "integrity": "sha512-0jjjIEL6+0jag3l2XWWizO64/aZVtpiGE3t0Zgqxv0DPuxiMjvB3M24fCyhZUO4KomJQPj3LTSUnDP3GpdwC0g==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-string-width": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/fast-string-width/-/fast-string-width-3.0.2.tgz", + "integrity": "sha512-gX8LrtNEI5hq8DVUfRQMbr5lpaS4nMIWV+7XEbXk2b8kiQIizgnlr12B4dA3ZEx3308ze0O4Q1R+cHts8kyUJg==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-string-truncated-width": "^3.0.2" + } + }, "node_modules/fast-uri": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.2.tgz", @@ -1767,6 +3123,16 @@ ], "license": "BSD-3-Clause" }, + "node_modules/fast-wrap-ansi": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/fast-wrap-ansi/-/fast-wrap-ansi-0.2.2.tgz", + "integrity": "sha512-7F2Fl+TjRSenLqlU3UjSH0iyqopqoZIu7eZVpEirP2g1GtWa2G/ecEmBdgz31+Mxr+ELclgg6sokpSFIQiZ02Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-string-width": "^3.0.2" + } + }, "node_modules/fastq": { "version": "1.20.1", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz", @@ -1777,6 +3143,22 @@ "reusify": "^1.0.4" } }, + "node_modules/figures": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-6.1.0.tgz", + "integrity": "sha512-d+l3qxjSesT4V7v2fh+QnmFnUWv9lSpjarhShNTgBOfA0ttejbQUAlHLitbjkoRiDulW0OPoQPYIGhIC8ohejg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-unicode-supported": "^2.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/fill-range": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", @@ -1850,6 +3232,26 @@ "dev": true, "license": "ISC" }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/get-caller-file": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", @@ -1873,6 +3275,62 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/get-stream": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-9.0.1.tgz", + "integrity": "sha512-kVCxPF3vQM/N0B1PmoqVUqgHP+EeVjmZSQn+1oCRPxd2P21P2F19lIgbR3HBosbB1PUhOAoctJnfEn2GbN2eZA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sec-ant/readable-stream": "^0.4.1", + "is-stream": "^4.0.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/git-raw-commits": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/git-raw-commits/-/git-raw-commits-5.0.1.tgz", @@ -1989,6 +3447,19 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/graceful-fs": { "version": "4.2.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", @@ -1996,6 +3467,42 @@ "dev": true, "license": "ISC" }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz", + "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==", + "dev": true, + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/human-signals": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-8.0.1.tgz", + "integrity": "sha512-eKCa6bwnJhvxj14kZk5NCPc6Hb6BdsU9DZcOnmQKSnO1VKrfV0zCvtttPZUsBvjmNDn8rpcJfpwSYnHBjc95MQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, "node_modules/husky": { "version": "9.1.7", "resolved": "https://registry.npmjs.org/husky/-/husky-9.1.7.tgz", @@ -2012,6 +3519,23 @@ "url": "https://github.com/sponsors/typicode" } }, + "node_modules/iconv-lite": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz", + "integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==", + "dev": true, + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, "node_modules/ignore": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", @@ -2151,6 +3675,39 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/is-stream": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-4.0.1.tgz", + "integrity": "sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-unicode-supported": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-2.1.0.tgz", + "integrity": "sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, "node_modules/jiti": { "version": "2.6.1", "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz", @@ -2161,6 +3718,13 @@ "jiti": "lib/jiti-cli.mjs" } }, + "node_modules/js-md4": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/js-md4/-/js-md4-0.3.2.tgz", + "integrity": "sha512-/GDnfQYsltsjRswQhN9fhv3EMw2sCpUdrdxyWDOUK7eyD++r3gRhzgiQgc/x4MAv2i1iuQ4lxO5mvqM3vj4bwA==", + "dev": true, + "license": "MIT" + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -2200,6 +3764,13 @@ "dev": true, "license": "MIT" }, + "node_modules/json-rpc-2.0": { + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/json-rpc-2.0/-/json-rpc-2.0-1.7.1.tgz", + "integrity": "sha512-JqZjhjAanbpkXIzFE7u8mE/iFblawwlXtONaCvRqI+pyABVz7B4M1EUNpyVW+dZjqgQ2L5HFmZCmOCgUKm00hg==", + "dev": true, + "license": "MIT" + }, "node_modules/json-schema-test-esm": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/json-schema-test-esm/-/json-schema-test-esm-3.0.0.tgz", @@ -2216,6 +3787,19 @@ "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", "license": "MIT" }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/jsonfile": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", @@ -2574,6 +4158,33 @@ "node": ">=12" } }, + "node_modules/lodash.groupby": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/lodash.groupby/-/lodash.groupby-4.6.0.tgz", + "integrity": "sha512-5dcWxm23+VAoz+awKmBaiBvzox8+RqMgFhi7UvX9DHZr2HdxHXM/Wrf8cfKpsW37RNrvtPn6hSwNqurSILbmJw==", + "dev": true, + "license": "MIT" + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, "node_modules/meow": { "version": "13.2.0", "resolved": "https://registry.npmjs.org/meow/-/meow-13.2.0.tgz", @@ -2611,6 +4222,13 @@ "node": ">=8.6" } }, + "node_modules/minimalistic-assert": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", + "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==", + "dev": true, + "license": "ISC" + }, "node_modules/minimatch": { "version": "3.1.5", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", @@ -2638,6 +4256,53 @@ "dev": true, "license": "MIT" }, + "node_modules/mutation-server-protocol": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/mutation-server-protocol/-/mutation-server-protocol-0.4.1.tgz", + "integrity": "sha512-SBGK0j8hLDne7bktgThKI8kGvGTx3rY3LAeQTmOKZ5bVnL/7TorLMvcVF7dIPJCu5RNUWhkkuF53kurygYVt3g==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "zod": "^4.1.12" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/mutation-testing-elements": { + "version": "3.7.3", + "resolved": "https://registry.npmjs.org/mutation-testing-elements/-/mutation-testing-elements-3.7.3.tgz", + "integrity": "sha512-SMeIPxngJpfjfNYctFpYQQtlBlZaVO0aoB3FKdwrI8Ee/2bkyUuCZzAOCLv1U9fnmfA37dPFq0Owduoxs2XgGQ==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/mutation-testing-metrics": { + "version": "3.7.3", + "resolved": "https://registry.npmjs.org/mutation-testing-metrics/-/mutation-testing-metrics-3.7.3.tgz", + "integrity": "sha512-B8QrP0ZomErzTPNlhrzKWPNBln+3afwBZPHv0Q7N8wZZTYxMptzb/Gdm3ExXVmioVYrtZAtsDs7W/T/b2AixOQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "mutation-testing-report-schema": "3.7.3" + } + }, + "node_modules/mutation-testing-report-schema": { + "version": "3.7.3", + "resolved": "https://registry.npmjs.org/mutation-testing-report-schema/-/mutation-testing-report-schema-3.7.3.tgz", + "integrity": "sha512-BHm3MYq+ckO+t5CtlG8zpqxc75rdJCkxVlE+fGuGJM3F7tNCQ/OW2N+TQVHN3BHsYa84+BFc6g3AwDYkUsw2MA==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/mute-stream": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-4.0.0.tgz", + "integrity": "sha512-gSrprq0fJ3EiOErzjdIZrjysVVmJ4uu1QWfCDss5LypA5OXvrMje5Ym5z6V6RLyJ2eF87lasX7t6a0AnFvZblg==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^22.22.2 || ^24.15.0 || >=26.0.0" + } + }, "node_modules/nearley": { "version": "2.20.1", "resolved": "https://registry.npmjs.org/nearley/-/nearley-2.20.1.tgz", @@ -2668,6 +4333,46 @@ "dev": true, "license": "MIT" }, + "node_modules/node-releases": { + "version": "2.0.46", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.46.tgz", + "integrity": "sha512-GYVXHE2KnrzAfsAjl4uP++evGFCrAU1jta4ubEjIG7YWt/64Gqv66a30yKwWczVjA6j3bM4nBwH7Pk1JmDHaxQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/npm-run-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-6.0.0.tgz", + "integrity": "sha512-9qny7Z9DsQU8Ou39ERsPU4OZQlSTP47ShQzuKZ6PRXpYLtIFgl/DEBYEXKlvcEa+9tHVcK8CF81Y2V72qaZhWA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^4.0.0", + "unicorn-magic": "^0.3.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/npm-run-path/node_modules/path-key": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", + "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/object-hash": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", @@ -2678,6 +4383,19 @@ "node": ">= 6" } }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", @@ -2759,6 +4477,19 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/parse-ms": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-ms/-/parse-ms-4.0.0.tgz", + "integrity": "sha512-TXfryirbmq34y8QBwgqCVLi+8oA3oWx2eAnSn62ITyEhEYaWRlVZ2DvMM9eZbMs/RfxPu/PK/aBLyGj4IrqMHw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/path-exists": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", @@ -2779,6 +4510,16 @@ "node": ">=0.10.0" } }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/path-type": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", @@ -2809,6 +4550,32 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/pretty-ms": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/pretty-ms/-/pretty-ms-9.3.0.tgz", + "integrity": "sha512-gjVS5hOP+M3wMm5nmNOucbIrqudzs9v/57bWRHQWLYklXqoXKrVfYW2W9+glfGsqtPgpiz5WwyEEB+ksXIx3gQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "parse-ms": "^4.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/progress": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/pure-rand": { "version": "8.4.0", "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-8.4.0.tgz", @@ -2826,6 +4593,22 @@ ], "license": "MIT" }, + "node_modules/qs": { + "version": "6.15.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.2.tgz", + "integrity": "sha512-Rzq0KEyX/w/tEybncDgdkZrJgVUsUMk3xjh3t5bv3S1HTAtg+uOYt72+ZfwiQwKdysThkTBdL/rTi6HDmX9Ddw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", @@ -2976,6 +4759,23 @@ "queue-microtask": "^1.2.2" } }, + "node_modules/rxjs": { + "version": "7.8.2", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz", + "integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true, + "license": "MIT" + }, "node_modules/sast-json-schema": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/sast-json-schema/-/sast-json-schema-0.4.1.tgz", @@ -3020,6 +4820,118 @@ "dev": true, "license": "ISC" }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz", + "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/slash": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", @@ -3043,6 +4955,16 @@ "node": ">=0.10" } }, + "node_modules/source-map": { + "version": "0.7.6", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.6.tgz", + "integrity": "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">= 12" + } + }, "node_modules/sprintf-js": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", @@ -3084,6 +5006,19 @@ "url": "https://github.com/chalk/strip-ansi?sponsor=1" } }, + "node_modules/strip-final-newline": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-4.0.0.tgz", + "integrity": "sha512-aulFJcD6YK8V1G7iRB5tigAP4TsHBZZrOV8pjV++zdUwmeV8uzbY7yn6h9MswN62adStNZFuCIx4haBnRuMDaw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/tinybench": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-6.0.2.tgz", @@ -3117,6 +5052,16 @@ "node": ">=8.0" } }, + "node_modules/tree-kill": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", + "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", + "dev": true, + "license": "MIT", + "bin": { + "tree-kill": "cli.js" + } + }, "node_modules/tslib": { "version": "2.8.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", @@ -3124,6 +5069,43 @@ "dev": true, "license": "0BSD" }, + "node_modules/tunnel": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/tunnel/-/tunnel-0.0.6.tgz", + "integrity": "sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.6.11 <=0.7.0 || >=0.7.3" + } + }, + "node_modules/typed-inject": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/typed-inject/-/typed-inject-5.0.0.tgz", + "integrity": "sha512-0Ql2ORqBORLMdAW89TQKZsb1PQkFGImFfVmncXWe7a+AA3+7dh7Se9exxZowH4kbnlvKEFkMxUYdHUpjYWFJaA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/typed-rest-client": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/typed-rest-client/-/typed-rest-client-2.3.1.tgz", + "integrity": "sha512-k4kX5Up6qA68D0Cby2AK+6+vM5k3qTxe+/3FqhnHRExjY5cfbOnzjQZbP/LXleF8hVoDvDqxlgk9KK83HoBZlQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "des.js": "^1.1.0", + "js-md4": "^0.3.2", + "qs": "6.15.1", + "tunnel": "0.0.6", + "underscore": "^1.13.8" + }, + "engines": { + "node": ">= 16.0.0" + } + }, "node_modules/typescript": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.3.tgz", @@ -3139,6 +5121,13 @@ "node": ">=14.17" } }, + "node_modules/underscore": { + "version": "1.13.8", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.8.tgz", + "integrity": "sha512-DXtD3ZtEQzc7M8m4cXotyHR+FAS18C64asBYY5vqZexfYryNNnDc02W4hKg3rdQuqOYas1jkseX0+nZXjTXnvQ==", + "dev": true, + "license": "MIT" + }, "node_modules/undici-types": { "version": "7.24.6", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.24.6.tgz", @@ -3146,6 +5135,19 @@ "dev": true, "license": "MIT" }, + "node_modules/unicorn-magic": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.3.0.tgz", + "integrity": "sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/universalify": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", @@ -3156,6 +5158,37 @@ "node": ">= 4.0.0" } }, + "node_modules/update-browserslist-db": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, "node_modules/uri-js-replace": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/uri-js-replace/-/uri-js-replace-1.0.1.tgz", @@ -3163,6 +5196,29 @@ "dev": true, "license": "MIT" }, + "node_modules/weapon-regex": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/weapon-regex/-/weapon-regex-1.3.6.tgz", + "integrity": "sha512-wsf1m1jmMrso5nhwVFJJHSubEBf3+pereGd7+nBKtYJ18KoB/PWJOHS3WRkwS04VrOU0iJr2bZU+l1QaTJ+9nA==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, "node_modules/which-module": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.1.tgz", @@ -3205,6 +5261,13 @@ "node": ">=10" } }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + }, "node_modules/yargs": { "version": "18.0.0", "resolved": "https://registry.npmjs.org/yargs/-/yargs-18.0.0.tgz", @@ -3232,6 +5295,29 @@ "engines": { "node": "^20.19.0 || ^22.12.0 || >=23" } + }, + "node_modules/yoctocolors": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yoctocolors/-/yoctocolors-2.1.2.tgz", + "integrity": "sha512-CzhO+pFNo8ajLM2d2IW/R93ipy99LWjtwblvC1RsoSUMZgyLbYFr221TnSNT7GjGdYui6P459mw9JH/g/zW2ug==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/zod": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.4.3.tgz", + "integrity": "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } } } } diff --git a/package.json b/package.json index 653c2cd..60bc318 100644 --- a/package.json +++ b/package.json @@ -27,6 +27,7 @@ "2020-12.json", "src/$defs.json", "cli.js", + "package.json", "SECURITY.md" ], "publishConfig": { @@ -43,10 +44,11 @@ "prepublishOnly": "npm run build && npm test", "lint": "biome check --write --no-errors-on-unmatched", "pretest": "npm run build", - "test": "npm run test:lint && npm run test:unit && npm run test:sast && npm run test:perf && npm run test:dast", + "test": "npm run test:lint && npm run test:unit && npm run test:mutation && npm run test:sast && npm run test:perf && npm run test:dast", "test:lint": "biome ci --no-errors-on-unmatched", "test:lint:staged": "biome check --staged --no-errors-on-unmatched", "test:unit": "node --test --experimental-test-coverage --test-coverage-include=cli.js --test-coverage-lines=80 --test-coverage-branches=70 --test-coverage-functions=80 ./tests/*.test.js", + "test:mutation": "stryker run", "test:perf": "node --test --test-concurrency=1 ./tests/*.perf.js", "test:dast": "npm run test:dast:fuzz", "test:dast:fuzz": "node --test ./tests/*.fuzz.js", @@ -56,7 +58,9 @@ "rm:node_modules": "find . -name 'node_modules' -type d -prune -exec rm -rf '{}' +", "test:sast": "npm run test:sast:license && npm run test:sast:lockfile && npm run test:sast:semgrep && npm run test:sast:trufflehog && npm run test:sast:gitleaks && npm run test:sast:actionlint && npm run test:sast:zizmor && npm run test:sast:trivy", "test:sast:actionlint": "actionlint", - "test:sast:gitleaks": "gitleaks dir --no-banner .", + "test:sast:gitleaks": "npm run test:sast:gitleaks:dir && npm run test:sast:gitleaks:git", + "test:sast:gitleaks:dir": "gitleaks dir . --redact --no-banner", + "test:sast:gitleaks:git": "gitleaks git . --redact --no-banner", "test:sast:license": "license-check-and-add check -f .license.config.json", "test:sast:lockfile": "lockfile-lint --path package-lock.json --type npm --allowed-hosts npm --validate-https", "test:sast:semgrep": "semgrep scan --config auto", @@ -99,11 +103,15 @@ "@biomejs/biome": "^2.0.0", "@commitlint/cli": "^21.0.0", "@commitlint/config-conventional": "^21.0.0", + "@stryker-mutator/core": "^9.0.0", "ajv-cmd": "^0.13.0", "fast-check": "^4.0.0", "husky": "^9.0.0", "json-schema-test-esm": "^3.0.0", "license-check-and-add": "^4.0.0", "tinybench": "^6.0.0" + }, + "overrides": { + "qs": "^6.15.2" } } diff --git a/src/.npmignore b/src/.npmignore new file mode 100644 index 0000000..1902eec --- /dev/null +++ b/src/.npmignore @@ -0,0 +1,6 @@ +draft-04.json +draft-06.json +draft-07.json +2019-09.json +2020-12.json +!$defs.json diff --git a/stryker.config.json b/stryker.config.json new file mode 100644 index 0000000..b33d646 --- /dev/null +++ b/stryker.config.json @@ -0,0 +1,18 @@ +{ + "$schema": "./node_modules/@stryker-mutator/core/schema/stryker-schema.json", + "packageManager": "npm", + "testRunner": "command", + "commandRunner": { + "command": "node bin/build.js && node --test ./tests/*.test.js" + }, + "coverageAnalysis": "off", + "tempDirName": "/tmp/stryker/sast-json-schema", + "disableTypeChecks": false, + "mutate": ["cli.js"], + "reporters": ["progress", "clear-text"], + "thresholds": { + "high": 100, + "low": 100, + "break": 100 + } +} diff --git a/tests/cli.analyze.test.js b/tests/cli.analyze.test.js index 3bae1d1..74dadba 100644 --- a/tests/cli.analyze.test.js +++ b/tests/cli.analyze.test.js @@ -1,7 +1,12 @@ -import { ok, strictEqual } from "node:assert"; +import { deepStrictEqual, ok, strictEqual } from "node:assert"; import { describe, test } from "node:test"; import schema202012 from "../2020-12.json" with { type: "json" }; -import { analyze, formatSarif, resolveSSRFRefs } from "../cli.js"; +import { + analyze, + formatSarif, + resolveInstancePath, + resolveSSRFRefs, +} from "../cli.js"; test("analyze should filter errors matching options.ignore by instancePath", async () => { const schema = { @@ -230,6 +235,143 @@ describe("analyze options validation", () => { ok(err.message.includes("non-negative integer")); } }); + + test("should throw TypeError for non-numeric maxHostnames", async () => { + try { + await analyze( + { type: "string", maxLength: 10 }, + { offline: true, maxHostnames: "abc" }, + ); + ok(false, "should have thrown"); + } catch (err) { + ok(err instanceof TypeError); + ok(err.message.includes("maxHostnames")); + ok(err.message.includes("non-negative integer")); + } + }); + + test("should throw TypeError for negative dnsTotalTimeoutMs", async () => { + try { + await analyze( + { type: "string", maxLength: 10 }, + { offline: true, dnsTotalTimeoutMs: -1 }, + ); + ok(false, "should have thrown"); + } catch (err) { + ok(err instanceof TypeError); + ok(err.message.includes("dnsTotalTimeoutMs")); + ok(err.message.includes("non-negative integer")); + } + }); + + test("should throw TypeError for non-integer maxSchemaSize", async () => { + try { + await analyze( + { type: "string", maxLength: 10 }, + { offline: true, maxSchemaSize: 3.5 }, + ); + ok(false, "should have thrown"); + } catch (err) { + ok(err instanceof TypeError); + ok(err.message.includes("maxSchemaSize")); + ok(err.message.includes("non-negative integer")); + } + }); + + test("should throw TypeError for negative analysisTimeoutMs", async () => { + try { + await analyze( + { type: "string", maxLength: 10 }, + { offline: true, analysisTimeoutMs: -1 }, + ); + ok(false, "should have thrown"); + } catch (err) { + ok(err instanceof TypeError); + ok(err.message.includes("analysisTimeoutMs")); + ok(err.message.includes("non-negative integer")); + } + }); +}); + +// --- analyze size guard --- + +describe("analyze size guard", () => { + test("should reject schema exceeding maxSchemaSize with a RangeError", async () => { + try { + await analyze( + { type: "string", maxLength: 10, pattern: "^[a-z]+$" }, + { offline: true, maxSchemaSize: 5 }, + ); + ok(false, "should have thrown"); + } catch (err) { + ok(err instanceof RangeError); + ok(err.message.includes("size")); + } + }); + + test("should resolve to an array when within maxSchemaSize", async () => { + const errors = await analyze( + { type: "string", maxLength: 10, pattern: "^[a-z]+$" }, + { offline: true, maxSchemaSize: 1_000_000 }, + ); + ok(Array.isArray(errors)); + }); + + test("should throw TypeError for a circular schema", async () => { + const o = { type: "object" }; + o.self = o; + try { + await analyze(o, { offline: true }); + ok(false, "should have thrown"); + } catch (err) { + ok(err instanceof TypeError); + ok(err.message.includes("JSON-serializable")); + } + }); +}); + +// --- analyze time budget --- + +describe("analyze time budget", () => { + test("analysisTimeoutMs=0 should emit a timeout error", async () => { + const errors = await analyze( + { type: "string", maxLength: 10, pattern: "^[a-z]+$" }, + { offline: true, analysisTimeoutMs: 0 }, + ); + const timeout = errors.find((e) => e.keyword === "timeout"); + ok(timeout, "expected a timeout error"); + strictEqual(timeout.schemaPath, "#/timeout"); + strictEqual(timeout.instancePath, ""); + strictEqual(timeout.message, "schema analysis exceeded time budget"); + deepStrictEqual(timeout.params, {}); + }); + + test("ignore must NOT suppress the timeout finding (incomplete analysis stays visible)", async () => { + const errors = await analyze( + { type: "string", maxLength: 10, pattern: "^[a-z]+$" }, + { offline: true, analysisTimeoutMs: 0, ignore: [":timeout", ""] }, + ); + ok( + errors.some((e) => e.keyword === "timeout"), + "timeout finding must remain even when explicitly ignored", + ); + }); + + test("ignore must NOT suppress the depth finding", async () => { + const errors = await analyze( + { + type: "object", + properties: { + a: { type: "string", maxLength: 10, pattern: "^[a-z]+$" }, + }, + }, + { offline: true, overrideMaxDepth: 0, ignore: [":depth", ""] }, + ); + ok( + errors.some((e) => e.keyword === "depth"), + "depth finding must remain even when explicitly ignored", + ); + }); }); // --- regression: filter schemaPaths must match what AJV actually emits --- @@ -426,6 +568,32 @@ describe("resolveSSRFRefs", () => { }); strictEqual(errors.length, 0); }); + + test("should refuse DNS above maxHostnames cap (no DNS performed)", async () => { + const refs = Array.from({ length: 60 }, (_, i) => ({ + hostname: `h${i}.invalid`, + ref: `https://h${i}.invalid/schema.json`, + path: `/$ref/${i}`, + })); + const errors = await resolveSSRFRefs(refs, { maxHostnames: 50 }); + strictEqual(errors.length, 1); + strictEqual(errors[0].keyword, "ssrf"); + ok(errors[0].message.includes("too many")); + }); + + test("should fail closed when dnsTotalTimeoutMs budget is exceeded (no DNS performed)", async () => { + const refs = [ + { + hostname: "h.invalid", + ref: "https://h.invalid/schema.json", + path: "/$ref", + }, + ]; + const errors = await resolveSSRFRefs(refs, { dnsTotalTimeoutMs: 0 }); + strictEqual(errors.length, 1); + strictEqual(errors[0].keyword, "ssrf"); + ok(errors[0].message.includes("budget")); + }); }); // --- resolveInstancePath (via analyze overrides) --- @@ -485,6 +653,124 @@ describe("resolveInstancePath via overrides", () => { }); ok(errors.some((e) => e.keyword === "maxItems")); }); + + test("resolves an instancePath segment named 'constructor' (own-property read)", async () => { + // resolveInstancePath walks /properties/constructor/enum. The "constructor" + // segment must resolve via a guarded own-property read; if the walk were + // blocked or diverted onto the prototype, the maxItems error could not be + // suppressed and the first assertion would fail. Backs the + // prototype-pollution-loop nosemgrep in cli.js (the walk is a read, never + // a write, and Object.hasOwn keeps it on own properties). + const schema = { + $schema: "https://json-schema.org/draft/2020-12/schema", + $id: "test", + type: "object", + properties: { + constructor: { + type: "string", + maxLength: 50, + enum: Array.from({ length: 2000 }, (_, i) => `s${i}`), + }, + }, + required: ["constructor"], + unevaluatedProperties: false, + maxProperties: 5, + }; + const within = await analyze(schema, { + overrideMaxItems: 2000, + offline: true, + }); + ok(!within.some((e) => e.keyword === "maxItems")); + const below = await analyze(schema, { + overrideMaxItems: 1500, + offline: true, + }); + ok(below.some((e) => e.keyword === "maxItems")); + }); + + test("resolves an instancePath segment named '__proto__' (own-property read)", async () => { + // JSON.parse makes "__proto__" a real own data property (not the prototype + // setter), mirroring how a hostile schema reaches the tool. The walk must + // read it as own data and never traverse the real prototype chain. + const enumJson = JSON.stringify( + Array.from({ length: 2000 }, (_, i) => `s${i}`), + ); + const schema = JSON.parse( + `{"$schema":"https://json-schema.org/draft/2020-12/schema","$id":"test","type":"object","properties":{"__proto__":{"type":"string","maxLength":50,"enum":${enumJson}}},"required":["__proto__"],"unevaluatedProperties":false,"maxProperties":5}`, + ); + const within = await analyze(schema, { + overrideMaxItems: 2000, + offline: true, + }); + ok(!within.some((e) => e.keyword === "maxItems")); + const below = await analyze(schema, { + overrideMaxItems: 1500, + offline: true, + }); + ok(below.some((e) => e.keyword === "maxItems")); + }); +}); + +// --- resolveInstancePath (direct) --- +// The override filters only ever hand resolveInstancePath a JSON pointer that +// AJV emitted for a real array/object location, so they never exercise its +// defensive guards. These cover the helper directly: a non-walkable root, an +// empty pointer, a mid-walk descent into a non-object, and a missing segment. +describe("resolveInstancePath direct", () => { + test("returns undefined when the root is not a walkable object", () => { + strictEqual(resolveInstancePath(null, "/a"), undefined); + strictEqual(resolveInstancePath(42, "/a"), undefined); + strictEqual(resolveInstancePath("string", "/a"), undefined); + }); + + test("returns the root object for an empty pointer", () => { + const root = { a: 1 }; + strictEqual(resolveInstancePath(root, ""), root); + }); + + test("returns undefined when a mid-walk segment is not an object", () => { + strictEqual(resolveInstancePath({ a: 5 }, "/a/b"), undefined); + }); + + test("returns undefined when a segment is not an own property", () => { + strictEqual(resolveInstancePath({ a: {} }, "/a/missing"), undefined); + }); + + test("walks own properties, unescaping ~1 and ~0 segments", () => { + const root = { "a/b": { "c~d": 7 } }; + strictEqual(resolveInstancePath(root, "/a~1b/c~0d"), 7); + }); +}); + +// --- allErrors completeness --- +// The meta-schema validators compile with allErrors:true so a single pass +// surfaces EVERY violation, not just the first. This locks in that behavior, +// which is the justification for the ajv-allerrors-true nosemgrep in cli.js: +// dropping allErrors would silently hide findings from a security report. +describe("analyze allErrors completeness", () => { + test("reports violations from multiple sibling properties in one pass", async () => { + const schema = { + $schema: "https://json-schema.org/draft/2020-12/schema", + $id: "test", + type: "object", + properties: { + a: { type: "string" }, + b: { type: "string" }, + }, + required: ["a", "b"], + unevaluatedProperties: false, + maxProperties: 5, + }; + const errors = await analyze(schema, { offline: true }); + ok( + errors.some((e) => e.instancePath === "/properties/a"), + "expected a violation for property a", + ); + ok( + errors.some((e) => e.instancePath === "/properties/b"), + "expected a violation for property b", + ); + }); }); // --- resolveSSRFRefs private IP detection --- diff --git a/tests/cli.crawl.test.js b/tests/cli.crawl.test.js index 1ae2643..3b5a05e 100644 --- a/tests/cli.crawl.test.js +++ b/tests/cli.crawl.test.js @@ -582,6 +582,96 @@ describe("crawlSchema", () => { ok(Array.isArray(r.errors)); }); + test("should flag ReDoS-vulnerable patternProperties key before denylist matching", () => { + const r = crawlSchema({ + patternProperties: { "^(a+)+$": { type: "string" } }, + }); + const err = r.errors.find( + (e) => e.keyword === "patternProperties" && e.schemaPath === "#/redos", + ); + ok(err, "expected a patternProperties ReDoS error"); + strictEqual(err.params.reason, "hitMaxScore"); + strictEqual(err.params.pattern, "^(a+)+$"); + ok(err.message.includes("^(a+)+$")); + ok(err.instancePath.includes("/patternProperties/")); + }); + + test("unsafe patternProperties key is never matched via RegExp (short-circuits first)", () => { + // new RegExp(patternKey) in cli.js is reached only AFTER isSafePattern + // clears the key (the `if (!patternSafe) continue;` guard). An unsafe key + // must therefore yield a ReDoS finding and NO denylist-match finding, + // proving the dynamic RegExp never runs on a catastrophic pattern. This + // is the justification for the detect-non-literal-regexp nosemgrep. + const r = crawlSchema({ + patternProperties: { + "^(a+)+$": { type: "string" }, + "^(\\w+)*$": { type: "string" }, + }, + }); + const redos = r.errors.filter( + (e) => e.keyword === "patternProperties" && e.schemaPath === "#/redos", + ); + const matched = r.errors.filter( + (e) => e.keyword === "patternProperties" && e.params?.matches, + ); + strictEqual(redos.length, 2, "both unsafe keys must be flagged as ReDoS"); + strictEqual(matched.length, 0, "no denylist match may run on unsafe keys"); + }); + + // --- instance-data keywords are not analyzed as schemas --- + // const/enum/default/examples hold literal instance values, never + // subschemas. The crawler must not descend into them, or it reports + // false positives on data that merely looks like a schema. + test("should not run ReDoS analysis on a pattern inside const", () => { + const r = crawlSchema({ const: { pattern: "^(a+)+$" } }); + ok(!r.errors.some((e) => e.keyword === "pattern")); + }); + + test("should not flag a numeric range inside default", () => { + const r = crawlSchema({ + type: "object", + default: { type: "integer", minimum: 100, maximum: 1 }, + }); + ok(!r.errors.some((e) => e.keyword === "minimum")); + }); + + test("should not flag dangerous property names inside enum values", () => { + const r = crawlSchema( + JSON.parse('{"enum":[{"properties":{"__proto__":{"type":"string"}}}]}'), + ); + ok(!r.errors.some((e) => e.schemaPath === "#/dangerous-name")); + }); + + test("should not flag dangerous property names inside examples values", () => { + const r = crawlSchema( + JSON.parse( + '{"examples":[{"properties":{"__proto__":{"type":"string"}}}]}', + ), + ); + ok(!r.errors.some((e) => e.schemaPath === "#/dangerous-name")); + }); + + test("should still analyze real subschemas alongside instance-data keywords", () => { + // A genuine sibling subschema (in properties) must still be crawled even + // when const/default/examples are present and skipped. + const r = crawlSchema( + JSON.parse( + '{"properties":{"bad":{"type":"string","minLength":10,"maxLength":5}},"default":{"pattern":"^(a+)+$"}}', + ), + ); + ok(r.errors.some((e) => e.keyword === "minLength")); + ok(!r.errors.some((e) => e.keyword === "pattern")); + }); + + // --- analysis time budget (deadline) --- + test("should fail closed with a timeout error when deadline already passed", () => { + const r = crawlSchema({ type: "string", pattern: "^[a-z]+$" }, 32, { + deadline: 0, + }); + ok(r.errors.some((e) => e.keyword === "timeout")); + strictEqual(r.timedOut, true); + }); + // --- dangerous-name detection across all property-key sites (default lang=js) --- // Schemas come from JSON.parse in real usage. Object literals like // `{ __proto__: x }` set the prototype rather than adding a key, so we diff --git a/tests/cli.ip.test.js b/tests/cli.ip.test.js index 8bfadf8..ef1aeca 100644 --- a/tests/cli.ip.test.js +++ b/tests/cli.ip.test.js @@ -101,4 +101,12 @@ describe("isPrivateIP IPv6 extended", () => { test("IPv4-mapped dotted with zone ID classified private", () => { strictEqual(isPrivateIP("::ffff:192.168.1.1%eth0"), true); }); + + test("IPv4-mapped hex with invalid hex groups classified private (fail-closed)", () => { + strictEqual(isPrivateIP("0:0:0:0:0:ffff:0808:gggg"), true); + }); + + test("IPv4-mapped hex with valid hex still classified private", () => { + strictEqual(isPrivateIP("0:0:0:0:0:ffff:7f00:1"), true); + }); }); diff --git a/tests/cli.test.js b/tests/cli.test.js index 9da7dc9..c59dc8a 100644 --- a/tests/cli.test.js +++ b/tests/cli.test.js @@ -32,6 +32,121 @@ describe("cli.", () => { ok(r.stdout.includes("--format")); }); + test("--help documents the new resource-limit flags", async () => { + const r = await runCli(["--help"]); + strictEqual(r.code, 0); + ok(r.stdout.includes("--max-schema-size")); + ok(r.stdout.includes("--analysis-timeout-ms")); + ok(r.stdout.includes("--max-ssrf-hostnames")); + ok(r.stdout.includes("--dns-total-timeout-ms")); + }); + + test("--max-schema-size below schema size exits 2 with size error", async () => { + const { writeFile, mkdtemp } = await import("node:fs/promises"); + const { tmpdir } = await import("node:os"); + const { join } = await import("node:path"); + const dir = await mkdtemp(join(tmpdir(), "sast-test-")); + const path = join(dir, "schema.json"); + await writeFile( + path, + JSON.stringify({ + $schema: "https://json-schema.org/draft/2020-12/schema", + $id: "test", + type: "string", + maxLength: 10, + pattern: "^[a-z]+$", + }), + ); + const r = await runCli(["--offline", "--max-schema-size", "5", path]); + strictEqual(r.code, 2); + ok(r.stderr.includes("size")); + }); + + test("--max-schema-size is applied to the file-size check, not just analyze()", async () => { + const { writeFile, mkdtemp } = await import("node:fs/promises"); + const { tmpdir } = await import("node:os"); + const { join } = await import("node:path"); + const dir = await mkdtemp(join(tmpdir(), "sast-test-")); + const path = join(dir, "padded.json"); + // A clean schema whose compact serialization is tiny, padded with + // trailing whitespace so the FILE is far larger than the schema. With a + // limit between the two, only a file-size check (not the serialized-size + // check inside analyze) will reject it. + const schema = { + $schema: "https://json-schema.org/draft/2020-12/schema", + $id: "test", + type: "string", + maxLength: 10, + pattern: "^[a-z]+$", + }; + await writeFile(path, `${JSON.stringify(schema)}${" ".repeat(5000)}`); + const r = await runCli(["--offline", "--max-schema-size", "1000", path]); + strictEqual(r.code, 2); + ok(r.stderr.includes("exceeds")); + }); + + test("--max-schema-size with a non-integer reports a validation error, not a file-size error", async () => { + const { writeFile, mkdtemp } = await import("node:fs/promises"); + const { tmpdir } = await import("node:os"); + const { join } = await import("node:path"); + const dir = await mkdtemp(join(tmpdir(), "sast-test-")); + const path = join(dir, "schema.json"); + await writeFile( + path, + JSON.stringify({ + $schema: "https://json-schema.org/draft/2020-12/schema", + $id: "test", + type: "string", + maxLength: 10, + pattern: "^[a-z]+$", + }), + ); + const r = await runCli(["--offline", "--max-schema-size", "3.5", path]); + strictEqual(r.code, 2); + ok(r.stderr.includes("non-negative integer")); + ok(!r.stderr.includes("byte size limit")); + }); + + test("--analysis-timeout-ms 0 reports a timeout finding (exit 1)", async () => { + const { writeFile, mkdtemp } = await import("node:fs/promises"); + const { tmpdir } = await import("node:os"); + const { join } = await import("node:path"); + const dir = await mkdtemp(join(tmpdir(), "sast-test-")); + const path = join(dir, "schema.json"); + await writeFile( + path, + JSON.stringify({ + $schema: "https://json-schema.org/draft/2020-12/schema", + $id: "test", + type: "string", + maxLength: 10, + pattern: "^[a-z]+$", + }), + ); + const r = await runCli(["--offline", "--analysis-timeout-ms", "0", path]); + strictEqual(r.code, 1); + }); + + test("--max-ssrf-hostnames with non-integer exits 2", async () => { + const { writeFile, mkdtemp } = await import("node:fs/promises"); + const { tmpdir } = await import("node:os"); + const { join } = await import("node:path"); + const dir = await mkdtemp(join(tmpdir(), "sast-test-")); + const path = join(dir, "schema.json"); + await writeFile( + path, + JSON.stringify({ + $schema: "https://json-schema.org/draft/2020-12/schema", + $id: "test", + type: "string", + maxLength: 10, + pattern: "^[a-z]+$", + }), + ); + const r = await runCli(["--offline", "--max-ssrf-hostnames", "abc", path]); + strictEqual(r.code, 2); + }); + test("--help documents exit codes", async () => { const r = await runCli(["--help"]); strictEqual(r.code, 0); @@ -39,6 +154,8 @@ describe("cli.", () => { ok(/0\b.*no issues/i.test(r.stdout)); ok(/1\b.*issues/i.test(r.stdout)); ok(/2\b.*(usage|tool)/i.test(r.stdout)); + // exit-code descriptions name the resource-limit conditions (matches README) + ok(/depth-exceeded/i.test(r.stdout)); }); test("--version prints a semver-looking string", async () => { @@ -59,6 +176,35 @@ describe("cli.", () => { ok(r.stderr.includes("cannot read file")); }); + test("nonexistent --ref-schema-files exits 2", async () => { + const { writeFile, mkdtemp } = await import("node:fs/promises"); + const { tmpdir } = await import("node:os"); + const { join } = await import("node:path"); + const dir = await mkdtemp(join(tmpdir(), "sast-test-")); + const path = join(dir, "schema.json"); + await writeFile( + path, + JSON.stringify({ + $schema: "https://json-schema.org/draft/2020-12/schema", + $id: "test", + type: "string", + maxLength: 10, + pattern: "^[a-z]+$", + }), + ); + // The main is gated by stat() first; ref files are read directly, + // so a missing ref file is the only path that reaches readJsonFile's + // readFile error branch. + const r = await runCli([ + "--offline", + "-r", + "/tmp/does-not-exist-ref-xyz.json", + path, + ]); + strictEqual(r.code, 2); + ok(r.stderr.includes("cannot read --ref-schema-files file")); + }); + test("oversized file is rejected before being read", async () => { const { mkdtemp, open } = await import("node:fs/promises"); const { tmpdir } = await import("node:os"); @@ -204,6 +350,26 @@ describe("cli.", () => { ok(r.stdout.includes("has no issues")); }); + test("schema with issues prints human-format header and exits 1", async () => { + const { writeFile, mkdtemp } = await import("node:fs/promises"); + const { tmpdir } = await import("node:os"); + const { join } = await import("node:path"); + const dir = await mkdtemp(join(tmpdir(), "sast-test-")); + const path = join(dir, "insecure.json"); + await writeFile( + path, + JSON.stringify({ + $schema: "https://json-schema.org/draft/2020-12/schema", + $id: "test", + type: "string", + pattern: "^(a+)+$", + }), + ); + const r = await runCli(["--offline", path]); + strictEqual(r.code, 1); + ok(r.stdout.includes("has issues")); + }); + test("--override-max-depth with non-integer exits 2", async () => { const { writeFile, mkdtemp } = await import("node:fs/promises"); const { tmpdir } = await import("node:os"); From d77c1af18269e81d28c509476fee959be1fe7f45 Mon Sep 17 00:00:00 2001 From: will Farrell Date: Mon, 1 Jun 2026 05:47:57 -0600 Subject: [PATCH 07/13] ci: clean up Signed-off-by: will Farrell --- .license.config.json | 1 + cli.js | 3 +-- package.json | 4 ++-- stryker.config.json | 1 - 4 files changed, 4 insertions(+), 5 deletions(-) diff --git a/.license.config.json b/.license.config.json index db08634..07ad316 100644 --- a/.license.config.json +++ b/.license.config.json @@ -12,6 +12,7 @@ ".husky/**/*", "tests/fixtures/*", "commitlint.config.cjs", + "stryker.config.*", "LICENSE", ".license.template", "**/.gitignore", diff --git a/cli.js b/cli.js index 17cdd1b..ba797ce 100755 --- a/cli.js +++ b/cli.js @@ -617,8 +617,7 @@ export const crawlSchema = (obj, maxDepth = MAX_DEPTH, options = {}) => { message: `patternProperties key "${patternKey}" is vulnerable to ReDoS`, }); } - } catch { - } + } catch {} if (!patternSafe) continue; try { // nosemgrep: javascript.lang.security.audit.detect-non-literal-regexp.detect-non-literal-regexp diff --git a/package.json b/package.json index 60bc318..f348192 100644 --- a/package.json +++ b/package.json @@ -63,8 +63,8 @@ "test:sast:gitleaks:git": "gitleaks git . --redact --no-banner", "test:sast:license": "license-check-and-add check -f .license.config.json", "test:sast:lockfile": "lockfile-lint --path package-lock.json --type npm --allowed-hosts npm --validate-https", - "test:sast:semgrep": "semgrep scan --config auto", - "test:sast:trivy": "trivy fs --scanners vuln,license --include-dev-deps --ignored-licenses 0BSD,Apache-2.0,BSD-1-Clause,BSD-2-Clause,BSD-3-Clause,CC0-1.0,CC-BY-4.0,ISC,MIT,Python-2.0 --exit-code 1 --disable-telemetry .", + "test:sast:semgrep": "semgrep scan --config auto --error", + "test:sast:trivy": "trivy fs --scanners vuln,license --include-dev-deps --ignored-licenses 0BSD,Apache-2.0,BSD-1-Clause,BSD-2-Clause,BSD-3-Clause,CC0-1.0,CC-BY-4.0,ISC,MIT,Python-2.0,LGPL-3.0-or-later,MPL-2.0,BlueOak-1.0.0,Unlicense --exit-code 1 --skip-files '**/bun.lock' --disable-telemetry .", "test:sast:trufflehog": "trufflehog filesystem --only-verified --log-level=-1 ./", "test:sast:zizmor": "zizmor .github/workflows/" }, diff --git a/stryker.config.json b/stryker.config.json index b33d646..08d2214 100644 --- a/stryker.config.json +++ b/stryker.config.json @@ -7,7 +7,6 @@ }, "coverageAnalysis": "off", "tempDirName": "/tmp/stryker/sast-json-schema", - "disableTypeChecks": false, "mutate": ["cli.js"], "reporters": ["progress", "clear-text"], "thresholds": { From f1073f5ff4f6e0f74eb150c2534dab4d365113c9 Mon Sep 17 00:00:00 2001 From: will Farrell Date: Fri, 12 Jun 2026 13:11:26 -0600 Subject: [PATCH 08/13] test: improve coverage Signed-off-by: will Farrell --- .github/workflows/release.yml | 4 + .github/workflows/test-dast.yml | 4 + .github/workflows/test-dco.yml | 4 + .github/workflows/test-lint.yml | 6 + .github/workflows/test-mutation.yml | 5 + .github/workflows/test-perf.yml | 4 + .github/workflows/test-sast.yml | 6 +- .github/workflows/test-unit.yml | 8 +- README.md | 5 +- SECURITY.md | 2 +- bin/build.js | 154 ++-- cli.js | 896 +++++++++++++++------ src/$defs.json | 80 +- src/2019-09.json | 3 - src/draft-04.json | 22 +- tests/bfixes.test.js | 254 ++++++ tests/build.test.js | 66 ++ tests/cli.analyze.test.js | 843 ++++++++++++++++++++ tests/cli.crawl.test.js | 1141 ++++++++++++++++++++++++++- tests/cli.fuzz.js | 210 +++++ tests/cli.ip.test.js | 153 ++++ tests/cli.perf.js | 73 ++ tests/cli.run.test.js | 572 ++++++++++++++ tests/cli.sast.test.js | 38 +- 24 files changed, 4188 insertions(+), 365 deletions(-) create mode 100644 tests/bfixes.test.js create mode 100644 tests/build.test.js create mode 100644 tests/cli.fuzz.js create mode 100644 tests/cli.perf.js create mode 100644 tests/cli.run.test.js diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 645f88e..25d71f5 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -7,6 +7,10 @@ on: paths: - 'package.json' +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: false + env: NODE_VERSION: 24.x diff --git a/.github/workflows/test-dast.yml b/.github/workflows/test-dast.yml index 3023f06..3a8c050 100644 --- a/.github/workflows/test-dast.yml +++ b/.github/workflows/test-dast.yml @@ -3,6 +3,10 @@ name: Tests (dast) on: pull_request: +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + env: NODE_VERSION: 24.x diff --git a/.github/workflows/test-dco.yml b/.github/workflows/test-dco.yml index 8bfb9cb..9e67c8e 100644 --- a/.github/workflows/test-dco.yml +++ b/.github/workflows/test-dco.yml @@ -3,6 +3,10 @@ name: Tests (dco) on: pull_request: +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + permissions: contents: read diff --git a/.github/workflows/test-lint.yml b/.github/workflows/test-lint.yml index 790583b..3ba644a 100644 --- a/.github/workflows/test-lint.yml +++ b/.github/workflows/test-lint.yml @@ -2,6 +2,12 @@ name: Tests (lint) on: pull_request: + push: + branches: [main, develop] + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true env: NODE_VERSION: 24.x diff --git a/.github/workflows/test-mutation.yml b/.github/workflows/test-mutation.yml index 5038cec..93df38b 100644 --- a/.github/workflows/test-mutation.yml +++ b/.github/workflows/test-mutation.yml @@ -3,6 +3,10 @@ name: Tests (mutation) on: pull_request: +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + env: NODE_VERSION: 24.x @@ -30,6 +34,7 @@ jobs: with: node-version: ${{ env.NODE_VERSION }} registry-url: https://registry.npmjs.org + cache: npm - name: Install dependencies run: | npm ci --ignore-scripts diff --git a/.github/workflows/test-perf.yml b/.github/workflows/test-perf.yml index d069b8d..698e320 100644 --- a/.github/workflows/test-perf.yml +++ b/.github/workflows/test-perf.yml @@ -3,6 +3,10 @@ name: Tests (perf) on: pull_request: +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + env: NODE_VERSION: 24.x diff --git a/.github/workflows/test-sast.yml b/.github/workflows/test-sast.yml index 542c308..b001bf7 100644 --- a/.github/workflows/test-sast.yml +++ b/.github/workflows/test-sast.yml @@ -6,6 +6,10 @@ on: - cron: '43 3 * * 5' workflow_dispatch: +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + env: NODE_VERSION: 24.x @@ -162,7 +166,7 @@ jobs: name: 'semgrep: SAST' runs-on: ubuntu-latest container: - image: semgrep/semgrep:1.111.0@sha256:3e6e5065d9e68abffddffdb536a8db2d79a8fa92dc424daa48d3a4b7d9bc65d0 + image: semgrep/semgrep@sha256:3e6e5065d9e68abffddffdb536a8db2d79a8fa92dc424daa48d3a4b7d9bc65d0 # v1.111.0 if: (github.actor != 'dependabot[bot]') steps: # harden-runner skipped: this job runs in a container; harden-runner patches the host runner. diff --git a/.github/workflows/test-unit.yml b/.github/workflows/test-unit.yml index 444018c..1b0cd4e 100644 --- a/.github/workflows/test-unit.yml +++ b/.github/workflows/test-unit.yml @@ -2,6 +2,12 @@ name: Tests (unit) on: pull_request: + push: + branches: [main, develop] + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true env: NODE_VERSION: 24.x @@ -35,7 +41,7 @@ jobs: npm ci --ignore-scripts - name: Build run: | - npm run build --if-present + npm run build - name: Unit tests run: | npm run test:unit diff --git a/README.md b/README.md index 6532e21..aa738d1 100644 --- a/README.md +++ b/README.md @@ -126,8 +126,9 @@ ajv sast --fail path/to/schema.json - **Prototype-pollution denylist does not cover `patternProperties` keys.** The meta-schema rejects `__proto__`, `constructor`, and `prototype` as literal keys in `properties`, `$defs`, `definitions`, `dependentSchemas`, `dependentRequired`, and `required`. It does NOT reject these names when introduced via a `patternProperties` regex key, because any literal denylist (`^__proto__$`) is trivially bypassed by equivalent regexes (`^_{2}proto_{2}$`, `^[_][_]proto__$`, `^.{9}$`). Enforced by the CLI: `crawlSchema` compiles each `patternProperties` key and tests it against the denylisted names. Consumers using the meta-schema standalone (without `cli.js` / `analyze()`) get property-key protection but not `patternProperties` protection. - **Language-specific deserialization-vector names are not in the meta-schema.** Only `__proto__`, `constructor`, `prototype` are rejected at the meta-schema layer (the universal baseline). Names like `@type` (Java), `$type` (.NET), `__class__` (Python), `isa` (Objective-C), `__struct__` (Elixir), or PHP magic methods are enforced only at the CLI / `analyze()` layer via `--lang`. See [Language coverage](#language-coverage). - **Depth limits are a runtime concern.** Deeply nested schemas could cause stack overflow during recursive validation. Configure your validator's depth limits (e.g. AJV does not limit recursion depth by default). Enforced by the CLI, see `--override-max-depth`. +- **`const` / `default` / `enum` / `examples` value sizes are bounded recursively, but value nesting depth is a runtime concern.** The meta-schema bounds nested string length (`maxLength`), array length (`maxItems`), and object property counts (`maxProperties`) at every level of a literal value, so an oversized nested string, array, or object is rejected (ASVS 1.3.3). It does NOT cap the nesting *depth* of a literal value: a value nested thousands of levels deep is not given a clean rejection by the meta-schema rule itself (it fails closed via the validator's own recursion limit). Schema-nesting depth is enforced by the CLI, see `--override-max-depth`. - **Min/max logical consistency not enforced.** A schema with `minimum: 100, maximum: 1` (impossible range) will pass validation. This cannot be reliably enforced in JSON Schema alone and would require a wrapper function. Having unit tests for your schema is recommended, this would catch this type of error. Enforced by the CLI. -- **`pattern` regex validation has known gaps.** The check rejects negated character classes `[^...]` as broad denylist matchers (use allowlist patterns like `[\p{L}\p{N}]` instead), blocks nested quantifiers like `(a+)+`, backreferences, identical overlapping quantifiers like `[a-z]+[a-z]+`, semantically identical overlapping quantifiers like `\d+[0-9]+`, and superset overlaps like `\w+\d+` (where `\w` ⊃ `\d`). Bare alternation at the top level (`^a|b$`) is rejected, but alternation across sibling groups (`^(a)|(b)$`) is not detected at the meta-schema level (it is enforced by the CLI). The check cannot detect non-identical overlapping quantifiers (e.g. `[a-z]+\\w+` where `\\w` ⊃ `[a-z]`). Use runtime ReDoS checking for full protection. +- **`pattern` regex validation has known gaps.** The check rejects negated character classes `[^...]` as broad denylist matchers (use allowlist patterns like `[\p{L}\p{N}]` instead), blocks nested quantifiers like `(a+)+`, a bounded quantifier wrapped around a group with an unbounded inner quantifier like `(a+){1,5}`, absurd quantifier upper bounds (5+ digits, e.g. `a{1,1000000}` or `[a-z]{10000}`), backreferences, identical overlapping quantifiers like `[a-z]+[a-z]+`, semantically identical overlapping quantifiers like `\d+[0-9]+`, and superset overlaps like `\w+\d+` (where `\w` ⊃ `\d`). Bare alternation at the top level (`^a|b$`) is rejected, but alternation across sibling groups (`^(a)|(b)$`) is not detected at the meta-schema level (it is enforced by the CLI). The check cannot detect non-identical overlapping quantifiers (e.g. `[a-z]+\\w+` where `\\w` ⊃ `[a-z]`). It also does NOT reject a bounded quantifier wrapped around a group whose inner quantifier is itself bounded with a large product (e.g. `(a{1,1000}){1,1000}`); this nested-bounded-quantifier shape is caught by the CLI runtime (`redos-detector`) but not by the standalone meta-schema. Use runtime ReDoS checking for full protection. - **Remote `$ref` URLs can be SSRF vectors.** The meta-schema restricts `$ref` to `#` (local) or `https://` URLs and blocks private IP ranges (dotted-decimal, hex `0x`, and decimal representations), but DNS-based bypasses (domains resolving to internal IPs) cannot be detected at the schema level. Ensure your validator is configured to disallow or restrict remote schema loading (e.g., use `ajv.addSchema()` instead of allowing external fetches). Dereferencing before running SAST is recommended. Enforced by the CLI. ## Language coverage @@ -187,7 +188,7 @@ All meta-schemas reject keywords not listed in their respective JSON Schema spec | `$defs` | n/a | n/a | n/a | ✓ | ✓ | | | `title`, `description`, `default` | ✓ | ✓ | ✓ | ✓ | ✓ | | | `const` | n/a | ✓ | ✓ | ✓ | ✓ | Type-locked to declared `type` | -| `contains` | n/a | ✓ | ✓ | ✓ | ✓ | Requires `maxContains` + `uniqueItems` | +| `contains` | n/a | ✓ | ✓ | ✓ | ✓ | draft-06/07 require `maxItems` + `uniqueItems`; 2019-09/2020-12 additionally require `maxContains` + `unevaluatedItems` | | `propertyNames` | n/a | ✓ | ✓ | ✓ | ✓ | | | `if`/`then`/`else` | n/a | n/a | ✓ | ✓ | ✓ | | | `contentMediaType`, `contentEncoding` | n/a | n/a | ✓ | ✓ | ✓ | Allow-listed per RFC 6838 / RFC-standard | diff --git a/SECURITY.md b/SECURITY.md index 581de56..2465ffb 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -8,7 +8,7 @@ This document outlines security procedures and general policies for the sast-jso * [Disclosure Policy](#disclosure-policy) ## Security Goals -Our goal is to ensure OSS follows secure design principles and meets security best practices as outlined by the following [OWASP ASVS v5.0 Level 3](https://github.com/OWASP/ASVS/tree/master/5.0/en). +Our goal is to ensure OSS follows secure design principles and meets security best practices as outlined by the following [OWASP ASVS 5.0.0 Level 3](https://github.com/OWASP/ASVS/tree/master/5.0/en). Standards are evaluated using automated scans (Linting, Unit tests, SAST, SCA, DAST, Perf) and manual self-audits. 3rd party audits are welcome. diff --git a/bin/build.js b/bin/build.js index bee0fa3..2c77781 100644 --- a/bin/build.js +++ b/bin/build.js @@ -7,17 +7,14 @@ import { dirname, resolve } from "node:path"; import { fileURLToPath } from "node:url"; const root = resolve(dirname(fileURLToPath(import.meta.url)), ".."); -let defsLib; -try { - defsLib = JSON.parse(readFileSync(`${root}/src/$defs.json`, "utf8")); -} catch (err) { - console.error(`Error loading src/$defs.json: ${err.message}`); - process.exit(1); -} const drafts = ["draft-04", "draft-06", "draft-07", "2019-09", "2020-12"]; -const outputs = []; const REF_PREFIX = "#/$defs/"; +const LOCAL_REF_CONTAINERS = { + "#/$defs/": "$defs", + "#/definitions/": "definitions", +}; + const collectRefs = (node, acc) => { if (Array.isArray(node)) { for (const child of node) collectRefs(child, acc); @@ -33,54 +30,111 @@ const collectRefs = (node, acc) => { } }; -for (const draft of drafts) { - let src; - let manifest; +export const verifyRefs = (schema, file = "schema") => { + const walk = (node) => { + if (Array.isArray(node)) { + for (const child of node) walk(child); + return; + } + if (node === null || typeof node !== "object") return; + for (const [k, v] of Object.entries(node)) { + if (k === "$ref" && typeof v === "string") { + for (const [prefix, container] of Object.entries( + LOCAL_REF_CONTAINERS, + )) { + if (!v.startsWith(prefix)) continue; + const name = v.slice(prefix.length); + const bag = schema[container]; + const resolves = + bag && + typeof bag === "object" && + !Array.isArray(bag) && + Object.hasOwn(bag, name); + if (!resolves) { + throw new Error( + `${file}: dangling $ref "${v}" does not resolve to an entry in "${container}"`, + ); + } + } + } else { + walk(v); + } + } + }; + walk(schema); +}; + +const build = () => { + let defsLib; try { - src = readFileSync(`${root}/src/${draft}.json`, "utf8"); - manifest = JSON.parse(src); + defsLib = JSON.parse(readFileSync(`${root}/src/$defs.json`, "utf8")); } catch (err) { - console.error(`Error loading src/${draft}.json: ${err.message}`); + console.error(`Error loading src/$defs.json: ${err.message}`); process.exit(1); } - const authored = - manifest.$defs && - typeof manifest.$defs === "object" && - !Array.isArray(manifest.$defs) - ? { ...manifest.$defs } - : {}; + const outputs = []; + for (const draft of drafts) { + let src; + let manifest; + try { + src = readFileSync(`${root}/src/${draft}.json`, "utf8"); + manifest = JSON.parse(src); + } catch (err) { + console.error(`Error loading src/${draft}.json: ${err.message}`); + process.exit(1); + } + const authored = + manifest.$defs && + typeof manifest.$defs === "object" && + !Array.isArray(manifest.$defs) + ? { ...manifest.$defs } + : {}; - const needed = new Set(); - collectRefs(manifest, needed); - const seen = new Set(); - let grew = true; - while (grew) { - grew = false; - for (const name of [...needed]) { - if (seen.has(name)) continue; - seen.add(name); - const entry = authored[name] ?? defsLib[name]; - if (entry === undefined) - throw new Error(`${draft}: unknown $defs entry "${name}"`); - const before = needed.size; - collectRefs(entry, needed); - if (needed.size > before) grew = true; + const needed = new Set(); + collectRefs(manifest, needed); + const seen = new Set(); + let grew = true; + while (grew) { + grew = false; + for (const name of [...needed]) { + if (seen.has(name)) continue; + seen.add(name); + const entry = authored[name] ?? defsLib[name]; + if (entry === undefined) + throw new Error(`${draft}: unknown $defs entry "${name}"`); + const before = needed.size; + collectRefs(entry, needed); + if (needed.size > before) grew = true; + } } + const finalDefs = {}; + for (const name of [...needed].sort()) { + finalDefs[name] = authored[name] ?? defsLib[name]; + } + manifest.$defs = finalDefs; + const outPath = `${root}/${draft}.json`; + verifyRefs(manifest, `${draft}.json`); + writeFileSync(outPath, `${JSON.stringify(manifest, null, "\t")}\n`); + outputs.push(outPath); + console.log( + `built ${draft}.json (${Object.keys(finalDefs).length} defs, ${Object.keys(authored).length} local)`, + ); } - const finalDefs = {}; - for (const name of [...needed].sort()) { - finalDefs[name] = authored[name] ?? defsLib[name]; + + try { + execFileSync("npx", ["biome", "format", "--write", ...outputs], { + cwd: root, + stdio: "inherit", + }); + } catch (err) { + console.error(`biome format step failed: ${err.message}`); + throw err; } - manifest.$defs = finalDefs; - const outPath = `${root}/${draft}.json`; - writeFileSync(outPath, `${JSON.stringify(manifest, null, "\t")}\n`); - outputs.push(outPath); - console.log( - `built ${draft}.json (${Object.keys(finalDefs).length} defs, ${Object.keys(authored).length} local)`, - ); -} +}; -execFileSync("npx", ["biome", "format", "--write", ...outputs], { - cwd: root, - stdio: "inherit", -}); +const isEntryPoint = + process.argv[1] && + resolve(process.argv[1]) === fileURLToPath(import.meta.url); +if (isEntryPoint) { + build(); +} diff --git a/cli.js b/cli.js index ba797ce..8c7d570 100755 --- a/cli.js +++ b/cli.js @@ -3,7 +3,7 @@ // SPDX-License-Identifier: MIT import { lookup as dnsLookup } from "node:dns/promises"; import { readFile, stat } from "node:fs/promises"; -import { resolve } from "node:path"; +import { relative, resolve } from "node:path"; import { pathToFileURL } from "node:url"; import { parseArgs } from "node:util"; import Ajv from "ajv/dist/2020.js"; @@ -39,6 +39,10 @@ const builtSchemas = new Map( ["draft-04", schemaDraft04], ].map(([version, metaSchema]) => [ version, + // allErrors:true is required so a single pass surfaces EVERY meta-schema + // violation for the security report; the schemas are trusted, not attacker + // input, so unbounded error allocation is not a DoS vector here. + // nosemgrep: javascript.ajv.security.audit.ajv-allerrors-true.ajv-allerrors-true new Ajv(defaultOptions).compile(metaSchema), ]), ); @@ -57,16 +61,48 @@ const knownSchemaUrls = new Map([ // Maps a user schema's $schema URL to the matching draft version. const schemaVersion = (url) => { if (!url) return DEFAULT_VERSION; + // Stryker disable next-line Regex: dropping the ^/$ anchors only changes the + // result for URLs with an interior "//" or "#", none of which are in the known + // set, so they resolve to the same (unsupported) lookup either way. const normalized = url.replace(/^(?:https?:)?\/\//, "").replace(/#$/, ""); return knownSchemaUrls.get(normalized); }; export const MAX_DEPTH = 32; export const MAX_SCHEMA_SIZE = 64 * 1024 * 1024; // 64 MiB +// Hard cap on the total number of remote $ref/$dynamicRef entries crawlSchema +// will collect into result.refs. The distinct-hostname cap (MAX_SSRF_HOSTNAMES) +// only applies later, after every ref has already been buffered, so a schema +// with a huge number of refs to the same few hosts would still accumulate an +// unbounded array. This is a backstop (overall bounded by MAX_SCHEMA_SIZE); set +// well above what realistic schemas need (multiple refs per distinct host). +export const MAX_COLLECTED_REFS = 4 * MAX_SSRF_HOSTNAMES; // Per-pattern budget for ReDoS analysis. Patterns that exceed this are // fail-closed (reported as unsafe with reason "timedOut") to keep total // scan time bounded on adversarial input. export const REDOS_TIMEOUT_MS = 1_000; +// HEAP CIRCUIT BREAKER: the PRIMARY memory bound for ReDoS analysis. The +// `timeout` option bounds time but NOT memory, and redos-detector's `maxSteps` +// cannot serve as the memory control: a value low enough to bound a catastrophic +// pattern (which retains ~7MB post-GC and ~270MB pre-GC each, so a handful OOM a +// 600MB heap) also wrongly fail-closes legitimate complex-but-safe patterns +// (e.g. semver is reported hitMaxSteps at maxSteps<=250 but is SAFE at the +// library default). So we drop maxSteps and instead read the live heap before +// each pattern: once it has grown beyond this budget above the phase baseline, +// analysis STOPS and one fail-closed (incomplete) finding is emitted. Chosen at +// 128MB: a single catastrophic pattern grows the heap by ~270MB at the default, +// so the breaker fires after the FIRST evil pattern (delta ~270MB > 128MB) and +// before the second, keeping peak well under --max-old-space-size=600; meanwhile +// realistic schemas with many SIMPLE patterns retain almost nothing and never +// approach 128MB. Injectable via crawlSchema options for deterministic testing. +export const REDOS_HEAP_BUDGET_BYTES = 128 * 1024 * 1024; +// Defense in depth: a hard cap on the TOTAL number of regex patterns crawlSchema +// will ReDoS-analyze in a single crawl. The heap circuit breaker above is the +// primary memory control; this is an independent backstop against an adversary +// supplying a huge number of patterns. Set well above what realistic schemas +// need (they can legitimately carry hundreds of simple patterns, which are +// cheap). +export const MAX_REDOS_PATTERNS = 256; export const ANALYSIS_TIMEOUT_MS = 60_000; // Property names that act as deserialization / type-confusion vectors in @@ -276,6 +312,9 @@ export const sast = (schema) => { const version = schemaVersion(schema?.$schema); const validate = builtSchemas.get(version); if (!validate) { + // Stryker disable next-line OptionalChaining: reaching this throw requires a + // non-null schema object (a null schema resolves to the default and validates), + // so schema?.$schema and schema.$schema are equivalent here. throw new Error(`Unsupported $schema: ${schema?.$schema}`); } return validate; @@ -286,21 +325,17 @@ export default sast; // Checks whether a numeric schema's min/max bounds describe an impossible // range. Returns an AJV-style error object when they do, or null otherwise. const checkNumericRange = (current, path) => { + // Number.isFinite is true only for an actual finite number, so it already + // implies `typeof === "number"`; no separate type check is needed. const hasMin = - Object.hasOwn(current, "minimum") && - typeof current.minimum === "number" && - Number.isFinite(current.minimum); + Object.hasOwn(current, "minimum") && Number.isFinite(current.minimum); const hasExMin = Object.hasOwn(current, "exclusiveMinimum") && - typeof current.exclusiveMinimum === "number" && Number.isFinite(current.exclusiveMinimum); const hasMax = - Object.hasOwn(current, "maximum") && - typeof current.maximum === "number" && - Number.isFinite(current.maximum); + Object.hasOwn(current, "maximum") && Number.isFinite(current.maximum); const hasExMax = Object.hasOwn(current, "exclusiveMaximum") && - typeof current.exclusiveMaximum === "number" && Number.isFinite(current.exclusiveMaximum); if (!(hasMin || hasExMin) || !(hasMax || hasExMax)) return null; @@ -397,16 +432,95 @@ export const crawlSchema = (obj, maxDepth = MAX_DEPTH, options = {}) => { result.depth = 1; const stack = [[obj, "", 1]]; - while (stack.length > 0) { - if (deadline != null && Date.now() > deadline) { + // Defense in depth: total number of regex patterns ReDoS-analyzed so far. + // Shared across top-level `pattern` and patternProperties keys. Once it + // exceeds MAX_REDOS_PATTERNS, no further pattern is analyzed and a single + // fail-closed budget finding is emitted (see redosBudgetExceeded). + let redosPatternCount = 0; + let redosBudgetReported = false; + // One-time flag: have we already recorded the collected-refs truncation + // finding? (See MAX_COLLECTED_REFS in the $ref/$dynamicRef collection below.) + let refsTruncated = false; + // Pushes the timeout finding and flags the result as timed out. Used both at + // the top of the stack loop and before each individual ReDoS analysis (the + // per-pattern loops can run many isSafePattern() calls in one stack frame, so + // the once-per-pop check is not enough on adversarial input). + const timeoutBail = () => { + result.errors.push({ + instancePath: "", + schemaPath: "#/timeout", + keyword: "timeout", + params: {}, + message: "schema analysis exceeded time budget", + }); + result.timedOut = true; + }; + // True when a deadline is configured and has passed. + // Stryker disable next-line ConditionalExpression,EqualityOperator: a missing + // deadline makes Date.now() > undefined false anyway (so forcing the guard true + // is equivalent), and Date.now() is never exactly the deadline, so > vs >= + // cannot differ. Timing-only boundary. + const deadlinePassed = () => deadline != null && Date.now() > deadline; + + // Returns true (and emits one #/redos-budget finding the first time) when the + // total-pattern cap has been exceeded, so callers can skip further analysis. + // Marked incomplete:true so --ignore cannot suppress it (analysis stopped). + const redosBudgetExceeded = (path) => { + if (redosPatternCount <= MAX_REDOS_PATTERNS) return false; + if (!redosBudgetReported) { + redosBudgetReported = true; result.errors.push({ - instancePath: "", - schemaPath: "#/timeout", - keyword: "timeout", - params: {}, - message: "schema analysis exceeded time budget", + instancePath: path, + schemaPath: "#/redos-budget", + keyword: "pattern", + params: { limit: MAX_REDOS_PATTERNS, incomplete: true }, + message: `refusing to ReDoS-analyze more than ${MAX_REDOS_PATTERNS} patterns; remaining patterns not analyzed`, + }); + } + return true; + }; + + // HEAP CIRCUIT BREAKER (primary memory bound). Reads live heap usage before + // each pattern; once it has grown beyond redosHeapBudgetBytes above the + // baseline captured at the first pattern, analysis stops and one fail-closed + // finding is emitted. Marked incomplete:true so --ignore cannot suppress it. + // Injectable: options.memoryUsage / options.redosHeapBudgetBytes for tests. + const memoryUsage = + typeof options.memoryUsage === "function" + ? options.memoryUsage + : () => process.memoryUsage().heapUsed; + const redosHeapBudgetBytes = + // Stryker disable next-line ConditionalExpression: when absent the default + // const is used; any test exercising the override passes it explicitly. + options.redosHeapBudgetBytes != null + ? options.redosHeapBudgetBytes + : REDOS_HEAP_BUDGET_BYTES; + let redosHeapBaseline = null; + let redosHeapReported = false; + // Returns true (and emits one #/redos-budget heap finding the first time) when + // the heap has grown more than the budget above the baseline. The first call + // captures the baseline (delta 0, never trips), so the breaker only fires once + // a real allocation has crossed the budget. + const redosHeapExceeded = (path) => { + const current = memoryUsage(); + if (redosHeapBaseline === null) redosHeapBaseline = current; + if (current - redosHeapBaseline <= redosHeapBudgetBytes) return false; + if (!redosHeapReported) { + redosHeapReported = true; + result.errors.push({ + instancePath: path, + schemaPath: "#/redos-budget", + keyword: "heap", + params: { budget: redosHeapBudgetBytes, incomplete: true }, + message: `ReDoS analysis heap budget of ${redosHeapBudgetBytes} bytes exceeded; remaining patterns not analyzed`, }); - result.timedOut = true; + } + return true; + }; + + while (stack.length > 0) { + if (deadlinePassed()) { + timeoutBail(); return result; } @@ -498,37 +612,73 @@ export const crawlSchema = (obj, maxDepth = MAX_DEPTH, options = {}) => { Object.hasOwn(current, "pattern") && typeof current.pattern === "string" ) { - try { - const patternResult = isSafePattern(current.pattern, { - timeout: REDOS_TIMEOUT_MS, - }); - if (!patternResult.safe) { - const reason = patternResult.error ?? "hitMaxScore"; - const message = - reason === "timedOut" - ? `pattern analysis timed out after ${REDOS_TIMEOUT_MS}ms (fail-closed as ReDoS)` - : reason === "hitMaxSteps" - ? "pattern analysis exceeded step limit (fail-closed as ReDoS)" - : "pattern is vulnerable to ReDoS"; + // Check the deadline BEFORE the (potentially expensive) analysis: the + // once-per-pop check above is not enough when one stack frame holds many + // patterns. Bail to the timeout path on an expired deadline. + // Stryker disable next-line ConditionalExpression,BlockStatement: this guard + // only fires when a REAL isSafePattern() call between the once-per-pop check + // and here is slow; time cannot deterministically pass in a fast test, so the + // block is NoCoverage and the conditional is equivalent (timing-only). The + // original deadline check carried the same disable before deadlinePassed(). + if (deadlinePassed()) { + timeoutBail(); + return result; + } + redosPatternCount++; + // Skip analysis (of this and every later pattern) once a backstop trips: + // the total-pattern cap or the heap circuit breaker (primary memory bound). + if ( + !redosBudgetExceeded(`${path}/pattern`) && + !redosHeapExceeded(`${path}/pattern`) + ) { + try { + // The timeout bounds analysis TIME; the heap breaker above bounds + // MEMORY. No maxSteps: it would fail-close legitimate safe patterns. + // Stryker disable next-line ObjectLiteral: the timeout option bounds + // analysis TIME only; for any pattern fast enough for a test the + // safe/unsafe verdict is identical with or without it, so dropping it + // (-> {}) is an equivalent (timing-only) mutant. + const patternResult = isSafePattern(current.pattern, { + timeout: REDOS_TIMEOUT_MS, + }); + if (!patternResult.safe) { + // Stryker disable next-line LogicalOperator,StringLiteral: redos-detector + // always reports error:"hitMaxScore" for these, so the ?? fallback is + // defensive dead-weight here. + const reason = patternResult.error ?? "hitMaxScore"; + // timedOut/hitMaxSteps reasons only arise on a real library timeout, + // which cannot be triggered deterministically in a fast test. + // Stryker disable ConditionalExpression,StringLiteral + const message = + reason === "timedOut" + ? `pattern analysis timed out after ${REDOS_TIMEOUT_MS}ms (fail-closed as ReDoS)` + : reason === "hitMaxSteps" + ? "pattern analysis exceeded step limit (fail-closed as ReDoS)" + : "pattern is vulnerable to ReDoS"; + // Stryker restore ConditionalExpression,StringLiteral + result.errors.push({ + instancePath: `${path}/pattern`, + schemaPath: "#/redos", + keyword: "pattern", + params: { pattern: current.pattern, reason }, + message, + }); + } + } catch { result.errors.push({ instancePath: `${path}/pattern`, schemaPath: "#/redos", keyword: "pattern", - params: { pattern: current.pattern, reason }, - message, + params: { pattern: current.pattern, reason: "parseError" }, + message: "pattern could not be parsed for ReDoS analysis", }); } - } catch { - result.errors.push({ - instancePath: `${path}/pattern`, - schemaPath: "#/redos", - keyword: "pattern", - params: { pattern: current.pattern, reason: "parseError" }, - message: "pattern could not be parsed for ReDoS analysis", - }); } } + // Stryker disable next-line ConditionalExpression,EqualityOperator: this only + // skips the dangerous-name loops when the denylist is empty; entering with an + // empty denySet matches nothing, so it is a pure (equivalent) optimization. if (denylist.length > 0) { for (const siteKey of [ "properties", @@ -554,8 +704,9 @@ export const crawlSchema = (obj, maxDepth = MAX_DEPTH, options = {}) => { } if (Array.isArray(current.required)) { - for (let i = 0; i < current.required.length; i++) { - const name = current.required[i]; + for (const [i, name] of current.required.entries()) { + // Stryker disable next-line ConditionalExpression: denySet only holds + // strings, so denySet.has(non-string) is already false. if (typeof name === "string" && denySet.has(name)) { result.errors.push({ instancePath: `${path}/required/${i}`, @@ -576,9 +727,12 @@ export const crawlSchema = (obj, maxDepth = MAX_DEPTH, options = {}) => { for (const [trigger, deps] of Object.entries( current.dependentRequired, )) { + // Stryker disable next-line ConditionalExpression: a non-array deps has + // no length, so the loop below simply never runs. if (Array.isArray(deps)) { - for (let i = 0; i < deps.length; i++) { - const name = deps[i]; + for (const [i, name] of deps.entries()) { + // Stryker disable next-line ConditionalExpression,LogicalOperator: denySet + // only holds strings, so has(non-string) is already false. if (typeof name === "string" && denySet.has(name)) { result.errors.push({ instancePath: `${path}/dependentRequired/${escapeJsonPointer(trigger)}/${i}`, @@ -592,73 +746,123 @@ export const crawlSchema = (obj, maxDepth = MAX_DEPTH, options = {}) => { } } } + } - if ( - typeof current.patternProperties === "object" && - current.patternProperties !== null && - !Array.isArray(current.patternProperties) - ) { - for (const patternKey of Object.keys(current.patternProperties)) { - let patternSafe = true; - try { - const patternResult = isSafePattern(patternKey, { - timeout: REDOS_TIMEOUT_MS, + // ReDoS scanning of patternProperties keys is independent of the + // dangerous-name denylist, so it runs unconditionally; the dangerous-name + // match below self-gates (filtering an empty denylist yields no matches). + if ( + typeof current.patternProperties === "object" && + current.patternProperties !== null && + !Array.isArray(current.patternProperties) + ) { + for (const patternKey of Object.keys(current.patternProperties)) { + const keyPath = `${path}/patternProperties/${escapeJsonPointer(patternKey)}`; + // Check the deadline before each key: a single object can carry many + // patternProperties keys, all analyzed in ONE stack frame, so the + // once-per-pop check above never fires between them. + // Stryker disable next-line ConditionalExpression,BlockStatement: fires only + // when a REAL per-key isSafePattern() call is slow; time cannot pass between + // the once-per-pop check and here in a fast test, so the block is NoCoverage + // and the conditional is equivalent (timing-only), matching the convention. + if (deadlinePassed()) { + timeoutBail(); + return result; + } + redosPatternCount++; + // Stop analyzing further patterns once a backstop trips: the + // total-pattern cap or the heap circuit breaker (primary memory bound). + if (redosBudgetExceeded(keyPath) || redosHeapExceeded(keyPath)) break; + let patternSafe = true; + try { + // The timeout bounds analysis TIME; the heap breaker above bounds + // MEMORY. No maxSteps: it would fail-close legitimate safe patterns. + // Stryker disable next-line ObjectLiteral: the timeout option bounds + // analysis TIME only; for any key fast enough for a test the verdict + // is identical with or without it, so dropping it is timing-equivalent. + const patternResult = isSafePattern(patternKey, { + timeout: REDOS_TIMEOUT_MS, + }); + if (!patternResult.safe) { + patternSafe = false; + result.errors.push({ + instancePath: keyPath, + schemaPath: "#/redos", + keyword: "patternProperties", + params: { + pattern: patternKey, + // Stryker disable next-line LogicalOperator,StringLiteral: redos-detector + // always reports error:"hitMaxScore"; the ?? fallback is dead-weight. + reason: patternResult.error ?? "hitMaxScore", + }, + message: `patternProperties key "${patternKey}" is vulnerable to ReDoS`, + }); + } + } catch {} + if (!patternSafe) continue; + try { + // nosemgrep: javascript.lang.security.audit.detect-non-literal-regexp.detect-non-literal-regexp + const re = new RegExp(patternKey); + const matches = denylist.filter((n) => re.test(n)); + if (matches.length > 0) { + result.errors.push({ + instancePath: keyPath, + schemaPath: "#/dangerous-name", + keyword: "patternProperties", + params: { + pattern: patternKey, + matches, + lang: options.lang ?? DEFAULT_LANG, + }, + message: `patternProperties key "${patternKey}" matches deserialization vector(s) for lang="${options.lang ?? DEFAULT_LANG}": ${matches.join(", ")}`, }); - if (!patternResult.safe) { - patternSafe = false; - result.errors.push({ - instancePath: `${path}/patternProperties/${escapeJsonPointer(patternKey)}`, - schemaPath: "#/redos", - keyword: "patternProperties", - params: { - pattern: patternKey, - reason: patternResult.error ?? "hitMaxScore", - }, - message: `patternProperties key "${patternKey}" is vulnerable to ReDoS`, - }); - } - } catch {} - if (!patternSafe) continue; - try { - // nosemgrep: javascript.lang.security.audit.detect-non-literal-regexp.detect-non-literal-regexp - const re = new RegExp(patternKey); - const matches = denylist.filter((n) => re.test(n)); - if (matches.length > 0) { - result.errors.push({ - instancePath: `${path}/patternProperties/${escapeJsonPointer(patternKey)}`, - schemaPath: "#/dangerous-name", - keyword: "patternProperties", - params: { - pattern: patternKey, - matches, - lang: options.lang ?? DEFAULT_LANG, - }, - message: `patternProperties key "${patternKey}" matches deserialization vector(s) for lang="${options.lang ?? DEFAULT_LANG}": ${matches.join(", ")}`, - }); - } - } catch { - // unparseable regex; meta-schema safePattern rejects it } + } catch { + // unparseable regex; meta-schema safePattern rejects it } } } - if ( - Object.hasOwn(current, "$ref") && - typeof current.$ref === "string" && - !current.$ref.startsWith("#") - ) { - try { - const url = new URL(current.$ref); - if (url.hostname) { - result.refs.push({ - hostname: url.hostname, - ref: current.$ref, - path: `${path}/$ref`, - }); + // Collect remote $ref and $dynamicRef (2020-12) URLs as SSRF fetch targets. + // $id is deliberately NOT collected: it declares a base URI identifier, not + // a fetch target, and the -r/--ref-schema-files flag uses $id hostnames as + // the SAFE list, so flagging $id would self-flag the user's own schema. + for (const refKey of ["$ref", "$dynamicRef"]) { + const refValue = current[refKey]; + if ( + Object.hasOwn(current, refKey) && + typeof refValue === "string" && + !refValue.startsWith("#") + ) { + try { + const url = new URL(refValue); + if (url.hostname) { + // Backstop: stop buffering once the collected-refs cap is reached, + // recording one truncation finding. Without this, a schema with a + // huge number of refs could accumulate an unbounded array before + // the later distinct-hostname cap ever applies. + if (result.refs.length >= MAX_COLLECTED_REFS) { + if (!refsTruncated) { + refsTruncated = true; + result.errors.push({ + instancePath: `${path}/${refKey}`, + schemaPath: "#/refs-truncated", + keyword: "$ref", + params: { limit: MAX_COLLECTED_REFS, incomplete: true }, + message: `more than ${MAX_COLLECTED_REFS} remote $ref(s); remaining refs not collected for SSRF analysis`, + }); + } + } else { + result.refs.push({ + hostname: url.hostname, + ref: refValue, + path: `${path}/${refKey}`, + }); + } + } + } catch { + // not a valid URL, skip } - } catch { - // not a valid URL, skip } } @@ -672,6 +876,8 @@ export const crawlSchema = (obj, maxDepth = MAX_DEPTH, options = {}) => { ) { visited.add(value); const newDepth = currentDepth + 1; + // Stryker disable next-line ConditionalExpression,EqualityOperator: this only + // tracks the max depth seen; > vs >= and always-assign reach the same maximum. if (newDepth > result.depth) result.depth = newDepth; if (result.depth > maxDepth) { result.depthExceeded = true; @@ -687,6 +893,10 @@ export const crawlSchema = (obj, maxDepth = MAX_DEPTH, options = {}) => { }; // RFC 1918 + loopback + link-local + CGN + TEST-NETs + multicast + reserved. +// IPv6 covered: :: and ::1, unique-local fc00::/7, link-local fe80::/10 and +// site-local fec0::/10 (combined fe80-feff), multicast ff00::/8, IPv4-mapped +// ::ffff:0:0/96, NAT64 64:ff9b::/96, 6to4 2002::/16, and documentation +// 2001:db8::/32. NAT64/6to4/IPv4-mapped recurse on their embedded IPv4. // Used to block $ref URLs whose hostname resolves to an internal/private IP. // Fail-closed: malformed IPv6 (e.g. invalid hex groups, wrong group count) is // treated as private (returns true) so it is blocked rather than allowed @@ -711,6 +921,9 @@ export const isPrivateIP = (ip) => { if (a === 198 && (b === 18 || b === 19)) return true; // 198.18.0.0/15 benchmark if (a === 198 && b === 51 && parts[2] === 100) return true; // 198.51.100.0/24 TEST-NET-2 if (a === 203 && b === 0 && parts[2] === 113) return true; // 203.0.113.0/24 TEST-NET-3 + // Stryker disable next-line ConditionalExpression: octets are validated to + // 0-255 and 240-255 is already private via the next line, so the `a <= 239` + // bound is redundant; dropping it is an equivalent mutant (no input changes). if (a >= 224 && a <= 239) return true; // 224.0.0.0/4 multicast if (a >= 240) return true; // 240.0.0.0/4 reserved + 255.255.255.255 broadcast } @@ -748,18 +961,44 @@ export const isPrivateIP = (ip) => { const normalized = groups.join(":"); if (normalized === "0:0:0:0:0:0:0:0" || normalized === "0:0:0:0:0:0:0:1") return true; - if (groups[0].startsWith("fc") || groups[0].startsWith("fd")) return true; // unique local - if (groups[0].startsWith("fe80")) return true; // link-local - if (groups[0].startsWith("ff")) return true; // multicast + // Decode two normalized hex groups into a dotted IPv4 and recurse. + // Fail-closed: invalid hex parses as NaN, and NaN bit-math would forge a + // public-looking IPv4. Block (return true) instead. + const embeddedIPv4Private = (hiGroup, loGroup) => { + const hi = Number.parseInt(hiGroup, 16); + const lo = Number.parseInt(loGroup, 16); + if (Number.isNaN(hi) || Number.isNaN(lo)) return true; + return isPrivateIP(`${hi >> 8}.${hi & 0xff}.${lo >> 8}.${lo & 0xff}`); + }; + if (groups[0].startsWith("fc") || groups[0].startsWith("fd")) return true; // unique local fc00::/7 + if (groups[0].startsWith("ff")) return true; // multicast ff00::/8 + // First-group numeric ranges. Fail-closed on NaN (malformed hex) and on + // values > 0xffff (an over-long group such as "fe800" is malformed IPv6). + const g0 = Number.parseInt(groups[0], 16); + // Stryker disable next-line EqualityOperator: 0xffff (and above) starts with + // "ff" and is already returned by the multicast check, so the boundary value + // is unreachable here; `>` vs `>=` is an equivalent mutant. + if (Number.isNaN(g0) || g0 > 0xffff) return true; + // fe80-feff covers link-local fe80::/10 (fe80-febf) and site-local + // fec0::/10 (fec0-feff); the old check only matched the literal "fe80". + // Stryker disable next-line ConditionalExpression,EqualityOperator: the "ff" + // startsWith and `g0 > 0xffff` returns above already exclude every g0 above + // 0xfeff, so the upper-bound conjunct is always true here; dropping or + // loosening it is an equivalent mutant (no reachable input changes). + if (g0 >= 0xfe80 && g0 <= 0xfeff) return true; + // 2002::/16 6to4: embedded IPv4 sits in groups 1 and 2. + if (groups[0] === "2002") + return embeddedIPv4Private(groups[1], groups[2]); + // 2001:db8::/32 documentation (the IPv6 analog of the IPv4 TEST-NETs). + if (groups[0] === "2001" && groups[1] === "db8") return true; + // 64:ff9b::/96 NAT64 well-known prefix (RFC 6052): normalized form is + // 64:ff9b:0:0:0:0:X:Y with the IPv4 embedded in the last two groups. + if (normalized.startsWith("64:ff9b:0:0:0:0:")) { + return embeddedIPv4Private(groups[6], groups[7]); + } // IPv4-mapped with hex groups (e.g. 0:0:0:0:0:ffff:7f00:1) if (normalized.startsWith("0:0:0:0:0:ffff:")) { - const hi = Number.parseInt(groups[6], 16); - const lo = Number.parseInt(groups[7], 16); - // Fail-closed: invalid hex parses as NaN, and NaN bit-math would - // forge 0.0.0.0-style public-looking IPv4. Block instead. - if (Number.isNaN(hi) || Number.isNaN(lo)) return true; - const mappedIP = `${hi >> 8}.${hi & 0xff}.${lo >> 8}.${lo & 0xff}`; - return isPrivateIP(mappedIP); + return embeddedIPv4Private(groups[6], groups[7]); } } } @@ -793,6 +1032,8 @@ const lookupHostname = async (hostname, entries, timeoutMs) => { }; export const resolveSSRFRefs = async (refs, options = {}) => { + // Stryker disable next-line LogicalOperator: ?? vs && only changes the DNS + // timeout magnitude, never the resolve/abort outcome; equivalent mutant. const timeoutMs = options.dnsTimeoutMs ?? DNS_TIMEOUT_MS; const concurrency = options.dnsConcurrency ?? DNS_CONCURRENCY; const safeHostnames = options.safeHostnames ?? new Set(); @@ -812,13 +1053,23 @@ export const resolveSSRFRefs = async (refs, options = {}) => { instancePath: "", schemaPath: "#/ssrf", keyword: "ssrf", - params: { hostnames: hostnameMap.size, limit: maxHostnames }, + // `incomplete: true` marks this as an INCOMPLETE-analysis finding: DNS + // was entirely skipped, so analyze() must never let --ignore drop it + // (same protection depth/timeout get). The normal per-host findings + // below deliberately omit this marker and stay ignorable. + params: { + hostnames: hostnameMap.size, + limit: maxHostnames, + incomplete: true, + }, message: `too many distinct remote $ref hostnames (${hostnameMap.size}); refusing SSRF DNS resolution above ${maxHostnames}`, }, ]; } const totalMs = + // Stryker disable next-line ConditionalExpression: with the option absent the + // default branch and Number(undefined)=NaN both yield "never time out". options.dnsTotalTimeoutMs != null ? Number(options.dnsTotalTimeoutMs) : DNS_TOTAL_TIMEOUT_MS; @@ -826,8 +1077,14 @@ export const resolveSSRFRefs = async (refs, options = {}) => { const results = []; const batches = [...hostnameMap.entries()]; + // Stryker disable next-line EqualityOperator: < vs <= only adds one empty + // trailing batch (slice past the end), so it is an equivalent mutant. for (let i = 0; i < batches.length; i += concurrency) { + // Stryker disable next-line EqualityOperator: Date.now() is never exactly the + // deadline, so > vs >= cannot differ here. if (Date.now() > overallDeadline) { + // Stryker disable next-line MethodExpression: the budget is only ever hit + // at i=0 in tests, where slice(i) === the whole array. for (const [hostname, entries] of batches.slice(i)) { for (const { ref, path } of entries) { results.push([ @@ -835,7 +1092,10 @@ export const resolveSSRFRefs = async (refs, options = {}) => { instancePath: path, schemaPath: "#/ssrf", keyword: "ssrf", - params: { ref, hostname }, + // Incomplete-analysis marker: this host's DNS was skipped because + // the total budget was exhausted, so analyze() must not let + // --ignore drop it (see applyIgnore). + params: { ref, hostname, incomplete: true }, message: `$ref hostname "${hostname}" not checked: SSRF DNS budget exceeded`, }, ]); @@ -856,6 +1116,8 @@ export const resolveSSRFRefs = async (refs, options = {}) => { export const resolveInstancePath = (obj, pointer) => { if (typeof obj !== "object" || obj === null) return undefined; + // Stryker disable next-line ConditionalExpression: an empty pointer also yields + // zero parts below, so returning obj early vs falling through is equivalent. if (!pointer) return obj; const parts = pointer .split("/") @@ -881,19 +1143,19 @@ export const resolveInstancePath = (obj, pointer) => { export const analyze = async (schema, options = {}) => { if (options.overrideMaxDepth != null) { const n = Number(options.overrideMaxDepth); - if (!Number.isFinite(n) || n < 0 || !Number.isInteger(n)) { + if (n < 0 || !Number.isInteger(n)) { throw new TypeError("overrideMaxDepth must be a non-negative integer"); } } if (options.overrideMaxItems != null) { const n = Number(options.overrideMaxItems); - if (!Number.isFinite(n) || n < 0 || !Number.isInteger(n)) { + if (n < 0 || !Number.isInteger(n)) { throw new TypeError("overrideMaxItems must be a non-negative integer"); } } if (options.overrideMaxProperties != null) { const n = Number(options.overrideMaxProperties); - if (!Number.isFinite(n) || n < 0 || !Number.isInteger(n)) { + if (n < 0 || !Number.isInteger(n)) { throw new TypeError( "overrideMaxProperties must be a non-negative integer", ); @@ -901,42 +1163,56 @@ export const analyze = async (schema, options = {}) => { } if (options.maxSchemaSize != null) { const n = Number(options.maxSchemaSize); - if (!Number.isFinite(n) || n < 0 || !Number.isInteger(n)) { + if (n < 0 || !Number.isInteger(n)) { throw new TypeError("maxSchemaSize must be a non-negative integer"); } } if (options.analysisTimeoutMs != null) { const n = Number(options.analysisTimeoutMs); - if (!Number.isFinite(n) || n < 0 || !Number.isInteger(n)) { + if (n < 0 || !Number.isInteger(n)) { throw new TypeError("analysisTimeoutMs must be a non-negative integer"); } } if (options.maxHostnames != null) { const n = Number(options.maxHostnames); - if (!Number.isFinite(n) || n < 0 || !Number.isInteger(n)) { + if (n < 0 || !Number.isInteger(n)) { throw new TypeError("maxHostnames must be a non-negative integer"); } } if (options.dnsTotalTimeoutMs != null) { const n = Number(options.dnsTotalTimeoutMs); - if (!Number.isFinite(n) || n < 0 || !Number.isInteger(n)) { + if (n < 0 || !Number.isInteger(n)) { throw new TypeError("dnsTotalTimeoutMs must be a non-negative integer"); } } const applyIgnore = (errs) => { + // Stryker disable next-line ConditionalExpression,LogicalOperator: the + // length checks are short-circuit guards; filtering with an empty/no ignore + // set is a no-op, so dropping them yields the same returned array. if (Array.isArray(options.ignore) && options.ignore.length && errs.length) { const ignore = new Set(options.ignore); return errs.filter( (err) => - !ignore.has(err.instancePath) && - !ignore.has(`${err.instancePath}:${err.keyword}`), + // Findings marked incomplete (SSRF hostname-cap / DNS-budget) mean + // analysis was NOT completed, so they are never suppressible by + // --ignore, exactly like the depth/timeout findings. + // Stryker disable next-line OptionalChaining: every finding that reaches + // applyIgnore (AJV errors, crawl findings, SSRF findings) carries a + // `params` object, so `?.` and a plain access are equivalent here; the + // guard is defensive against a hypothetical param-less finding only. + err.params?.incomplete === true || + (!ignore.has(err.instancePath) && + !ignore.has(`${err.instancePath}:${err.keyword}`)), ); } return errs; }; const sizeLimit = + // Stryker disable next-line ConditionalExpression: when absent, Number(undefined) + // = NaN, and `bytes > NaN` is false, matching the MAX_SCHEMA_SIZE default for any + // schema small enough to test. options.maxSchemaSize != null ? Number(options.maxSchemaSize) : MAX_SCHEMA_SIZE; @@ -949,6 +1225,9 @@ export const analyze = async (schema, options = {}) => { ); } if ( + // Stryker disable next-line ConditionalExpression: JSON.stringify only returns a + // non-string (undefined) for undefined/function input, which a parsed schema + // never is, so this defensive check is always true here. typeof serialized === "string" && Buffer.byteLength(serialized) > sizeLimit ) { @@ -962,12 +1241,15 @@ export const analyze = async (schema, options = {}) => { resolveDangerousNames(options.lang); // throws on unknown lang - let deadline; + // Default budget first, then narrow it if the caller set one (avoids an else + // branch whose only job is the default). + let deadline = Date.now() + ANALYSIS_TIMEOUT_MS; + // Stryker disable next-line ConditionalExpression: without the option, + // Number(undefined)=NaN gives a NaN deadline that never fires, the same + // observable result (no timeout) as the default budget within a fast test. if (options.analysisTimeoutMs != null) { const ms = Number(options.analysisTimeoutMs); deadline = ms <= 0 ? 0 : Date.now() + ms; - } else { - deadline = Date.now() + ANALYSIS_TIMEOUT_MS; } const crawl = crawlSchema(schema, maxDepth, { lang: options.lang, deadline }); @@ -999,6 +1281,12 @@ export const analyze = async (schema, options = {}) => { errors.push(...crawl.errors); if (!options.offline) { + // Notify the caller (e.g. run(), to print a STDERR notice) of the remote + // refs about to be DNS-resolved, BEFORE any lookup happens. Opt-in: absent + // callback means no-op, keeping analyze() pure for library consumers. + // Stryker disable next-line OptionalChaining: with no callback the ?. and a + // plain call are equivalent (no observable effect) for library callers. + options.onRemoteRefs?.(crawl.refs); const ssrfErrors = await resolveSSRFRefs(crawl.refs, { dnsTimeoutMs: options.dnsTimeoutMs, dnsConcurrency: options.dnsConcurrency, @@ -1012,6 +1300,9 @@ export const analyze = async (schema, options = {}) => { if (options.overrideMaxItems != null && errors.length) { const limit = Number(options.overrideMaxItems); errors = errors.filter((err) => { + // Stryker disable next-line ConditionalExpression: treating a non-maxItems + // error as maxItems still resolves a non-array instance, so !Array.isArray + // keeps it — the same outcome as the `return true` fall-through. if (err.schemaPath === SCHEMA_PATH_MAX_ITEMS) { const arr = resolveInstancePath(schema, err.instancePath); return !Array.isArray(arr) || arr.length > limit; @@ -1022,10 +1313,16 @@ export const analyze = async (schema, options = {}) => { if (options.overrideMaxProperties != null && errors.length) { const limit = Number(options.overrideMaxProperties); errors = errors.filter((err) => { + // Stryker disable next-line ConditionalExpression: as above, a non-target + // error resolves to a non-object instance and is kept either way. if (err.schemaPath === SCHEMA_PATH_MAX_PROPERTIES) { const obj = resolveInstancePath(schema, err.instancePath); + // a real maxProperties finding always resolves to a non-null object, so + // these two defensive guards are false and the length check decides. return ( + // Stryker disable next-line ConditionalExpression,LogicalOperator typeof obj !== "object" || + // Stryker disable next-line ConditionalExpression obj === null || Object.keys(obj).length > limit ); @@ -1040,8 +1337,19 @@ export const analyze = async (schema, options = {}) => { // code-scanning, SonarQube, and other security pipelines that consume SARIF. // instancePath is encoded as logicalLocations.fullyQualifiedName (JSON Pointer) // since SARIF doesn't natively model JSON-pointer regions. -export const formatSarif = (errors, inputPath) => { - const inputUri = pathToFileURL(resolve(inputPath)).href; +export const formatSarif = (errors, inputPath, cwd = process.cwd()) => { + // GitHub code scanning / SonarQube match results to repo files via a path + // RELATIVE to the repo root, so prefer a relative uri + SRCROOT uriBaseId + // (resolvable via originalUriBaseIds). Only fall back to an absolute file:// + // uri when the input lives OUTSIDE cwd (relative would escape with ".."). + // Stryker disable next-line StringLiteral: relative() only yields backslash + // separators on Windows; on the POSIX test platform the input never contains a + // "\\", so this replacement is a no-op and the mutant is equivalent there. + const relPath = relative(cwd, resolve(inputPath)).replaceAll("\\", "/"); + const insideCwd = relPath !== "" && !relPath.startsWith("../"); + const artifactLocation = insideCwd + ? { uri: relPath, uriBaseId: "SRCROOT" } + : { uri: pathToFileURL(resolve(inputPath)).href }; const ruleMap = new Map(); for (const err of errors) { const ruleId = err.schemaPath @@ -1071,6 +1379,13 @@ export const formatSarif = (errors, inputPath) => { rules: [...ruleMap.values()], }, }, + ...(insideCwd + ? { + originalUriBaseIds: { + SRCROOT: { uri: pathToFileURL(`${resolve(cwd)}/`).href }, + }, + } + : {}), results: errors.map((err) => { const ruleId = err.schemaPath ? err.schemaPath.replace(/^#\//, "").split("/")[0] || err.keyword @@ -1082,7 +1397,7 @@ export const formatSarif = (errors, inputPath) => { locations: [ { physicalLocation: { - artifactLocation: { uri: inputUri }, + artifactLocation, }, logicalLocations: [ { @@ -1106,16 +1421,40 @@ export const formatSarif = (errors, inputPath) => { }; // --- CLI entrypoint --- -if (process.argv[1] && resolve(process.argv[1]) === import.meta.filename) { + +// Thrown by `die` to unwind to run()'s handler with an exit code, instead of +// calling process.exit (which would make the entrypoint untestable in-process). +class CliExit extends Error { + constructor(code) { + // Stryker disable next-line StringLiteral: this sentinel's message is never + // read (only .code is), so its exact text is unobservable. + super(`cli exit ${code}`); + this.code = code; + } +} + +// Parses argv, reads the schema file, runs analyze(), and writes the report. +// Returns the exit code (0 = no issues, 1 = issues, 2 = usage/tool error). All +// I/O is injectable via `io` ({ log, error, write, readFile, stat }) so the whole +// entrypoint is unit-testable without spawning a subprocess. +export const run = async (argv, io = {}) => { + const log = io.log ?? ((m) => console.log(m)); + const errorLog = io.error ?? ((m) => console.error(m)); + const write = io.write ?? ((s) => process.stdout.write(s)); + const readFileFn = io.readFile ?? readFile; + const statFn = io.stat ?? stat; + const die = (msg) => { - console.error(`Error: ${msg}`); - process.exit(2); + errorLog(`Error: ${msg}`); + throw new CliExit(2); }; const readJsonFile = async (filePath, label) => { let content; try { - content = await readFile(filePath, "utf8"); + // Stryker disable next-line StringLiteral: JSON.parse coerces a Buffer the + // same as a string, so the "utf8" encoding hint is not observable here. + content = await readFileFn(filePath, "utf8"); } catch (err) { die(`cannot read ${label}: ${err.message}`); } @@ -1126,34 +1465,36 @@ if (process.argv[1] && resolve(process.argv[1]) === import.meta.filename) { } }; - let values; - let positionals; try { - ({ values, positionals } = parseArgs({ - allowPositionals: true, - options: { - "override-max-items": { type: "string" }, - "override-max-depth": { type: "string" }, - "override-max-properties": { type: "string" }, - "max-schema-size": { type: "string" }, - "analysis-timeout-ms": { type: "string" }, - "max-ssrf-hostnames": { type: "string" }, - "dns-total-timeout-ms": { type: "string" }, - ignore: { type: "string", multiple: true }, - offline: { type: "boolean", default: false }, - lang: { type: "string", default: DEFAULT_LANG }, - format: { type: "string", default: "human" }, - "ref-schema-files": { type: "string", multiple: true, short: "r" }, - version: { type: "boolean", short: "v", default: false }, - help: { type: "boolean", short: "h", default: false }, - }, - })); - } catch (err) { - die(err.message); - } + let values; + let positionals; + try { + ({ values, positionals } = parseArgs({ + args: argv, + allowPositionals: true, + options: { + "override-max-items": { type: "string" }, + "override-max-depth": { type: "string" }, + "override-max-properties": { type: "string" }, + "max-schema-size": { type: "string" }, + "analysis-timeout-ms": { type: "string" }, + "max-ssrf-hostnames": { type: "string" }, + "dns-total-timeout-ms": { type: "string" }, + ignore: { type: "string", multiple: true }, + offline: { type: "boolean", default: false }, + lang: { type: "string", default: DEFAULT_LANG }, + format: { type: "string", default: "human" }, + "ref-schema-files": { type: "string", multiple: true, short: "r" }, + version: { type: "boolean", short: "v", default: false }, + help: { type: "boolean", short: "h", default: false }, + }, + })); + } catch (err) { + die(err.message); + } - if (values.help) { - console.log(`Usage: sast-json-schema [options] + if (values.help) { + log(`Usage: sast-json-schema [options] Options: --override-max-items Override max items limit (default: 1024) @@ -1181,115 +1522,156 @@ Exit codes: 0 no issues found 1 schema has issues, including depth-exceeded, analysis timeout, and SSRF hostname-cap / DNS-budget findings 2 usage / tool error: bad args, unreadable file, invalid JSON, unsupported $schema, oversized schema, or non-JSON-serializable (circular) schema`); - process.exit(0); - } + return 0; + } - if (values.version) { - console.log(pkg.version); - process.exit(0); - } + if (values.version) { + log(pkg.version); + return 0; + } - if ( - values.format !== "human" && - values.format !== "json" && - values.format !== "sarif" - ) { - die(`--format must be "human", "json", or "sarif", got "${values.format}"`); - } + if ( + values.format !== "human" && + values.format !== "json" && + values.format !== "sarif" + ) { + die( + `--format must be "human", "json", or "sarif", got "${values.format}"`, + ); + } - if (!Object.hasOwn(DANGEROUS_NAMES_BY_LANG, values.lang)) { - die( - `--lang must be one of: ${Object.keys(DANGEROUS_NAMES_BY_LANG).join(", ")}, got "${values.lang}"`, - ); - } + if (!Object.hasOwn(DANGEROUS_NAMES_BY_LANG, values.lang)) { + die( + `--lang must be one of: ${Object.keys(DANGEROUS_NAMES_BY_LANG).join(", ")}, got "${values.lang}"`, + ); + } - const input = positionals[0]; - if (!input) die("missing required argument "); + const input = positionals[0]; + if (!input) die("missing required argument "); - const filePath = resolve(input); - let fileStat; - try { - fileStat = await stat(filePath); - } catch (err) { - die(`cannot read file "${input}": ${err.message}`); - } - // Only enforce --max-schema-size at the file gate when it parses to a valid - // non-negative integer. For invalid values (e.g. 3.5 or a negative), fall - // back to the default here and let analyze() raise the proper validation - // error instead of a misleading "file exceeds N byte" message. - const parsedMaxSchemaSize = - values["max-schema-size"] != null - ? Number(values["max-schema-size"]) - : null; - const fileSizeLimit = - parsedMaxSchemaSize != null && - Number.isInteger(parsedMaxSchemaSize) && - parsedMaxSchemaSize >= 0 - ? parsedMaxSchemaSize - : MAX_SCHEMA_SIZE; - if (fileStat.size > fileSizeLimit) { - die(`schema file exceeds ${fileSizeLimit} byte size limit: "${input}"`); - } - const schema = await readJsonFile(filePath, `file "${input}"`); - - const safeHostnames = new Set(); - if (values["ref-schema-files"]) { - for (const refFile of values["ref-schema-files"]) { - const refSchema = await readJsonFile( - resolve(refFile), - `--ref-schema-files file "${refFile}"`, - ); - if (typeof refSchema.$id === "string") { - try { - const url = new URL(refSchema.$id); - if (url.hostname) safeHostnames.add(url.hostname); - } catch {} + const filePath = resolve(input); + let fileStat; + try { + fileStat = await statFn(filePath); + } catch (err) { + die(`cannot read file "${input}": ${err.message}`); + } + // Only enforce --max-schema-size at the file gate when it parses to a valid + // non-negative integer. For invalid values (e.g. 3.5 or a negative), fall + // back to the default here and let analyze() raise the proper validation + // error instead of a misleading "file exceeds N byte" message. + const parsedMaxSchemaSize = + // Stryker disable next-line ConditionalExpression: when absent, Number(undefined) + // = NaN, which the !Number.isInteger guard below rejects to MAX just like null does. + values["max-schema-size"] != null + ? Number(values["max-schema-size"]) + : null; + const fileSizeLimit = + // Stryker disable next-line ConditionalExpression: the != null head is + // redundant with Number.isInteger(null) === false on the next line. + parsedMaxSchemaSize != null && + Number.isInteger(parsedMaxSchemaSize) && + parsedMaxSchemaSize >= 0 + ? parsedMaxSchemaSize + : MAX_SCHEMA_SIZE; + if (fileStat.size > fileSizeLimit) { + die(`schema file exceeds ${fileSizeLimit} byte size limit: "${input}"`); + } + const schema = await readJsonFile(filePath, `file "${input}"`); + + const safeHostnames = new Set(); + if (values["ref-schema-files"]) { + for (const refFile of values["ref-schema-files"]) { + const refSchema = await readJsonFile( + resolve(refFile), + `--ref-schema-files file "${refFile}"`, + ); + // Stryker disable next-line ConditionalExpression: a non-string $id makes + // `new URL` throw into the catch below, so skipping vs trying is the same. + if (typeof refSchema.$id === "string") { + try { + const url = new URL(refSchema.$id); + // Stryker disable next-line ConditionalExpression: an empty hostname + // is never a real $ref host, so adding "" to the safe set is a no-op. + if (url.hostname) safeHostnames.add(url.hostname); + } catch {} + } } } - } - const options = { offline: values.offline, lang: values.lang }; - if (values["override-max-items"] != null) - options.overrideMaxItems = values["override-max-items"]; - if (values["override-max-depth"] != null) - options.overrideMaxDepth = values["override-max-depth"]; - if (values["override-max-properties"] != null) - options.overrideMaxProperties = values["override-max-properties"]; - if (values["max-schema-size"] != null) - options.maxSchemaSize = values["max-schema-size"]; - if (values["analysis-timeout-ms"] != null) - options.analysisTimeoutMs = values["analysis-timeout-ms"]; - if (values["max-ssrf-hostnames"] != null) - options.maxHostnames = values["max-ssrf-hostnames"]; - if (values["dns-total-timeout-ms"] != null) - options.dnsTotalTimeoutMs = values["dns-total-timeout-ms"]; - if (values.ignore) options.ignore = values.ignore; - options.safeHostnames = safeHostnames; - - let errors; - try { - errors = await analyze(schema, options); - } catch (err) { - die(`analyzing schema "${input}": ${err.message}`); - } + // Pass options straight through; analyze() already treats undefined (an + // absent flag) as "use the default" via its own `!= null` guards, so a + // conditional copy here would only add equivalent-mutant noise. + const options = { + offline: values.offline, + lang: values.lang, + overrideMaxItems: values["override-max-items"], + overrideMaxDepth: values["override-max-depth"], + overrideMaxProperties: values["override-max-properties"], + maxSchemaSize: values["max-schema-size"], + analysisTimeoutMs: values["analysis-timeout-ms"], + maxHostnames: values["max-ssrf-hostnames"], + dnsTotalTimeoutMs: values["dns-total-timeout-ms"], + ignore: values.ignore, + safeHostnames, + // Resolving attacker-controlled hostnames from an untrusted schema is a + // blind-SSRF / DNS-exfil amplifier. Warn on STDERR (never STDOUT, to keep + // json/sarif output clean) right before DNS runs, but only when there is + // at least one non-safe remote hostname to resolve. + onRemoteRefs: (refs) => { + const hostnames = new Set(); + for (const { hostname } of refs) { + if (!safeHostnames.has(hostname)) hostnames.add(hostname); + } + if (hostnames.size > 0) { + errorLog( + `note: resolving ${hostnames.size} remote $ref hostname(s) via DNS; pass --offline to skip`, + ); + } + }, + }; - if (values.format === "json") { - process.stdout.write(`${JSON.stringify(errors)}\n`); - if (errors.length) { - console.error(`${input} has ${errors.length} issue(s)`); - process.exit(1); + let errors; + try { + errors = await analyze(schema, options); + } catch (err) { + die(`analyzing schema "${input}": ${err.message}`); + } + + if (values.format === "json") { + write(`${JSON.stringify(errors)}\n`); + if (errors.length) { + errorLog(`${input} has ${errors.length} issue(s)`); + return 1; + } + return 0; + } + if (values.format === "sarif") { + write(`${JSON.stringify(formatSarif(errors, input))}\n`); + if (errors.length) { + errorLog(`${input} has ${errors.length} issue(s)`); + return 1; + } + return 0; } - } else if (values.format === "sarif") { - process.stdout.write(`${JSON.stringify(formatSarif(errors, input))}\n`); if (errors.length) { - console.error(`${input} has ${errors.length} issue(s)`); - process.exit(1); + log(`${input} has issues`); + log(JSON.stringify(errors, null, 2)); + return 1; } - } else if (errors.length) { - console.log(`${input} has issues`); - console.log(JSON.stringify(errors, null, 2)); - process.exit(1); - } else { - console.log(`${input} has no issues`); + log(`${input} has no issues`); + return 0; + } catch (err) { + if (err instanceof CliExit) return err.code; + throw err; } +}; + +// Stryker disable ConditionalExpression,LogicalOperator,BlockStatement,MethodExpression: the +// main-module guard and its body only run when cli.js IS the process entry, which the +// in-process tests (which import run() directly) never are; the spawned subprocess in +// cli.test.js exercises it but perTest cannot attribute that coverage back here. +if (process.argv[1] && resolve(process.argv[1]) === import.meta.filename) { + process.exitCode = await run(process.argv.slice(2)); } +// Stryker restore all diff --git a/src/$defs.json b/src/$defs.json index 9582e83..56c26ef 100644 --- a/src/$defs.json +++ b/src/$defs.json @@ -23,6 +23,7 @@ "maxProperties": 1024 }, "safeValueSizeLimits": { + "$comment": "Recursively bounds the size of a const/default/enum/examples value. Top-level strings/arrays/objects are bounded by length/items/property counts, and nested array items and object property values are bounded by self-$ref, so a nested oversized string, array, or object is rejected at every level (ASVS 1.3.3). Recursion terminates on the value's own structure; AJV resolves the recursive $ref without infinite compilation.", "allOf": [ { "if": { @@ -37,7 +38,7 @@ "type": "array" }, "then": { - "$ref": "#/$defs/safeArrayItemsLimits" + "$ref": "#/$defs/safeValueSizeLimits.array" } }, { @@ -45,11 +46,25 @@ "type": "object" }, "then": { - "$ref": "#/$defs/safeObjectPropertiesLimits" + "$ref": "#/$defs/safeValueSizeLimits.object" } } ] }, + "safeValueSizeLimits.array": { + "$comment": "Array branch of safeValueSizeLimits: bounds the item count and recurses into each item so nested strings/arrays/objects are also bounded.", + "$ref": "#/$defs/safeArrayItemsLimits", + "items": { + "$ref": "#/$defs/safeValueSizeLimits" + } + }, + "safeValueSizeLimits.object": { + "$comment": "Object branch of safeValueSizeLimits: bounds the property count and recurses into each property value so nested strings/arrays/objects are also bounded.", + "$ref": "#/$defs/safeObjectPropertiesLimits", + "additionalProperties": { + "$ref": "#/$defs/safeValueSizeLimits" + } + }, "safeStringLengthLimits": { "type": "string", "minLength": 0, @@ -247,6 +262,18 @@ "not": { "pattern": "\\|([^()]|\\([^()]*\\))*\\$$" } + }, + { + "$comment": "Reject absurd quantifier upper bounds: a {n,m} (or {n}) whose count has 5+ digits (>= 10000). Catches huge single repetitions like a{1,1000000} and a{100000} that the runtime redos-detector would otherwise have to catch. Four-digit bounds (<= 9999) remain allowed.", + "not": { + "pattern": "\\{[0-9]*,?[0-9]{5,}\\}" + } + }, + { + "$comment": "Reject a bounded quantifier {n,m} applied to a group (...) that itself contains an unbounded quantifier (+ or *), e.g. (a+){1,5}, (a*){1,5}, or (?:a+){1,5}. This nested-quantifier-around-a-repeating-group shape is a classic exponential-backtracking ReDoS. Groups whose only inner quantifiers are fixed/bounded {n}, e.g. (?:[0-9a-f]{4}-){3}, and bounded groups with no inner quantifier, e.g. (abc){1,5}, remain allowed. Known limitation: a group whose inner quantifier is itself bounded but with a large product, e.g. (a{1,1000}){1,1000}, is NOT rejected here; the CLI runtime (redos-detector) still catches it. See README Known Limitations.", + "not": { + "pattern": "\\([^()]*[+*][^()]*\\)\\{[0-9]" + } } ] }, @@ -893,6 +920,36 @@ "required": ["maxProperties", "propertyNames"] } }, + "dependentSchemas.additionalProperties.draft-04": { + "$comment": "draft-04 variant of dependentSchemas.additionalProperties. draft-04 has no `propertyNames` keyword (it is banned via `propertyNames: false`), so a typed additionalProperties map can only be bounded by `maxProperties`. Requiring `propertyNames` here would make every draft-04 dictionary schema unsatisfiable.", + "properties": { + "type": { + "$ref": "#/$defs/typeObject" + }, + "additionalProperties": { + "oneOf": [ + { + "const": false + }, + { + "type": "object", + "required": ["type"] + } + ] + } + }, + "if": { + "properties": { + "additionalProperties": { + "type": "object" + } + }, + "required": ["additionalProperties"] + }, + "then": { + "required": ["maxProperties"] + } + }, "dependentSchemas.unevaluatedProperties": { "properties": { "type": { @@ -1159,25 +1216,6 @@ } } }, - "dependentSchemas.$id": { - "properties": { - "$id": { - "oneOf": [ - { - "$ref": "#/$defs/safeIdUrl" - }, - { - "$ref": "#/$defs/safeUrn" - }, - { - "type": "string", - "maxLength": 1024, - "pattern": "^[a-zA-Z0-9_-]+$" - } - ] - } - } - }, "dependentSchemas.$anchor": { "properties": { "$anchor": { diff --git a/src/2019-09.json b/src/2019-09.json index 1d4232f..75107da 100644 --- a/src/2019-09.json +++ b/src/2019-09.json @@ -91,9 +91,6 @@ "items": { "$ref": "#/$defs/dependentSchemas.items" }, - "prefixItems": { - "$ref": "#/$defs/dependentSchemas.prefixItems" - }, "properties": { "$ref": "#/$defs/dependentSchemas.properties" }, diff --git a/src/draft-04.json b/src/draft-04.json index 9bbaed9..1dc4808 100644 --- a/src/draft-04.json +++ b/src/draft-04.json @@ -143,26 +143,6 @@ } } }, - "dependentSchemas.id": { - "$comment": "Draft-04 used `id` as the identifier keyword; draft-06+ renamed it to $id. Same URL/local-ref rules apply.", - "properties": { - "id": { - "oneOf": [ - { - "$ref": "#/$defs/safeIdUrl" - }, - { - "$ref": "#/$defs/safeUrn" - }, - { - "type": "string", - "maxLength": 1024, - "pattern": "^[a-zA-Z0-9_-]+$" - } - ] - } - } - }, "schemaBase": { "properties": { "additionalItems": { @@ -240,7 +220,7 @@ "$ref": "#/$defs/dependentSchemas.properties" }, "additionalProperties": { - "$ref": "#/$defs/dependentSchemas.additionalProperties" + "$ref": "#/$defs/dependentSchemas.additionalProperties.draft-04" }, "items": { "$ref": "#/$defs/dependentSchemas.items" diff --git a/tests/bfixes.test.js b/tests/bfixes.test.js new file mode 100644 index 0000000..33d4f7d --- /dev/null +++ b/tests/bfixes.test.js @@ -0,0 +1,254 @@ +import assert from "node:assert"; +import { describe, it } from "node:test"; +import Ajv from "ajv/dist/2020.js"; +import schema202012 from "../2020-12.json" with { type: "json" }; +import schemaDraft04 from "../draft-04.json" with { type: "json" }; + +const compile = (schema) => + new Ajv({ strictTypes: false, allowUnionTypes: true }).compile(schema); + +// Wrap a keyword value (const/default/examples) in a minimal-but-valid 2020-12 +// object schema so only the value-size rule under test decides accept/reject. +const wrap2020 = (keyword, value) => ({ + $schema: "https://json-schema.org/draft/2020-12/schema", + $id: "https://example.com/x", + type: "object", + additionalProperties: false, + required: [], + unevaluatedProperties: false, + properties: { + v: { + type: "object", + additionalProperties: false, + required: [], + unevaluatedProperties: false, + properties: {}, + [keyword]: value, + }, + }, +}); + +const buildDeep = (depth) => { + const root = {}; + let cur = root; + for (let i = 0; i < depth; i++) { + cur.a = {}; + cur = cur.a; + } + cur.leaf = "x"; + return root; +}; + +// === B3: safePattern static rule catches bounded-quantifier ReDoS === +describe("B3: safePattern bounded-quantifier ReDoS (2020-12)", () => { + const validate = compile(schema202012); + const withPattern = (pattern) => ({ + $schema: "https://json-schema.org/draft/2020-12/schema", + $id: "https://example.com/x", + type: "object", + additionalProperties: false, + required: [], + unevaluatedProperties: false, + properties: { + v: { type: "string", pattern, maxLength: 100 }, + }, + }); + const accepts = (pattern) => validate(withPattern(pattern)); + + const rejectCases = [ + ["bounded quantifier around a + group", "^(a+){1,5}$"], + ["bounded quantifier around a * group", "^(a*){1,5}$"], + ["bounded quantifier around a + non-capturing group", "^(?:a+){1,5}$"], + ["huge single repetition (5+ digit upper bound)", "^a{1,1000000}$"], + ["huge exact repetition (5+ digits)", "^a{100000}$"], + ["huge char-class repetition (5+ digits)", "^[a-z]{10000}$"], + ]; + for (const [label, pattern] of rejectCases) { + it(`rejects ${label}: ${pattern}`, () => { + assert.strictEqual(accepts(pattern), false); + }); + } + + const acceptCases = [ + ["simple bounded char class", "^[a-z]{1,10}$"], + ["bounded group with no inner quantifier", "^(abc){1,5}$"], + ["non-capturing bounded group, no inner quantifier", "^(?:abc){1,5}$"], + [ + "group with only fixed inner {n} quantifier (uuid)", + "^(?:urn:uuid:)?[0-9a-f]{8}-(?:[0-9a-f]{4}-){3}[0-9a-f]{12}$", + ], + ["exact small repetition", "^a{2,8}$"], + ["date pattern", "^[0-9]{4}-[0-9]{2}-[0-9]{2}$"], + ["four-digit upper bound (below the 5-digit cap)", "^[a-z]{1,9999}$"], + ]; + for (const [label, pattern] of acceptCases) { + it(`accepts ${label}: ${pattern}`, () => { + assert.strictEqual( + accepts(pattern), + true, + `expected ACCEPT, got errors: ${JSON.stringify(validate.errors, null, 2)}`, + ); + }); + } + + it("KNOWN LIMITATION: nested bounded-quantifier large product is accepted by meta-schema (CLI runtime catches it)", () => { + // (a{1,1000}){1,1000} has a large product of bounded quantifiers but no + // unbounded + / * inside the group, so the static meta-schema rule does + // not reject it. Documented in README Known Limitations; the CLI's + // redos-detector rejects it at runtime. + assert.strictEqual(accepts("^(a{1,1000}){1,1000}$"), true); + }); +}); + +// === B1: nested const/default/examples values are size-bounded recursively === +describe("B1: recursive value size limits (2020-12)", () => { + const validate = compile(schema202012); + + for (const keyword of ["const", "default"]) { + it(`rejects a nested 1MB string inside a ${keyword} object`, () => { + const value = { a: "X".repeat(1000000) }; + assert.strictEqual(validate(wrap2020(keyword, value)), false); + }); + + it(`does not accept a deeply-nested (2000-level) ${keyword}`, () => { + // The recursive self-$ref bounds size at every level. Pure depth is + // bounded by AJV's own runtime recursion limit: a 2000-level value is + // never accepted, it fails closed (validate returns false or throws a + // RangeError during validation). Either way it is not treated as safe. + let accepted = true; + try { + accepted = validate(wrap2020(keyword, buildDeep(2000))); + } catch { + accepted = false; + } + assert.strictEqual(accepted, false); + }); + + it(`rejects a 1MB string nested deep inside a ${keyword} (size, not depth)`, () => { + const deep = buildDeep(10); + let cur = deep; + while (cur.a) cur = cur.a; + cur.leaf = "X".repeat(1000000); + assert.strictEqual(validate(wrap2020(keyword, deep)), false); + }); + + it(`accepts a small nested ${keyword} (a few short strings)`, () => { + const value = { a: "hello", b: "world", c: { d: "ok" } }; + assert.strictEqual( + validate(wrap2020(keyword, value)), + true, + `expected ACCEPT, got errors: ${JSON.stringify(validate.errors, null, 2)}`, + ); + }); + } + + it("rejects a nested 1MB string inside an examples value", () => { + const schema = { + $schema: "https://json-schema.org/draft/2020-12/schema", + $id: "https://example.com/x", + type: "object", + additionalProperties: false, + required: [], + unevaluatedProperties: false, + properties: { + v: { + type: "object", + additionalProperties: false, + required: [], + unevaluatedProperties: false, + properties: {}, + examples: [{ a: "X".repeat(1000000) }], + }, + }, + }; + assert.strictEqual(validate(schema), false); + }); + + it("accepts a small nested examples value", () => { + const schema = { + $schema: "https://json-schema.org/draft/2020-12/schema", + $id: "https://example.com/x", + type: "object", + additionalProperties: false, + required: [], + unevaluatedProperties: false, + properties: { + v: { + type: "object", + additionalProperties: false, + required: [], + unevaluatedProperties: false, + properties: {}, + examples: [{ a: "hi", b: ["ok", "fine"] }], + }, + }, + }; + assert.strictEqual( + validate(schema), + true, + `expected ACCEPT, got errors: ${JSON.stringify(validate.errors, null, 2)}`, + ); + }); +}); + +// === B2: draft-04 typed additionalProperties must be satisfiable === +describe("B2: draft-04 typed additionalProperties (dictionary)", () => { + const validate = compile(schemaDraft04); + const dictionary = (overrides = {}) => ({ + $schema: "https://json-schema.org/draft-04/schema", + id: "https://example.com/dict", + type: "object", + additionalProperties: { + type: "string", + pattern: "^[a-z]+$", + maxLength: 10, + }, + maxProperties: 10, + required: [], + ...overrides, + }); + + it("accepts a valid draft-04 dictionary schema with maxProperties", () => { + const valid = validate(dictionary()); + assert.strictEqual( + valid, + true, + `expected ACCEPT, got errors: ${JSON.stringify(validate.errors, null, 2)}`, + ); + }); + + it("rejects the same dictionary schema without maxProperties", () => { + const noMax = dictionary(); + delete noMax.maxProperties; + assert.strictEqual(validate(noMax), false); + }); + + it("2020-12 still requires propertyNames for a typed additionalProperties map (unchanged)", () => { + const validate2020 = compile(schema202012); + const base = { + $schema: "https://json-schema.org/draft/2020-12/schema", + $id: "https://example.com/dict", + type: "object", + additionalProperties: { + type: "string", + pattern: "^[a-z]+$", + maxLength: 10, + }, + maxProperties: 10, + required: [], + unevaluatedProperties: false, + }; + // Without propertyNames: rejected. + assert.strictEqual(validate2020(base), false); + // With propertyNames: accepted. + const withPN = { + ...base, + propertyNames: { type: "string", pattern: "^[a-z]+$", maxLength: 20 }, + }; + assert.strictEqual( + validate2020(withPN), + true, + `expected ACCEPT, got errors: ${JSON.stringify(validate2020.errors, null, 2)}`, + ); + }); +}); diff --git a/tests/build.test.js b/tests/build.test.js new file mode 100644 index 0000000..d454ce8 --- /dev/null +++ b/tests/build.test.js @@ -0,0 +1,66 @@ +import { strictEqual, throws } from "node:assert"; +import { describe, test } from "node:test"; +import { verifyRefs } from "../bin/build.js"; + +describe("verifyRefs", () => { + test("passes when every $defs ref resolves", () => { + const schema = { + $defs: { + a: { type: "string" }, + b: { $ref: "#/$defs/a" }, + }, + properties: { + x: { $ref: "#/$defs/b" }, + }, + }; + strictEqual(verifyRefs(schema, "ok.json"), undefined); + }); + + test("throws naming the dangling pointer", () => { + const schema = { + $defs: { a: { type: "string" } }, + properties: { x: { $ref: "#/$defs/nope" } }, + }; + throws( + () => verifyRefs(schema, "bad.json"), + (err) => { + strictEqual(err instanceof Error, true); + strictEqual(err.message.includes("#/$defs/nope"), true); + strictEqual(err.message.includes("bad.json"), true); + return true; + }, + ); + }); + + test("finds refs nested in properties, allOf arrays, and items", () => { + const schema = { + $defs: { present: { type: "string" } }, + allOf: [{ properties: { y: { items: { $ref: "#/$defs/missing" } } } }], + }; + throws(() => verifyRefs(schema, "nested.json"), /#\/\$defs\/missing/); + }); + + test("ignores non-local refs (http and #/properties)", () => { + const schema = { + $defs: { a: { type: "string" } }, + properties: { + x: { $ref: "http://example.com/other.json#/$defs/whatever" }, + y: { $ref: "#/properties/x" }, + }, + }; + strictEqual(verifyRefs(schema, "ignore.json"), undefined); + }); + + test("resolves #/definitions/ pointers against definitions", () => { + const ok = { + definitions: { a: { type: "string" } }, + properties: { x: { $ref: "#/definitions/a" } }, + }; + strictEqual(verifyRefs(ok, "defs-ok.json"), undefined); + const bad = { + definitions: { a: { type: "string" } }, + properties: { x: { $ref: "#/definitions/missing" } }, + }; + throws(() => verifyRefs(bad, "defs-bad.json"), /#\/definitions\/missing/); + }); +}); diff --git a/tests/cli.analyze.test.js b/tests/cli.analyze.test.js index 74dadba..364df4c 100644 --- a/tests/cli.analyze.test.js +++ b/tests/cli.analyze.test.js @@ -1,11 +1,13 @@ import { deepStrictEqual, ok, strictEqual } from "node:assert"; import { describe, test } from "node:test"; +import { pathToFileURL } from "node:url"; import schema202012 from "../2020-12.json" with { type: "json" }; import { analyze, formatSarif, resolveInstancePath, resolveSSRFRefs, + run, } from "../cli.js"; test("analyze should filter errors matching options.ignore by instancePath", async () => { @@ -447,6 +449,37 @@ describe("analyze DNS options", () => { ); }); + test("$dynamicRef to a private-resolving hostname yields ssrf error online", async () => { + const schema = { + $schema: "https://json-schema.org/draft/2020-12/schema", + $id: "test", + $dynamicRef: "https://localhost/schema.json", + }; + const errors = await analyze(schema, { dnsTimeoutMs: 5_000 }); + const ssrf = errors.filter((e) => e.keyword === "ssrf"); + strictEqual( + ssrf.length, + 1, + "localhost $dynamicRef must be flagged as ssrf", + ); + strictEqual(ssrf[0].instancePath, "/$dynamicRef"); + ok(ssrf[0].params.resolvedIP, "should include the resolved private IP"); + }); + + test("offline mode should not emit ssrf errors for remote $dynamicRef", async () => { + const schema = { + $schema: "https://json-schema.org/draft/2020-12/schema", + $id: "test", + $dynamicRef: "https://localhost/schema.json", + }; + const offline = await analyze(schema, { offline: true }); + strictEqual( + offline.filter((e) => e.keyword === "ssrf").length, + 0, + "offline mode must skip DNS lookup entirely", + ); + }); + test("dnsTimeoutMs=1 should fail fast when offline is unset", async () => { const schema = { $schema: "https://json-schema.org/draft/2020-12/schema", @@ -951,4 +984,814 @@ describe("formatSarif", () => { "", ); }); + + test("rule entry carries id, descriptions and driver metadata", () => { + const errors = [ + { + instancePath: "/properties/name", + schemaPath: "#/maxLength", + keyword: "maxLength", + params: { limit: 100 }, + message: "must have maxLength", + }, + ]; + const driver = formatSarif(errors, "/tmp/schema.json").runs[0].tool.driver; + strictEqual( + driver.informationUri, + "https://github.com/willfarrell/sast-json-schema", + ); + const rule = driver.rules[0]; + strictEqual(rule.id, "maxLength"); + strictEqual(rule.name, "maxLength"); + strictEqual(rule.shortDescription.text, "maxLength"); + strictEqual(rule.fullDescription.text, "must have maxLength"); + strictEqual(rule.defaultConfiguration.level, "error"); + }); + + test("rule fullDescription falls back to the ruleId when message is absent", () => { + const errors = [{ schemaPath: "#/maxLength", keyword: "maxLength" }]; + const rule = formatSarif(errors, "/tmp/schema.json").runs[0].tool.driver + .rules[0]; + strictEqual(rule.fullDescription.text, "maxLength"); + }); + + test("rule id is 'unknown' in the rule table when schemaPath and keyword are absent", () => { + const errors = [{ instancePath: "/", message: "no rule" }]; + const rule = formatSarif(errors, "/tmp/schema.json").runs[0].tool.driver + .rules[0]; + strictEqual(rule.id, "unknown"); + }); + + test("repeated ruleId is de-duplicated, keeping the first occurrence", () => { + const errors = [ + { schemaPath: "#/maxLength", keyword: "maxLength", message: "first" }, + { schemaPath: "#/maxLength", keyword: "maxLength", message: "second" }, + ]; + const driver = formatSarif(errors, "/tmp/schema.json").runs[0].tool.driver; + strictEqual(driver.rules.length, 1); + strictEqual(driver.rules[0].id, "maxLength"); + // First-wins: re-adding would overwrite fullDescription with "second". + strictEqual(driver.rules[0].fullDescription.text, "first"); + }); + + test("ruleId is the first schemaPath segment, not the keyword, when both exist", () => { + const errors = [ + { schemaPath: "#/required/0", keyword: "required", message: "m" }, + ]; + const sarif = formatSarif(errors, "/tmp/schema.json"); + // `... || err.keyword` only fires when the segment is empty; here it is + // "required" from the path, which happens to differ from a keyword test. + strictEqual(sarif.runs[0].results[0].ruleId, "required"); + const errors2 = [ + { schemaPath: "#/properties/x", keyword: "kw", message: "m" }, + ]; + const sarif2 = formatSarif(errors2, "/tmp/schema.json"); + strictEqual(sarif2.runs[0].results[0].ruleId, "properties"); + // Same derivation in the rule table: must be the path segment, not keyword. + strictEqual(sarif2.runs[0].tool.driver.rules[0].id, "properties"); + }); + + test("ruleId strips only a leading '#/' from schemaPath", () => { + // A non-leading "#/" must be preserved: the strip is anchored to the start, + // so "foo#/bar" keeps its first segment "foo#" rather than collapsing to + // "foobar". Exercises both the rule-table and the per-result derivations. + const errors = [{ schemaPath: "foo#/bar", keyword: "kw", message: "m" }]; + const sarif = formatSarif(errors, "/tmp/schema.json"); + strictEqual(sarif.runs[0].results[0].ruleId, "foo#"); + strictEqual(sarif.runs[0].tool.driver.rules[0].id, "foo#"); + }); + + test("top-level $schema is the SARIF 2.1.0 schema URL", () => { + const sarif = formatSarif([{ keyword: "x" }], "/tmp/schema.json"); + strictEqual( + sarif.$schema, + "https://raw.githubusercontent.com/oasis-tcs/sarif-spec/main/Schemata/sarif-schema-2.1.0.json", + ); + }); + + test("result location carries the input file URI and value kind", () => { + const errors = [ + { instancePath: "/a", schemaPath: "#/maxLength", keyword: "maxLength" }, + ]; + const loc = formatSarif(errors, "/tmp/schema.json").runs[0].results[0] + .locations[0]; + ok(loc.physicalLocation.artifactLocation.uri.startsWith("file://")); + ok(loc.physicalLocation.artifactLocation.uri.endsWith("/tmp/schema.json")); + strictEqual(loc.logicalLocations[0].kind, "value"); + }); + + test("artifactLocation uri is repo-relative with a SRCROOT uriBaseId when the input is under cwd", () => { + const errors = [ + { instancePath: "/a", schemaPath: "#/maxLength", keyword: "maxLength" }, + ]; + const sarif = formatSarif(errors, "/repo/schemas/api.json", "/repo"); + const loc = sarif.runs[0].results[0].locations[0]; + strictEqual(loc.physicalLocation.artifactLocation.uri, "schemas/api.json"); + strictEqual(loc.physicalLocation.artifactLocation.uriBaseId, "SRCROOT"); + strictEqual( + sarif.runs[0].originalUriBaseIds.SRCROOT.uri, + pathToFileURL("/repo/").href, + ); + }); + + test("relative artifactLocation uri has no leading './' for a file directly in cwd", () => { + const errors = [ + { instancePath: "/a", schemaPath: "#/maxLength", keyword: "maxLength" }, + ]; + const sarif = formatSarif(errors, "/repo/api.json", "/repo"); + strictEqual( + sarif.runs[0].results[0].locations[0].physicalLocation.artifactLocation + .uri, + "api.json", + ); + }); + + test("originalUriBaseIds SRCROOT uri is the cwd as a file:// directory URI", () => { + const errors = [{ instancePath: "/a", keyword: "x" }]; + const sarif = formatSarif(errors, "/repo/sub/x.json", "/repo"); + strictEqual( + sarif.runs[0].originalUriBaseIds.SRCROOT.uri, + pathToFileURL("/repo/").href, + ); + ok(sarif.runs[0].originalUriBaseIds.SRCROOT.uri.endsWith("/repo/")); + }); + + test("input path equal to cwd (empty relative path) falls back to the absolute file:// uri", () => { + // resolve(inputPath) === cwd makes relative() return "", which must NOT be + // treated as inside-cwd (an empty uri is not a usable artifact location); + // it falls back to the absolute file:// uri with no uriBaseId. + const errors = [{ instancePath: "/a", keyword: "x" }]; + const sarif = formatSarif(errors, "/repo", "/repo"); + const loc = sarif.runs[0].results[0].locations[0]; + strictEqual( + loc.physicalLocation.artifactLocation.uri, + pathToFileURL("/repo").href, + ); + strictEqual(loc.physicalLocation.artifactLocation.uriBaseId, undefined); + strictEqual(sarif.runs[0].originalUriBaseIds, undefined); + }); + + test("input outside cwd falls back to the absolute file:// uri with no uriBaseId or originalUriBaseIds", () => { + const errors = [ + { instancePath: "/a", schemaPath: "#/maxLength", keyword: "maxLength" }, + ]; + const sarif = formatSarif(errors, "/elsewhere/api.json", "/repo"); + const loc = sarif.runs[0].results[0].locations[0]; + strictEqual( + loc.physicalLocation.artifactLocation.uri, + pathToFileURL("/elsewhere/api.json").href, + ); + ok(loc.physicalLocation.artifactLocation.uri.startsWith("file://")); + strictEqual(loc.physicalLocation.artifactLocation.uriBaseId, undefined); + strictEqual(sarif.runs[0].originalUriBaseIds, undefined); + }); + + test("result properties carry schemaPath, keyword and spread params", () => { + const errors = [ + { + instancePath: "/properties/name", + schemaPath: "#/maxLength", + keyword: "maxLength", + params: { limit: 100 }, + message: "must have maxLength", + }, + ]; + const props = formatSarif(errors, "/tmp/schema.json").runs[0].results[0] + .properties; + strictEqual(props.schemaPath, "#/maxLength"); + strictEqual(props.keyword, "maxLength"); + strictEqual(props.instancePath, "/properties/name"); + strictEqual(props.limit, 100); + }); + + test("result properties default schemaPath and keyword to empty strings", () => { + const errors = [{ instancePath: "/", message: "bare" }]; + const props = formatSarif(errors, "/tmp/schema.json").runs[0].results[0] + .properties; + strictEqual(props.schemaPath, ""); + strictEqual(props.keyword, ""); + }); +}); + +// Boundary and valid-value coverage for the integer option validators +// (!Number.isFinite(n) || n < 0 || !Number.isInteger(n)). Existing tests cover +// non-numeric/non-integer; these add the valid, zero and negative-integer cases. +describe("analyze integer option validators", () => { + const tiny = { type: "string" }; + + test("maxHostnames: a valid positive integer is accepted", async () => { + ok( + Array.isArray(await analyze(tiny, { offline: true, maxHostnames: 256 })), + ); + }); + test("maxHostnames: zero is a valid non-negative integer", async () => { + ok(Array.isArray(await analyze(tiny, { offline: true, maxHostnames: 0 }))); + }); + test("maxHostnames: a negative integer throws TypeError", async () => { + try { + await analyze(tiny, { offline: true, maxHostnames: -5 }); + ok(false, "should have thrown"); + } catch (err) { + ok(err instanceof TypeError); + ok(err.message.includes("maxHostnames")); + ok(err.message.includes("non-negative integer")); + } + }); + + test("dnsTotalTimeoutMs: a valid positive integer is accepted", async () => { + ok( + Array.isArray( + await analyze(tiny, { offline: true, dnsTotalTimeoutMs: 30000 }), + ), + ); + }); + test("dnsTotalTimeoutMs: zero is a valid non-negative integer", async () => { + ok( + Array.isArray( + await analyze(tiny, { offline: true, dnsTotalTimeoutMs: 0 }), + ), + ); + }); + + test("maxSchemaSize: a large valid integer is accepted", async () => { + ok( + Array.isArray( + await analyze(tiny, { offline: true, maxSchemaSize: 1_000_000 }), + ), + ); + }); + test("maxSchemaSize: zero is valid (no TypeError) and hits the size guard instead", async () => { + // 0 passes the n>=0 validator, so the failure must be the RangeError size + // guard, not the TypeError validator (kills n<0 -> n<=0). + try { + await analyze(tiny, { offline: true, maxSchemaSize: 0 }); + ok(false, "should have thrown"); + } catch (err) { + ok(err instanceof RangeError, `expected RangeError, got ${err.name}`); + ok(err.message.includes("size")); + } + }); + test("maxSchemaSize: a negative integer throws TypeError (not the size guard)", async () => { + try { + await analyze(tiny, { offline: true, maxSchemaSize: -5 }); + ok(false, "should have thrown"); + } catch (err) { + ok(err instanceof TypeError, `expected TypeError, got ${err.name}`); + ok(err.message.includes("maxSchemaSize")); + } + }); +}); + +// The size guard uses a strict `>`; a schema serialized to exactly the limit must +// be accepted (kills the `> sizeLimit` -> `>= sizeLimit` mutation). +describe("analyze size guard boundary", () => { + test("schema serialized to exactly maxSchemaSize is accepted", async () => { + const schema = { type: "string" }; + const exact = Buffer.byteLength(JSON.stringify(schema)); + ok( + Array.isArray( + await analyze(schema, { offline: true, maxSchemaSize: exact }), + ), + ); + }); + test("schema one byte over the limit is rejected", async () => { + const schema = { type: "string" }; + const exact = Buffer.byteLength(JSON.stringify(schema)); + try { + await analyze(schema, { offline: true, maxSchemaSize: exact - 1 }); + ok(false, "should have thrown"); + } catch (err) { + ok(err instanceof RangeError); + } + }); +}); + +// The depth-exceeded short-circuit returns one synthetic finding; pin its full +// shape so blanking any field is caught. +describe("analyze depth-exceeded payload", () => { + test("depth-exceeded returns the depth finding with full shape", async () => { + const schema = { + type: "object", + properties: { + a: { type: "string", maxLength: 10, pattern: "^[a-z]+$" }, + }, + }; + const errors = await analyze(schema, { + offline: true, + overrideMaxDepth: 0, + }); + strictEqual(errors.length, 1); + const e = errors[0]; + strictEqual(e.instancePath, ""); + strictEqual(e.schemaPath, "#/depth"); + strictEqual(e.keyword, "depth"); + strictEqual(e.params.limit, 0); + ok(typeof e.params.depth === "number"); + strictEqual(e.message, "must NOT have depth greater than 0"); + }); +}); + +// Full error-shape and boundary locks for resolveSSRFRefs. Existing tests assert +// keyword + a message substring; these pin instancePath, schemaPath and params so +// blanking a field (a mutation) is caught, plus the no-DNS cap/budget boundaries. +describe("resolveSSRFRefs error shapes and boundaries", () => { + const ref = (hostname, path = "/$ref") => ({ + hostname, + ref: `https://${hostname}/schema.json`, + path, + }); + + test("unresolvable hostname error has full shape", async () => { + const errors = await resolveSSRFRefs([ref("nx-host-abc987.invalid")], { + dnsTimeoutMs: 100, + }); + strictEqual(errors.length, 1); + const e = errors[0]; + strictEqual(e.instancePath, "/$ref"); + strictEqual(e.schemaPath, "#/ssrf"); + strictEqual(e.params.ref, "https://nx-host-abc987.invalid/schema.json"); + strictEqual(e.params.hostname, "nx-host-abc987.invalid"); + strictEqual( + e.message, + '$ref hostname "nx-host-abc987.invalid" does not resolve', + ); + }); + + test("private-resolving hostname (localhost) error has full shape", async () => { + const errors = await resolveSSRFRefs([ref("localhost")], { + dnsTimeoutMs: 5_000, + }); + strictEqual(errors.length, 1); + const e = errors[0]; + strictEqual(e.instancePath, "/$ref"); + strictEqual(e.schemaPath, "#/ssrf"); + strictEqual(e.params.hostname, "localhost"); + ok(e.params.resolvedIP, "must include the resolved private IP"); + ok(e.message.includes("resolves to private IP")); + }); + + test("a public hostname produces no findings at all", async () => { + // Not just zero resolvedIP findings: dropping the `if (!privateAddr) return []` + // guard would synthesize spurious findings, so assert a fully empty result. + const errors = await resolveSSRFRefs([ref("dns.google")], { + dnsTimeoutMs: 5_000, + }); + strictEqual(errors.length, 0); + }); + + test("hostname-cap finding has full shape", async () => { + const refs = Array.from({ length: 3 }, (_, i) => ref(`cap${i}.invalid`)); + const errors = await resolveSSRFRefs(refs, { maxHostnames: 2 }); + strictEqual(errors.length, 1); + const e = errors[0]; + strictEqual(e.instancePath, ""); + strictEqual(e.schemaPath, "#/ssrf"); + strictEqual(e.params.hostnames, 3); + strictEqual(e.params.limit, 2); + ok(e.message.includes("too many distinct remote $ref hostnames (3)")); + ok(e.message.includes("above 2")); + }); + + test("exactly maxHostnames distinct hosts is under the cap (strict >)", async () => { + // 2 distinct hosts with maxHostnames 2: must NOT trip the cap. Use + // dnsTotalTimeoutMs:0 so it fails closed on the budget rather than doing DNS. + const refs = [ref("a.invalid"), ref("b.invalid")]; + const errors = await resolveSSRFRefs(refs, { + maxHostnames: 2, + dnsTotalTimeoutMs: 0, + }); + ok( + !errors.some((e) => e.message.includes("too many distinct")), + "exactly-at-cap must not be reported as over the cap", + ); + ok( + errors.every((e) => e.message.includes("budget")), + "should fall through to the budget-exceeded path", + ); + }); + + test("budget-exceeded finding has full shape", async () => { + const errors = await resolveSSRFRefs([ref("budget.invalid")], { + dnsTotalTimeoutMs: 0, + }); + strictEqual(errors.length, 1); + const e = errors[0]; + strictEqual(e.instancePath, "/$ref"); + strictEqual(e.schemaPath, "#/ssrf"); + strictEqual(e.params.hostname, "budget.invalid"); + strictEqual(e.params.ref, "https://budget.invalid/schema.json"); + ok(e.message.includes("SSRF DNS budget exceeded")); + }); +}); + +// Additional resolveInstancePath guards the existing direct tests miss: an empty +// pointer with a non-object root (the root guard must win over the !pointer +// shortcut), and a null encountered mid-walk (the guard must return before +// Object.hasOwn(null) throws). +describe("resolveInstancePath extra guards", () => { + test("non-object root with an empty pointer is still undefined", () => { + strictEqual(resolveInstancePath(null, ""), undefined); + strictEqual(resolveInstancePath("string", ""), undefined); + strictEqual(resolveInstancePath(42, ""), undefined); + }); + + test("a null value mid-walk returns undefined (does not throw)", () => { + strictEqual(resolveInstancePath({ a: null }, "/a/b"), undefined); + }); + + test("does not walk into a string value by index", () => { + // {a:"str"} then "/a/0": the typeof guard must stop the walk, not read s[0]. + strictEqual(resolveInstancePath({ a: "str" }, "/a/0"), undefined); + }); + + test("does not resolve inherited prototype keys", () => { + // Only own keys are walked; a pointer to an inherited member must be undefined. + strictEqual(resolveInstancePath({}, "/toString"), undefined); + strictEqual(resolveInstancePath({ a: {} }, "/a/constructor"), undefined); + }); +}); + +// The SSRF check is gated on `!options.offline`. Use dnsTotalTimeoutMs:0 so the +// SSRF path fails closed on the budget (no real DNS): a remote $ref then yields +// an ssrf finding only when SSRF actually runs. +describe("analyze offline gate", () => { + const remoteRefSchema = { + $schema: "https://json-schema.org/draft/2020-12/schema", + $id: "test", + $ref: "https://ssrf-gate-test.invalid/s.json", + }; + + test("a remote $ref is SSRF-checked when offline is not set", async () => { + const errors = await analyze(remoteRefSchema, { dnsTotalTimeoutMs: 0 }); + ok( + errors.some((e) => e.keyword === "ssrf"), + "non-offline analysis must run the SSRF check", + ); + }); + + test("offline:true skips the SSRF check entirely", async () => { + const errors = await analyze(remoteRefSchema, { + offline: true, + dnsTotalTimeoutMs: 0, + }); + strictEqual( + errors.filter((e) => e.keyword === "ssrf").length, + 0, + "offline must skip SSRF even when a remote $ref is present", + ); + }); +}); + +// A2: the two SSRF "incomplete-analysis" findings (the hostname-cap finding and +// the DNS-total-budget finding) mean DNS resolution was skipped, so suppressing +// them would falsely report a partially-analyzed schema as clean. They must NOT +// be droppable by --ignore, exactly like the depth/timeout findings. Normal +// per-host ssrf findings (resolves-to-private / does-not-resolve) SHOULD remain +// ignorable. +describe("analyze SSRF incomplete-analysis findings are not suppressible", () => { + // Builds a schema with `n` distinct remote $ref hostnames so the hostname cap + // can be tripped with a small maxHostnames. + const manyHostSchema = (n) => { + const schema = { + $schema: "https://json-schema.org/draft/2020-12/schema", + $id: "test", + $defs: {}, + }; + for (let i = 0; i < n; i++) { + schema.$defs[`d${i}`] = { $ref: `https://cap-host-${i}.invalid/s.json` }; + } + return schema; + }; + + test('hostname-cap finding survives --ignore "" (cap finding marked incomplete)', async () => { + const schema = manyHostSchema(5); + const errors = await analyze(schema, { + maxHostnames: 2, + // "" matches the cap finding's empty instancePath; it must NOT suppress it. + ignore: [""], + }); + const cap = errors.find((e) => e.message.includes("too many distinct")); + ok(cap, 'hostname-cap finding must survive --ignore ""'); + strictEqual(cap.params.incomplete, true); + }); + + test("DNS-budget finding survives --ignore ", async () => { + const schema = { + $schema: "https://json-schema.org/draft/2020-12/schema", + $id: "test", + $ref: "https://budget-ignore-test.invalid/s.json", + }; + const errors = await analyze(schema, { + dnsTotalTimeoutMs: 0, + // the budget finding's instancePath is the ref path "/$ref". + ignore: ["/$ref"], + }); + const budget = errors.find((e) => + e.message.includes("SSRF DNS budget exceeded"), + ); + ok(budget, "DNS-budget finding must survive --ignore on its ref path"); + strictEqual(budget.params.incomplete, true); + }); + + test("a normal per-host ssrf finding IS still suppressible by its instancePath", async () => { + // localhost resolves to a private IP -> a normal (suppressible) ssrf finding + // at instancePath "/$ref". Ignoring that path must drop it. + const schema = { + $schema: "https://json-schema.org/draft/2020-12/schema", + $id: "test", + $ref: "https://localhost/s.json", + }; + const without = await analyze(schema, { dnsTimeoutMs: 5_000 }); + ok( + without.some((e) => e.keyword === "ssrf"), + "baseline: localhost ssrf finding is present", + ); + const ignored = await analyze(schema, { + dnsTimeoutMs: 5_000, + ignore: ["/$ref"], + }); + strictEqual( + ignored.filter((e) => e.keyword === "ssrf").length, + 0, + "a normal per-host ssrf finding must remain suppressible", + ); + }); +}); + +// The override filters must only suppress their OWN finding type and keep every +// other finding (the `return true` for non-target errors), and must use a strict +// `>` so an instance exactly at the override limit is suppressed. +describe("analyze override filters keep unrelated findings", () => { + const dangerous = JSON.parse( + '{"properties":{"__proto__":{"type":"string","maxLength":10,"pattern":"^[a-z]+$"}},"required":["__proto__"],"maxProperties":5,"unevaluatedProperties":false}', + ); + + test("overrideMaxItems does not drop a dangerous-name finding", async () => { + const errors = await analyze(dangerous, { + offline: true, + overrideMaxItems: 100, + }); + ok( + errors.some((e) => e.schemaPath === "#/dangerous-name"), + "a non-maxItems finding must survive the overrideMaxItems filter", + ); + }); + + test("overrideMaxProperties does not drop a dangerous-name finding", async () => { + const errors = await analyze(dangerous, { + offline: true, + overrideMaxProperties: 100, + }); + ok( + errors.some((e) => e.schemaPath === "#/dangerous-name"), + "a non-maxProperties finding must survive the overrideMaxProperties filter", + ); + }); + + test("overrideMaxProperties suppresses at exactly the property count (strict >)", async () => { + const props = {}; + for (let i = 0; i < 1100; i++) + props[`p${i}`] = { type: "string", maxLength: 10, pattern: "^[a-z]+$" }; + const schema = { + $schema: "https://json-schema.org/draft/2020-12/schema", + $id: "test", + type: "object", + properties: props, + required: ["p0"], + unevaluatedProperties: false, + maxProperties: 2000, + }; + // exactly 1100 properties; override of 1100 means 1100 > 1100 is false, so + // the finding is suppressed. `>=` would keep it. + const errors = await analyze(schema, { + offline: true, + overrideMaxProperties: 1100, + }); + ok(!errors.some((e) => e.keyword === "maxProperties")); + }); + + test("overrideMaxItems suppresses at exactly the array length (strict >)", async () => { + const schema = { + $schema: "https://json-schema.org/draft/2020-12/schema", + $id: "test", + type: "string", + maxLength: 100, + enum: Array.from({ length: 2000 }, (_, i) => `v${i}`), + }; + const errors = await analyze(schema, { + offline: true, + overrideMaxItems: 2000, + }); + ok(!errors.some((e) => e.keyword === "maxItems")); + }); +}); + +// Valid/zero/negative cases for the override integer validators (existing tests +// cover only non-numeric/non-integer). +describe("analyze override validator boundaries", () => { + const tiny = { type: "string" }; + for (const opt of ["overrideMaxItems", "overrideMaxProperties"]) { + test(`${opt}: a valid positive integer is accepted`, async () => { + ok(Array.isArray(await analyze(tiny, { offline: true, [opt]: 100 }))); + }); + test(`${opt}: zero is a valid non-negative integer`, async () => { + ok(Array.isArray(await analyze(tiny, { offline: true, [opt]: 0 }))); + }); + test(`${opt}: a negative integer throws TypeError`, async () => { + try { + await analyze(tiny, { offline: true, [opt]: -5 }); + ok(false, "should have thrown"); + } catch (err) { + ok(err instanceof TypeError); + ok(err.message.includes(opt)); + ok(err.message.includes("non-negative integer")); + } + }); + } +}); + +// Cover the deadline/maxDepth defaults and the keep-side of the override filter. +describe("analyze depth/timeout defaults and override keep-side", () => { + test("a schema deeper than the default MAX_DEPTH is flagged (default maxDepth used)", async () => { + let deep = { type: "string", maxLength: 10, pattern: "^[a-z]+$" }; + for (let i = 0; i < 40; i++) { + deep = { + type: "object", + properties: { a: deep }, + required: ["a"], + unevaluatedProperties: false, + maxProperties: 5, + }; + } + const errors = await analyze(deep, { offline: true }); + ok( + errors.some((e) => e.keyword === "depth"), + "deep schema must trip the default depth limit", + ); + }); + + test("a large analysisTimeoutMs does not immediately time out", async () => { + // ms > 0 means the deadline is in the future; the ms<=0 short-circuit to 0 + // (immediate timeout) must not fire. + const errors = await analyze( + { type: "string", maxLength: 10, pattern: "^[a-z]+$" }, + { offline: true, analysisTimeoutMs: 600000 }, + ); + ok(!errors.some((e) => e.keyword === "timeout")); + }); + + test("overrideMaxProperties below the property count keeps the maxProperties finding", async () => { + const props = {}; + for (let i = 0; i < 1100; i++) + props[`p${i}`] = { type: "string", maxLength: 10, pattern: "^[a-z]+$" }; + const schema = { + $schema: "https://json-schema.org/draft/2020-12/schema", + $id: "test", + type: "object", + properties: props, + required: ["p0"], + unevaluatedProperties: false, + maxProperties: 2000, + }; + const errors = await analyze(schema, { + offline: true, + overrideMaxProperties: 500, + }); + ok( + errors.some((e) => e.keyword === "maxProperties"), + "1100 properties still exceeds an override of 500", + ); + }); +}); + +// A3: resolving attacker-controlled hostnames from an untrusted schema is a +// blind-SSRF / DNS-exfil amplifier. run() must emit a one-line notice to STDERR +// (never STDOUT, to keep json/sarif output clean) whenever it is about to do DNS +// resolution, i.e. NOT --offline AND the schema actually has remote $ref(s). +describe("run() SSRF DNS notice (A3)", () => { + const runCli = async (argv, files = {}) => { + const out = { log: [], error: [], write: [] }; + const io = { + log: (m) => out.log.push(String(m)), + error: (m) => out.error.push(String(m)), + write: (s) => out.write.push(String(s)), + readFile: async (p) => { + const key = Object.keys(files).find((k) => p.endsWith(k)); + if (key === undefined) throw new Error(`ENOENT ${p}`); + return files[key]; + }, + stat: async (p) => { + const key = Object.keys(files).find((k) => p.endsWith(k)); + if (key === undefined) throw new Error(`ENOENT ${p}`); + return { size: files[key].length }; + }, + }; + const code = await run(argv, io); + return { code, ...out }; + }; + + const REMOTE = JSON.stringify({ + $schema: "https://json-schema.org/draft/2020-12/schema", + $id: "https://example.test/remote.json", + $ref: "https://a3-notice-host.invalid/x.json", + }); + const TWO_REMOTE = JSON.stringify({ + $schema: "https://json-schema.org/draft/2020-12/schema", + $id: "https://example.test/remote.json", + $defs: { + a: { $ref: "https://a3-h1.invalid/x.json" }, + b: { $ref: "https://a3-h2.invalid/x.json" }, + }, + }); + const CLEAN = JSON.stringify({ + $schema: "https://json-schema.org/draft/2020-12/schema", + $id: "https://example.test/clean.json", + type: "string", + maxLength: 10, + pattern: "^[a-z]+$", + }); + + test("emits the DNS notice to STDERR (not STDOUT) when remote refs exist and not offline", async () => { + const r = await runCli( + ["s.json", "--dns-total-timeout-ms", "0", "--format", "json"], + { "s.json": REMOTE }, + ); + ok( + r.error.some((m) => m.includes("resolving") && m.includes("DNS")), + "a DNS notice must be written to error (stderr)", + ); + ok( + r.error.some((m) => m.includes("--offline")), + "the notice should mention --offline as the opt-out", + ); + // Must NOT pollute stdout (json/sarif consumers parse it). + ok( + !r.write.some((s) => s.includes("resolving")), + "the notice must never go to stdout/write", + ); + ok( + !r.log.some((m) => m.includes("resolving")), + "the notice must never go to the stdout logger", + ); + }); + + test("the notice reports the count of remote ref hostnames", async () => { + const r = await runCli( + ["s.json", "--dns-total-timeout-ms", "0", "--format", "json"], + { "s.json": TWO_REMOTE }, + ); + const notice = r.error.find((m) => m.includes("resolving")); + ok(notice, "expected a DNS notice"); + ok(notice.includes("2"), `expected count 2 in the notice: ${notice}`); + }); + + test("a safe-listed hostname is NOT counted in the DNS notice", async () => { + // Two remote ref hosts; one (a3-h1) is marked safe via -r (its $id hostname). + // The notice must count only the UNSAFE host, so it reports 1, not 2. This + // pins the `if (!safeHostnames.has(hostname))` guard: forcing it true would + // count the safe host too and report 2. + const refSchema = JSON.stringify({ + $id: "https://a3-h1.invalid/ref.json", + }); + const r = await runCli( + [ + "s.json", + "--dns-total-timeout-ms", + "0", + "--format", + "json", + "-r", + "ref.json", + ], + { "s.json": TWO_REMOTE, "ref.json": refSchema }, + ); + const notice = r.error.find((m) => m.includes("resolving")); + ok(notice, "expected a DNS notice for the remaining unsafe host"); + ok( + notice.includes("1"), + `safe host must be excluded from the count: ${notice}`, + ); + ok( + !notice.includes("2"), + `the safe-listed host must not be counted: ${notice}`, + ); + }); + + test("is absent under --offline", async () => { + const r = await runCli(["s.json", "--offline", "--format", "json"], { + "s.json": REMOTE, + }); + ok( + !r.error.some((m) => m.includes("resolving")), + "no DNS notice when --offline is set", + ); + }); + + test("is absent when the schema has no remote refs", async () => { + const r = await runCli(["s.json", "--format", "json"], { "s.json": CLEAN }); + ok( + !r.error.some((m) => m.includes("resolving")), + "a clean schema with no remote refs stays quiet", + ); + }); }); diff --git a/tests/cli.crawl.test.js b/tests/cli.crawl.test.js index 3b5a05e..7142d1e 100644 --- a/tests/cli.crawl.test.js +++ b/tests/cli.crawl.test.js @@ -1,6 +1,11 @@ -import { ok, strictEqual } from "node:assert"; +import { deepStrictEqual, ok, strictEqual } from "node:assert"; import { describe, test } from "node:test"; -import { crawlSchema } from "../cli.js"; +import { + crawlSchema, + MAX_COLLECTED_REFS, + MAX_REDOS_PATTERNS, + REDOS_HEAP_BUDGET_BYTES, +} from "../cli.js"; describe("crawlSchema", () => { test("should return empty result for null input", () => { @@ -231,7 +236,7 @@ describe("crawlSchema", () => { // --- range consistency: NaN/Infinity edge cases --- test("should ignore NaN minimum", () => { const r = crawlSchema({ type: "integer", minimum: NaN, maximum: 5 }); - ok(!r.errors.some((e) => e.keyword === "minimum")); + strictEqual(r.errors.length, 0); }); test("should ignore Infinity exclusiveMinimum", () => { @@ -240,7 +245,10 @@ describe("crawlSchema", () => { exclusiveMinimum: Infinity, maximum: 100, }); - ok(!r.errors.some((e) => e.keyword === "minimum")); + // A non-finite bound must be ignored entirely: dropping the Number.isFinite + // guard would treat exclusiveMinimum:Infinity as the effective minimum and + // emit a spurious (exclusiveMinimum) range error, so assert no errors at all. + strictEqual(r.errors.length, 0); }); test("should ignore NaN exclusiveMaximum", () => { @@ -249,7 +257,9 @@ describe("crawlSchema", () => { minimum: 0, exclusiveMaximum: NaN, }); - ok(!r.errors.some((e) => e.keyword === "minimum")); + // Dropping the finiteness guard would make exclusiveMaximum:NaN the + // effective (exclusive) maximum and emit a bogus range error. + strictEqual(r.errors.length, 0); }); test("should ignore -Infinity maximum", () => { @@ -258,7 +268,92 @@ describe("crawlSchema", () => { minimum: 0, maximum: -Infinity, }); - ok(!r.errors.some((e) => e.keyword === "minimum")); + strictEqual(r.errors.length, 0); + }); + + // --- range consistency: exclusive/inclusive boundary tie-breaks --- + // When exclusiveMinimum === minimum, the exclusive bound is the effective one + // (>= picks exclusive). With maximum also at that value the range is empty. + test("should treat exclusiveMinimum === minimum as the exclusive bound", () => { + const r = crawlSchema({ + type: "number", + minimum: 5, + exclusiveMinimum: 5, + maximum: 5, + }); + ok( + r.errors.some((e) => e.keyword === "exclusiveMinimum"), + "exclusiveMinimum tie must win and make [5,5) empty", + ); + }); + + // When exclusiveMaximum === maximum, the exclusive bound wins (<= picks it), + // so (5,5] collapses to an empty range. + test("should treat exclusiveMaximum === maximum as the exclusive bound", () => { + const r = crawlSchema({ + type: "number", + minimum: 5, + maximum: 5, + exclusiveMaximum: 5, + }); + ok( + r.errors.some((e) => e.keyword === "exclusiveMaximum"), + "exclusiveMaximum tie must win and make (5,5] empty", + ); + }); + + // --- range error payload (params + message) --- + test("inclusive impossible range reports both bounds and the message", () => { + const r = crawlSchema({ type: "number", minimum: 10, maximum: 5 }); + const e = r.errors.find((err) => err.keyword === "minimum"); + ok(e, "expected a minimum range error"); + strictEqual(e.params.minimum, 10); + strictEqual(e.params.maximum, 5); + strictEqual(e.message, "numeric range is unsatisfiable"); + }); + + test("exclusive impossible range reports both exclusive bounds", () => { + const r = crawlSchema({ + type: "number", + exclusiveMinimum: 10, + exclusiveMaximum: 5, + }); + const e = r.errors.find((err) => err.keyword === "exclusiveMaximum"); + ok(e, "expected an exclusiveMaximum range error"); + strictEqual(e.params.exclusiveMinimum, 10); + strictEqual(e.params.exclusiveMaximum, 5); + }); + + // --- bound type guards: non-number bounds must be ignored entirely --- + // A string bound must not be coerced into a comparison. Each case is impossible + // only if the typeof guard is dropped (&&→|| would let the string through and + // numeric coercion would then fabricate a range error), so assert no errors. + test("should ignore a string minimum", () => { + const r = crawlSchema({ type: "number", minimum: "10", maximum: 5 }); + strictEqual(r.errors.length, 0); + }); + + test("should ignore a string maximum", () => { + const r = crawlSchema({ type: "number", minimum: 10, maximum: "5" }); + strictEqual(r.errors.length, 0); + }); + + test("should ignore a string exclusiveMinimum", () => { + const r = crawlSchema({ + type: "number", + exclusiveMinimum: "10", + maximum: 5, + }); + strictEqual(r.errors.length, 0); + }); + + test("should ignore a string exclusiveMaximum", () => { + const r = crawlSchema({ + type: "number", + minimum: 10, + exclusiveMaximum: "5", + }); + strictEqual(r.errors.length, 0); }); // --- circular reference protection --- @@ -443,6 +538,30 @@ describe("crawlSchema", () => { strictEqual(r.refs.length, 0); }); + // --- $dynamicRef collection (mirrors $ref) --- + test("should collect remote $dynamicRef URLs", () => { + const r = crawlSchema({ $dynamicRef: "https://internal.host/schema.json" }); + strictEqual(r.refs.length, 1); + strictEqual(r.refs[0].hostname, "internal.host"); + strictEqual(r.refs[0].ref, "https://internal.host/schema.json"); + strictEqual(r.refs[0].path, "/$dynamicRef"); + }); + + test("should skip fragment-only $dynamicRef", () => { + const r = crawlSchema({ $dynamicRef: "#meta" }); + strictEqual(r.refs.length, 0); + }); + + test("should skip $dynamicRef that is not a valid URL and does not start with #", () => { + const r = crawlSchema({ $dynamicRef: "relative/path/schema.json" }); + strictEqual(r.refs.length, 0); + }); + + test("should not collect $id as a fetch target", () => { + const r = crawlSchema({ $id: "https://internal.host/schema.json" }); + strictEqual(r.refs.length, 0); + }); + // --- $ref URL edge cases --- test("should collect $ref with IPv6 literal host", () => { const r = crawlSchema({ $ref: "https://[::1]/schema.json" }); @@ -973,3 +1092,1013 @@ describe("crawlSchema", () => { ok(!r.errors.some((e) => e.params.name === "safe")); }); }); + +// Regression lock for the DANGEROUS_NAMES_BY_LANG denylist. These names are a +// security contract, so they are duplicated here as literals on purpose: the +// test must fail if any entry is dropped or altered in cli.js. Deriving the +// expectations from the exported table instead would let a mutation hide +// behind itself (the test would read the same mutated value it asserts on). +const EXPECTED_DANGEROUS_NAMES_BY_LANG = { + js: ["__proto__", "constructor", "prototype"], + py: [ + "__proto__", + "constructor", + "prototype", + "__class__", + "__init__", + "__globals__", + "__builtins__", + "__import__", + "__reduce__", + "__subclasses__", + "__dict__", + "__mro__", + ], + rb: [ + "__proto__", + "constructor", + "prototype", + "__send__", + "json_class", + "instance_eval", + "instance_variable_set", + "singleton_class", + ], + rs: ["__proto__", "constructor", "prototype"], + java: ["__proto__", "constructor", "prototype", "@type", "@class"], + kotlin: ["__proto__", "constructor", "prototype", "@type", "@class"], + clojure: ["__proto__", "constructor", "prototype", "@type", "@class"], + cs: [ + "__proto__", + "constructor", + "prototype", + "$type", + "__type", + "@odata.type", + ], + vb: [ + "__proto__", + "constructor", + "prototype", + "$type", + "__type", + "@odata.type", + ], + fsharp: [ + "__proto__", + "constructor", + "prototype", + "$type", + "__type", + "@odata.type", + ], + php: [ + "__proto__", + "constructor", + "prototype", + "__construct", + "__destruct", + "__wakeup", + "__sleep", + "__serialize", + "__unserialize", + "__call", + "__callStatic", + "__get", + "__set", + "__isset", + "__unset", + "__toString", + "__invoke", + "__set_state", + "__clone", + "__debugInfo", + ], + objc: [ + "__proto__", + "constructor", + "prototype", + "isa", + "class", + "superclass", + "description", + "init", + "_cmd", + ], + swift: [ + "__proto__", + "constructor", + "prototype", + "isa", + "class", + "superclass", + "description", + "init", + "_cmd", + ], + ex: [ + "__proto__", + "constructor", + "prototype", + "__struct__", + "__exception__", + "__protocol__", + ], + lua: [ + "__proto__", + "constructor", + "prototype", + "__index", + "__newindex", + "__call", + "__metatable", + "__tostring", + "__name", + "__pairs", + "__eq", + "__lt", + "__le", + "__add", + "__sub", + "__mul", + "__div", + "__mod", + "__pow", + "__concat", + "__len", + "__unm", + "__band", + "__bor", + "__bxor", + "__bnot", + "__shl", + "__shr", + "__idiv", + "__close", + "__gc", + ], +}; + +describe("crawlSchema dangerous-name denylist", () => { + for (const [lang, names] of Object.entries( + EXPECTED_DANGEROUS_NAMES_BY_LANG, + )) { + for (const name of names) { + test(`lang="${lang}" flags property key ${JSON.stringify(name)}`, () => { + // Build via JSON.parse so a "__proto__" key is a real own property + // rather than the object prototype (an object literal would skip it). + const r = crawlSchema( + JSON.parse( + `{"properties":{${JSON.stringify(name)}:{"type":"string"}}}`, + ), + 32, + { lang }, + ); + ok( + r.errors.some( + (e) => e.keyword === "properties" && e.params.name === name, + ), + `expected lang="${lang}" to flag dangerous property name ${JSON.stringify(name)}`, + ); + }); + } + } + + // The "default" lang is the de-duplicated union of every language's list, so + // it must flag both a shared name and a name contributed by a single language + // (guards the spread/Set construction of DANGEROUS_NAMES_BY_LANG.default). + for (const name of ["__proto__", "constructor", "__bor", "@odata.type"]) { + test(`lang="default" flags property key ${JSON.stringify(name)}`, () => { + const r = crawlSchema( + JSON.parse( + `{"properties":{${JSON.stringify(name)}:{"type":"string"}}}`, + ), + 32, + { lang: "default" }, + ); + ok( + r.errors.some( + (e) => e.keyword === "properties" && e.params.name === name, + ), + `expected lang="default" to flag ${JSON.stringify(name)}`, + ); + }); + } +}); + +// Full error-shape locks for crawlSchema. Existing tests mostly assert `keyword`; +// these pin instancePath, schemaPath, params and message so that blanking any of +// them (a mutation) is caught. +describe("crawlSchema error payloads", () => { + const findByKeyword = (schema, keyword) => + crawlSchema(schema).errors.find((e) => e.keyword === keyword); + + test("minLength > maxLength error shape", () => { + const e = findByKeyword( + { type: "string", minLength: 10, maxLength: 5 }, + "minLength", + ); + strictEqual(e.instancePath, ""); + strictEqual(e.schemaPath, "#/minLength"); + strictEqual(e.params.minLength, 10); + strictEqual(e.params.maxLength, 5); + strictEqual(e.message, "minLength must be less than or equal to maxLength"); + }); + + test("minItems > maxItems error shape", () => { + const e = findByKeyword( + { type: "array", minItems: 10, maxItems: 3 }, + "minItems", + ); + strictEqual(e.schemaPath, "#/minItems"); + strictEqual(e.params.minItems, 10); + strictEqual(e.params.maxItems, 3); + strictEqual(e.message, "minItems must be less than or equal to maxItems"); + }); + + test("minContains > maxContains error shape", () => { + const e = findByKeyword( + { type: "array", minContains: 10, maxContains: 3 }, + "minContains", + ); + strictEqual(e.schemaPath, "#/minContains"); + strictEqual(e.params.minContains, 10); + strictEqual(e.params.maxContains, 3); + strictEqual( + e.message, + "minContains must be less than or equal to maxContains", + ); + }); + + test("minProperties > maxProperties error shape", () => { + const e = findByKeyword( + { type: "object", minProperties: 10, maxProperties: 5 }, + "minProperties", + ); + strictEqual(e.schemaPath, "#/minProperties"); + strictEqual(e.params.minProperties, 10); + strictEqual(e.params.maxProperties, 5); + strictEqual( + e.message, + "minProperties must be less than or equal to maxProperties", + ); + }); + + test("ReDoS pattern error shape", () => { + const e = findByKeyword({ type: "string", pattern: "(a+)+$" }, "pattern"); + strictEqual(e.instancePath, "/pattern"); + strictEqual(e.schemaPath, "#/redos"); + strictEqual(e.params.pattern, "(a+)+$"); + ok(typeof e.params.reason === "string" && e.params.reason.length > 0); + ok(typeof e.message === "string" && e.message.length > 0); + }); + + test("dangerous-name in properties error shape", () => { + const r = crawlSchema( + JSON.parse('{"properties":{"__proto__":{"type":"string"}}}'), + ); + const e = r.errors.find((x) => x.keyword === "properties"); + strictEqual(e.instancePath, "/properties/__proto__"); + strictEqual(e.schemaPath, "#/dangerous-name"); + strictEqual(e.params.name, "__proto__"); + strictEqual(e.params.lang, "default"); + strictEqual( + e.message, + 'properties key "__proto__" is a deserialization vector for lang="default"', + ); + }); + + test("dangerous-name in required error shape", () => { + const r = crawlSchema(JSON.parse('{"required":["constructor"]}')); + const e = r.errors.find((x) => x.keyword === "required"); + strictEqual(e.instancePath, "/required/0"); + strictEqual(e.schemaPath, "#/dangerous-name"); + strictEqual(e.params.name, "constructor"); + strictEqual( + e.message, + 'required entry "constructor" is a deserialization vector for lang="default"', + ); + }); + + test("dangerous-name in dependentRequired error shape", () => { + const r = crawlSchema( + JSON.parse('{"dependentRequired":{"a":["prototype"]}}'), + ); + const e = r.errors.find((x) => x.keyword === "dependentRequired"); + strictEqual(e.instancePath, "/dependentRequired/a/0"); + strictEqual(e.schemaPath, "#/dangerous-name"); + strictEqual(e.params.name, "prototype"); + strictEqual( + e.message, + 'dependentRequired entry "prototype" is a deserialization vector for lang="default"', + ); + }); + + test("ReDoS patternProperties key error shape", () => { + const r = crawlSchema( + JSON.parse('{"patternProperties":{"(a+)+$":{"type":"string"}}}'), + ); + const e = r.errors.find( + (x) => x.keyword === "patternProperties" && x.schemaPath === "#/redos", + ); + strictEqual(e.instancePath, "/patternProperties/(a+)+$"); + strictEqual(e.params.pattern, "(a+)+$"); + strictEqual( + e.message, + 'patternProperties key "(a+)+$" is vulnerable to ReDoS', + ); + }); + + test("dangerous-name patternProperties match error shape", () => { + const r = crawlSchema( + JSON.parse('{"patternProperties":{"^__proto__$":{"type":"string"}}}'), + ); + const e = r.errors.find( + (x) => + x.keyword === "patternProperties" && + x.schemaPath === "#/dangerous-name", + ); + strictEqual(e.instancePath, "/patternProperties/^__proto__$"); + strictEqual(e.params.pattern, "^__proto__$"); + ok(e.params.matches.includes("__proto__")); + strictEqual(e.params.lang, "default"); + strictEqual( + e.message, + 'patternProperties key "^__proto__$" matches deserialization vector(s) for lang="default": __proto__', + ); + }); +}); + +// Equal bounds are valid: the range checks use a strict `>` so tightening it to +// `>=` (a mutation) would flag a satisfiable range as impossible. +describe("crawlSchema equal-bound ranges are valid", () => { + const noError = (schema, keyword) => + ok(!crawlSchema(schema).errors.some((e) => e.keyword === keyword)); + + test("minLength === maxLength", () => + noError({ type: "string", minLength: 5, maxLength: 5 }, "minLength")); + test("minItems === maxItems", () => + noError({ type: "array", minItems: 5, maxItems: 5 }, "minItems")); + test("minContains === maxContains", () => + noError({ type: "array", minContains: 5, maxContains: 5 }, "minContains")); + test("minProperties === maxProperties", () => + noError( + { type: "object", minProperties: 5, maxProperties: 5 }, + "minProperties", + )); +}); + +// Type/own-property guards on the range checks: a non-array (or a schema missing +// one bound) must not trigger array/object range errors. +describe("crawlSchema range guards", () => { + test("minContains > maxContains ignored on non-array", () => { + ok( + !crawlSchema({ + type: "object", + minContains: 10, + maxContains: 3, + }).errors.some((e) => e.keyword === "minContains"), + ); + }); + test("minContains without maxContains does not error", () => { + ok( + !crawlSchema({ type: "array", minContains: 10 }).errors.some( + (e) => e.keyword === "minContains", + ), + ); + }); + test("dangerous name as the last required entry is still flagged", () => { + const r = crawlSchema(JSON.parse('{"required":["safe","__proto__"]}')); + const e = r.errors.find((x) => x.keyword === "required"); + strictEqual(e.params.name, "__proto__"); + strictEqual(e.instancePath, "/required/1"); + }); +}); + +// Targeted guards and boundaries in crawlSchema that blanket error-shape tests +// don't reach. +describe("crawlSchema guards and boundaries", () => { + test("non-object, non-null input returns the empty result", () => { + const r = crawlSchema("not-a-schema"); + strictEqual(r.depth, 0); + strictEqual(r.errors.length, 0); + strictEqual(r.refs.length, 0); + }); + + test("a non-string pattern is ignored (no ReDoS analysis)", () => { + const r = crawlSchema({ type: "string", pattern: 123 }); + ok(!r.errors.some((e) => e.keyword === "pattern")); + }); + + test("an unparseable pattern is reported as a parse-error ReDoS finding", () => { + const e = crawlSchema({ type: "string", pattern: "(" }).errors.find( + (x) => x.keyword === "pattern", + ); + ok(e, "expected a pattern finding for an unparseable regex"); + strictEqual(e.instancePath, "/pattern"); + strictEqual(e.schemaPath, "#/redos"); + strictEqual(e.params.reason, "parseError"); + strictEqual(e.message, "pattern could not be parsed for ReDoS analysis"); + }); + + test("required dangerous-name carries the requested lang, not the default", () => { + const e = crawlSchema(JSON.parse('{"required":["__send__"]}'), 32, { + lang: "rb", + }).errors.find((x) => x.keyword === "required"); + ok(e, "expected __send__ flagged for ruby"); + strictEqual(e.params.lang, "rb"); + ok(e.message.includes('lang="rb"')); + }); + + test("dependentRequired dangerous-name carries the requested lang", () => { + const e = crawlSchema( + JSON.parse('{"dependentRequired":{"a":["__send__"]}}'), + 32, + { lang: "rb" }, + ).errors.find((x) => x.keyword === "dependentRequired"); + ok(e, "expected __send__ flagged for ruby"); + strictEqual(e.params.lang, "rb"); + }); + + test("external $ref whose URL ends in '#' is still collected", () => { + const r = crawlSchema(JSON.parse('{"$ref":"http://evil.example/#"}')); + const ref = r.refs.find((x) => x.hostname === "evil.example"); + ok(ref, "expected the external $ref to be collected"); + strictEqual(ref.ref, "http://evil.example/#"); + strictEqual(ref.path, "/$ref"); + }); + + test("internal '#'-prefixed $ref is not collected as remote", () => { + const r = crawlSchema(JSON.parse('{"$ref":"#/$defs/foo"}')); + strictEqual(r.refs.length, 0); + }); + + test("a primitive property value is not descended into", () => { + // { foo: "bar" } — "bar" is a string, so the crawl must not recurse into it + // (recursing would bump the reported depth). + const r = crawlSchema({ type: "object", foo: "bar" }); + strictEqual(r.depth, 1); + }); + + test("depth exactly at the limit is not flagged as exceeded", () => { + // root(1) -> a(2) -> b(3): exactly maxDepth 3, must not be flagged. + const r = crawlSchema({ a: { b: { type: "string" } } }, 3); + strictEqual(r.depthExceeded, false); + }); + + test("depth one past the limit is flagged as exceeded", () => { + const r = crawlSchema({ a: { b: { c: { type: "string" } } } }, 3); + strictEqual(r.depthExceeded, true); + }); +}); + +describe("crawlSchema null-value and multi-match guards", () => { + test("a null property value is not descended into (typeof null is 'object')", () => { + // Must not throw: dropping the `value !== null` guard would push null onto + // the stack and then read null.type on the next iteration. + const r = crawlSchema({ type: "object", foo: null }); + strictEqual(r.depth, 1); + strictEqual(r.errors.length, 0); + }); + + test("patternProperties matching multiple dangerous names lists them comma-separated", () => { + // "^(__proto__|constructor)$" matches two denylist entries, exposing the + // ", " join separator in the message. + const r = crawlSchema( + JSON.parse( + '{"patternProperties":{"^(__proto__|constructor)$":{"type":"string"}}}', + ), + ); + const e = r.errors.find( + (x) => + x.keyword === "patternProperties" && + x.schemaPath === "#/dangerous-name", + ); + ok(e, "expected a dangerous-name patternProperties finding"); + ok(e.params.matches.includes("__proto__")); + ok(e.params.matches.includes("constructor")); + ok(e.message.includes("__proto__, constructor")); + }); +}); + +// Adversarial / malformed-input guards. These exercise exactly the edge cases +// crawlSchema's defensive type-guards exist for, so each both kills a mutant and +// documents a real robustness/security property (e.g. never analyze inherited or +// non-own keys, never crash on a null sub-schema). +describe("crawlSchema defensive guards", () => { + test("ReDoS analysis is independent of the denylist (lang:[] still scans)", () => { + const r = crawlSchema( + JSON.parse('{"patternProperties":{"(a+)+$":{}}}'), + 32, + { lang: [] }, + ); + ok(r.errors.some((e) => e.schemaPath === "#/redos")); + }); + + test("an inherited `pattern` is not analyzed (own-property only)", () => { + const proto = Object.create({ pattern: "(a+)+$" }); + ok(!crawlSchema(proto).errors.some((e) => e.schemaPath === "#/redos")); + }); + + test("a non-string `pattern` is ignored, not coerced to a regex", () => { + // String(["(a+)+$"]) === "(a+)+$"; the typeof guard must stop coercion. + const r = crawlSchema(JSON.parse('{"pattern":["(a+)+$"]}')); + ok(!r.errors.some((e) => e.schemaPath === "#/redos")); + }); + + test("a null denylist site does not crash the crawl", () => { + for (const key of [ + "properties", + "$defs", + "definitions", + "dependentSchemas", + "dependentRequired", + ]) { + const r = crawlSchema(JSON.parse(`{"${key}":null}`)); + ok(Array.isArray(r.errors), `${key}:null must not throw`); + } + }); + + test("a denylist site that is an array is not treated as a key map", () => { + const r = crawlSchema(JSON.parse('{"properties":["__proto__"]}')); + ok(!r.errors.some((e) => e.schemaPath === "#/dangerous-name")); + }); + + test("a null patternProperties/dependentRequired does not crash", () => { + ok( + Array.isArray( + crawlSchema(JSON.parse('{"patternProperties":null}')).errors, + ), + ); + ok( + Array.isArray( + crawlSchema(JSON.parse('{"dependentRequired":null}')).errors, + ), + ); + }); + + test("a non-string required entry is ignored", () => { + const r = crawlSchema(JSON.parse('{"required":[123,{"x":1}]}')); + ok(!r.errors.some((e) => e.schemaPath === "#/dangerous-name")); + }); + + test("an inherited or non-string $ref is not collected", () => { + ok( + crawlSchema(Object.create({ $ref: "https://x.example/s" })).refs + .length === 0, + ); + ok(crawlSchema(JSON.parse('{"$ref":123}')).refs.length === 0); + }); + + test("an internal '#'-prefixed $ref is not collected as remote", () => { + ok(crawlSchema(JSON.parse('{"$ref":"#/$defs/x"}')).refs.length === 0); + }); +}); + +// --- A1: ReDoS-analysis memory/work bounding (OOM defense) --- +// A single sub-1KB schema with multiple evil patternProperties keys used to OOM +// the scanner: the per-pattern work was unbounded (memory grew faster than the +// time deadline could fire), and the deadline was only checked once per stack +// pop, never between patterns. The fix is layered: (1) a per-pattern maxSteps +// bound, (2) a deadline check before each pattern analysis, (3) a hard cap on +// the total number of patterns analyzed. +describe("crawlSchema ReDoS-analysis bounds (A1)", () => { + const EVIL = "^(a|b|c|d|e|f|g|h|i|j|k|l|m|n|o|p|q|r|s|t|u|v|w|x|y|z)+"; + + test("exposes a heap-budget const and MAX_REDOS_PATTERNS cap", () => { + strictEqual(typeof REDOS_HEAP_BUDGET_BYTES, "number"); + ok(REDOS_HEAP_BUDGET_BYTES > 0, "REDOS_HEAP_BUDGET_BYTES must be positive"); + // Must sit below a single evil pattern's ~270MB footprint (so the breaker + // fires after the first one) yet leave headroom under a 600MB heap. + ok( + REDOS_HEAP_BUDGET_BYTES <= 256 * 1024 * 1024, + "REDOS_HEAP_BUDGET_BYTES must stay under 256MB so it bails before OOM", + ); + strictEqual(typeof MAX_REDOS_PATTERNS, "number"); + ok(MAX_REDOS_PATTERNS > 0, "MAX_REDOS_PATTERNS must be positive"); + }); + + // REGRESSION: legitimate complex-but-safe patterns must NOT be reported as + // ReDoS. maxSteps=100 wrongly fail-closed semver (hitMaxSteps); removing it + // restores the library-default verdict, which is SAFE for all of these. + test("does not flag legitimate safe patterns as ReDoS", () => { + const safePatterns = [ + // semver: SAFE at default maxSteps, but hitMaxSteps at maxSteps<=250. + "^(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)$", + // ISO date. + "^\\d{4}-\\d{2}-\\d{2}$", + // uuid. + "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$", + // slug. + "^[a-z0-9]+(?:-[a-z0-9]+)*$", + ]; + const patternProperties = {}; + for (const p of safePatterns) { + patternProperties[p] = { type: "string" }; + } + const r = crawlSchema({ patternProperties }); + const redos = r.errors.filter((e) => e.schemaPath === "#/redos"); + strictEqual( + redos.length, + 0, + `safe patterns must not be flagged; got: ${redos + .map((e) => e.params.pattern) + .join(", ")}`, + ); + // Also exercise the top-level `pattern` path for semver specifically. + const semver = crawlSchema({ + type: "string", + pattern: "^(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)$", + }); + strictEqual( + semver.errors.filter((e) => e.schemaPath === "#/redos").length, + 0, + "semver must not be flagged as ReDoS at the library default", + ); + }); + + // HEAP CIRCUIT BREAKER (primary memory bound): an injected memoryUsage that + // crosses an injected budget must stop analysis early and emit exactly one + // incomplete #/redos-budget finding that survives --ignore. + test("heap budget breaker stops analysis early and emits one incomplete finding", () => { + const patternProperties = {}; + for (let i = 0; i < 5; i++) { + patternProperties[`^safe${i}$`] = { type: "string" }; + } + // Baseline 100, then values stepping up by 50 per read. With a budget of + // 100, the breaker fires when current-baseline (100) > 100 is first true: + // reads are 100 (baseline), 150, 200, 250, ... so 250-100=150>100 trips on + // the 4th read (before the 3rd pattern, since the baseline read is read #1). + let calls = 0; + const memoryUsage = () => 100 + 50 * calls++; + const r = crawlSchema({ patternProperties }, 32, { + memoryUsage, + redosHeapBudgetBytes: 100, + }); + const budget = r.errors.filter( + (e) => e.schemaPath === "#/redos-budget" && e.keyword === "heap", + ); + strictEqual(budget.length, 1, "exactly one heap-budget finding"); + strictEqual(budget[0].params.incomplete, true, "must be marked incomplete"); + // Analysis stopped early: fewer than all 5 keys were analyzed. (Safe keys + // emit no #/redos finding, so we assert via the breaker firing + that the + // memoryUsage reader was called fewer times than 1 baseline + 5 patterns.) + ok( + calls < 6, + `analysis must stop early; memoryUsage called ${calls} times`, + ); + // Survives --ignore because it is incomplete. + const kept = crawlSchema({ patternProperties }, 32, { + memoryUsage: (() => { + let c = 0; + return () => 100 + 50 * c++; + })(), + redosHeapBudgetBytes: 100, + ignore: ["#/redos-budget"], + }); + // crawlSchema itself does not apply --ignore (analyze() does), so verify the + // finding shape carries incomplete:true, which applyIgnore honors. + strictEqual( + kept.errors.filter( + (e) => e.schemaPath === "#/redos-budget" && e.keyword === "heap", + ).length, + 1, + "heap-budget finding is present regardless of ignore", + ); + }); + + // Boundary pin for Stryker: exactly AT the budget (current-baseline == budget) + // must NOT trip the breaker (`<=` keeps the comparison false). The breaker reads + // heap once per analyzed pattern, so two patternProperties keys give a baseline + // read (100) then a second read (200): delta is EXACTLY the budget (100), which + // must NOT trip. Kills `<=`->`<` (which would fire at exactly the budget). + test("heap delta exactly equal to budget does not trip the breaker (<=)", () => { + let calls = 0; + // Read #1 (baseline) = 100; read #2 = 200 -> delta exactly 100 == budget. + const memoryUsage = () => (calls++ === 0 ? 100 : 200); + const r = crawlSchema( + { + patternProperties: { + "^safe1$": { type: "string" }, + "^safe2$": { type: "string" }, + }, + }, + 32, + { memoryUsage, redosHeapBudgetBytes: 100 }, + ); + strictEqual( + r.errors.filter((e) => e.schemaPath === "#/redos-budget").length, + 0, + "delta exactly equal to budget must not trip (<= keeps it false)", + ); + ok(calls >= 2, "the second pattern's read must have happened"); + }); + + // Companion to the boundary: delta = budget + 1 MUST trip, and the emitted heap + // finding's exact shape is pinned so the message StringLiteral and the + // incomplete BooleanLiteral mutants are killed. + test("heap delta of budget+1 trips and emits the exact heap finding", () => { + let calls = 0; + // Read #1 (baseline) = 100; read #2 = 201 -> delta 101 = budget + 1. + const memoryUsage = () => (calls++ === 0 ? 100 : 201); + const r = crawlSchema( + { + patternProperties: { + "^safe1$": { type: "string" }, + "^safe2$": { type: "string" }, + }, + }, + 32, + { memoryUsage, redosHeapBudgetBytes: 100 }, + ); + const heap = r.errors.filter( + (e) => e.schemaPath === "#/redos-budget" && e.keyword === "heap", + ); + strictEqual(heap.length, 1, "budget+1 must trip exactly once"); + strictEqual(heap[0].keyword, "heap"); + strictEqual(heap[0].schemaPath, "#/redos-budget"); + deepStrictEqual(heap[0].params, { budget: 100, incomplete: true }); + strictEqual(heap[0].params.incomplete, true); + strictEqual( + heap[0].message, + "ReDoS analysis heap budget of 100 bytes exceeded; remaining patterns not analyzed", + ); + }); + + // Single-fire guard: two SEPARATE pattern nodes both over budget must emit only + // ONE heap finding (the `!redosHeapReported` guard latches). Kills the + // ConditionalExpression (forcing `true` re-emits) and the BooleanLiteral + // (`redosHeapReported = false` never latches, so it re-emits). + test("the heap breaker reports at most once across multiple over-budget pattern nodes", () => { + let calls = 0; + // First analyzed pattern (read #1) = 0 baseline; every later read = 1e6, + // far over the budget, so the SECOND and THIRD pattern nodes both exceed it. + const memoryUsage = () => (calls++ === 0 ? 0 : 1_000_000); + const r = crawlSchema( + { + properties: { + a: { type: "string", pattern: "a" }, + b: { type: "string", pattern: "b" }, + c: { type: "string", pattern: "c" }, + }, + }, + 32, + { memoryUsage, redosHeapBudgetBytes: 100 }, + ); + const heap = r.errors.filter( + (e) => e.schemaPath === "#/redos-budget" && e.keyword === "heap", + ); + strictEqual( + heap.length, + 1, + "the heap finding must be emitted exactly once even with multiple over-budget nodes", + ); + // The heap finding's instancePath is the tripping node's `${path}/pattern`, + // never the empty string (pins the path template passed to redosHeapExceeded). + ok( + /^\/properties\/[abc]\/pattern$/.test(heap[0].instancePath), + `heap instancePath must name the tripping pattern node, got "${heap[0].instancePath}"`, + ); + }); + + // PRIMARY (heap breaker, OOM repro): many distinct evil patternProperties keys + // each grow the heap by ~270MB at the library default. The heap circuit breaker + // (default budget) must bail after the first one so the crawl returns WITHOUT + // crashing, emitting one incomplete heap-budget finding. (The manual 600MB + // check in the PR confirms no OOM; here the larger test-process heap simply + // proves we return cleanly and the breaker fired.) + test("40 distinct evil patternProperties keys return without crashing (heap breaker)", () => { + const patternProperties = {}; + for (let i = 0; i < 40; i++) { + patternProperties[`${EVIL}${i}$`] = { type: "string" }; + } + const r = crawlSchema({ patternProperties }); + strictEqual(r.timedOut, false, "must not have tripped the deadline"); + // The heap breaker bailed: at least one evil pattern was flagged before the + // budget tripped, and exactly one incomplete heap-budget finding is present. + const heap = r.errors.filter( + (e) => e.schemaPath === "#/redos-budget" && e.keyword === "heap", + ); + strictEqual(heap.length, 1, "the heap breaker must fire exactly once"); + strictEqual(heap[0].params.incomplete, true, "heap finding is incomplete"); + const redos = r.errors.filter( + (e) => e.keyword === "patternProperties" && e.schemaPath === "#/redos", + ); + ok( + redos.length >= 1 && redos.length < 40, + `breaker must stop early after flagging some keys; got ${redos.length}`, + ); + }); + + // Layer 1 also applies to top-level `pattern`: a single evil pattern is + // flagged fail-closed and completes quickly under the step bound. + test("a top-level evil pattern is flagged fail-closed under the step bound", () => { + const r = crawlSchema({ type: "string", pattern: `${EVIL}$` }); + const err = r.errors.find((e) => e.keyword === "pattern"); + ok(err, "expected a ReDoS finding for the evil pattern"); + strictEqual(err.schemaPath, "#/redos"); + }); + + // Once-per-pop deadline (top of the stack loop): an already-passed deadline + // must bail on the FIRST pop, before ANY structural check runs. The schema has + // NO pattern, so the per-pattern deadline guards cannot mask this one; a node + // with minLength > maxLength would emit a finding if the loop body ran. Killing + // the once-per-pop ConditionalExpression: bailing means the timeout finding is + // present AND the minLength finding is absent. + test("an already-passed deadline bails on the first pop before any structural check", () => { + const r = crawlSchema({ type: "string", minLength: 5, maxLength: 1 }, 32, { + deadline: 0, + }); + ok( + r.errors.some((e) => e.keyword === "timeout"), + "must emit the timeout finding on the first pop", + ); + strictEqual(r.timedOut, true); + ok( + !r.errors.some((e) => e.keyword === "minLength"), + "must bail BEFORE the minLength<=maxLength structural check runs", + ); + strictEqual(r.errors.length, 1, "only the timeout finding is emitted"); + }); + + // Layer 2: the deadline is checked before each top-level pattern analysis, so + // an already-passed deadline bails to the timeout path BEFORE any ReDoS work. + test("an already-passed deadline bails before analyzing a top-level pattern", () => { + const r = crawlSchema({ type: "string", pattern: `${EVIL}$` }, 32, { + deadline: 0, + }); + ok( + r.errors.some((e) => e.keyword === "timeout"), + "must emit the timeout finding", + ); + strictEqual(r.timedOut, true); + ok( + !r.errors.some((e) => e.keyword === "pattern"), + "must NOT have run ReDoS analysis after the deadline", + ); + }); + + // Layer 2: the deadline is also checked inside the patternProperties key loop. + test("an already-passed deadline bails before analyzing patternProperties keys", () => { + const r = crawlSchema( + { patternProperties: { [`${EVIL}1$`]: { type: "string" } } }, + 32, + { deadline: 0 }, + ); + ok( + r.errors.some((e) => e.keyword === "timeout"), + "must emit the timeout finding", + ); + strictEqual(r.timedOut, true); + ok( + !r.errors.some((e) => e.schemaPath === "#/redos"), + "must NOT have run ReDoS analysis after the deadline", + ); + }); + + // Layer 3 (defense in depth): a hard cap on the TOTAL number of patterns + // analyzed. Once exceeded, no further patterns are analyzed and exactly one + // fail-closed budget finding is emitted. + // SAFE keys (cheap, never trip the heap breaker) isolate the count cap. A + // non-tripping memoryUsage is injected so the heap breaker is out of the way. + // The reader is called once per ANALYZED pattern (plus the baseline read), so + // the call count proves analysis stops exactly at the cap. + test("exceeding MAX_REDOS_PATTERNS stops analysis and emits one incomplete budget finding", () => { + const patternProperties = {}; + const total = MAX_REDOS_PATTERNS + 5; + for (let i = 0; i < total; i++) { + patternProperties[`^ok${i}$`] = { type: "string" }; + } + let reads = 0; + const memoryUsage = () => { + reads++; + return 0; // never grows, so the heap breaker never fires + }; + const r = crawlSchema({ patternProperties }, 32, { memoryUsage }); + const budget = r.errors.filter((e) => e.schemaPath === "#/redos-budget"); + strictEqual(budget.length, 1, "exactly one budget finding"); + strictEqual(budget[0].keyword, "pattern"); + strictEqual(budget[0].params.incomplete, true, "count cap is incomplete"); + ok( + budget[0].message.includes(String(MAX_REDOS_PATTERNS)), + "budget message names the cap", + ); + // Per-pattern heap reads stop once the count cap fires. The count check + // runs BEFORE the heap read and short-circuits it, so the reader is called + // exactly once per analyzed pattern (MAX_REDOS_PATTERNS) and never for the + // skipped keys (the first call also captures the baseline). + strictEqual( + reads, + MAX_REDOS_PATTERNS, + "only the first MAX_REDOS_PATTERNS keys are analyzed; the rest are skipped", + ); + }); + + // Single-fire guard for the COUNT cap: two over-cap top-level `pattern` nodes + // (the `pattern` path does NOT break on a tripped budget, unlike the + // patternProperties path) must still emit only ONE budget finding. Kills the + // `if (!redosBudgetReported)` ConditionalExpression (forcing `true` re-emits). + test("the count cap reports exactly once across two over-cap pattern nodes", () => { + const properties = {}; + // MAX analyzed + 2 over-cap nodes. Distinct safe patterns so each is its own + // analyzed pattern and none is flagged as ReDoS. + for (let i = 0; i < MAX_REDOS_PATTERNS + 2; i++) { + properties[`p${i}`] = { type: "string", pattern: `lit${i}` }; + } + const r = crawlSchema({ properties }, 32, { memoryUsage: () => 0 }); + const budget = r.errors.filter((e) => e.schemaPath === "#/redos-budget"); + strictEqual( + budget.length, + 1, + "two over-cap pattern nodes must still emit exactly one budget finding", + ); + strictEqual(budget[0].keyword, "pattern"); + strictEqual(budget[0].params.incomplete, true); + // The budget finding's instancePath is the over-cap node's `${path}/pattern`, + // never the empty string (pins the path template passed to redosBudgetExceeded). + ok( + /^\/properties\/p\d+\/pattern$/.test(budget[0].instancePath), + `budget instancePath must name the over-cap pattern node, got "${budget[0].instancePath}"`, + ); + }); + + // The cap counts top-level `pattern` analyses too (shared budget), so a schema + // at exactly the cap of patterns does NOT emit a budget finding (strict >). + test("exactly MAX_REDOS_PATTERNS patterns does not trip the budget (strict >)", () => { + const patternProperties = {}; + for (let i = 0; i < MAX_REDOS_PATTERNS; i++) { + patternProperties[`^ok${i}$`] = { type: "string" }; + } + const r = crawlSchema({ patternProperties }, 32, { memoryUsage: () => 0 }); + ok( + !r.errors.some((e) => e.schemaPath === "#/redos-budget"), + "exactly-at-cap must not trip the budget", + ); + }); +}); + +// --- A4: collected refs are capped before the hostname cap --- +// crawlSchema used to push every remote $ref/$dynamicRef into result.refs with +// no bound; the hostname cap only applies later on the distinct-hostname map. +// A hard cap on result.refs length is a backstop (overall bounded by +// MAX_SCHEMA_SIZE) that stops collecting once exceeded and records one finding. +describe("crawlSchema collected-refs cap (A4)", () => { + test("exposes a positive MAX_COLLECTED_REFS const", () => { + strictEqual(typeof MAX_COLLECTED_REFS, "number"); + ok(MAX_COLLECTED_REFS > 0, "MAX_COLLECTED_REFS must be positive"); + }); + + // Build a schema carrying MORE distinct remote refs than the cap, nested so the + // crawl reaches them all. Each ref is to a distinct host so none de-duplicate. + const manyRefsSchema = (n) => { + const $defs = {}; + for (let i = 0; i < n; i++) { + $defs[`d${i}`] = { $ref: `https://refcap-${i}.invalid/s.json` }; + } + return { $defs }; + }; + + test("result.refs is capped at MAX_COLLECTED_REFS and a truncation finding is recorded", () => { + const r = crawlSchema(manyRefsSchema(MAX_COLLECTED_REFS + 10)); + ok( + r.refs.length <= MAX_COLLECTED_REFS, + `refs (${r.refs.length}) must not exceed the cap (${MAX_COLLECTED_REFS})`, + ); + const trunc = r.errors.filter((e) => e.schemaPath === "#/refs-truncated"); + strictEqual(trunc.length, 1, "exactly one truncation finding"); + ok( + trunc[0].message.includes(String(MAX_COLLECTED_REFS)), + "truncation message names the cap", + ); + // Pin the full finding shape so the instancePath/keyword StringLiteral and the + // params ObjectLiteral / incomplete BooleanLiteral mutants are all killed. + const t = trunc[0]; + strictEqual(t.schemaPath, "#/refs-truncated"); + strictEqual(t.keyword, "$ref"); + // instancePath is the path to the ref that first hit the cap; it must be a + // real JSON pointer ending at a $ref/$dynamicRef site, never the empty string. + ok(t.instancePath.length > 0, "instancePath must not be empty"); + ok( + /\/\$(ref|dynamicRef)$/.test(t.instancePath), + `instancePath must point at a ref site, got ${t.instancePath}`, + ); + deepStrictEqual(t.params, { + limit: MAX_COLLECTED_REFS, + incomplete: true, + }); + strictEqual(t.params.incomplete, true); + strictEqual( + t.message, + `more than ${MAX_COLLECTED_REFS} remote $ref(s); remaining refs not collected for SSRF analysis`, + ); + }); + + test("a schema with refs at or under the cap collects them all with no truncation finding", () => { + const r = crawlSchema(manyRefsSchema(MAX_COLLECTED_REFS)); + strictEqual(r.refs.length, MAX_COLLECTED_REFS, "all refs collected"); + ok( + !r.errors.some((e) => e.schemaPath === "#/refs-truncated"), + "no truncation finding at exactly the cap (strict >)", + ); + }); +}); diff --git a/tests/cli.fuzz.js b/tests/cli.fuzz.js new file mode 100644 index 0000000..7d4452f --- /dev/null +++ b/tests/cli.fuzz.js @@ -0,0 +1,210 @@ +import test from "node:test"; +import fc from "fast-check"; +import { analyze, crawlSchema, isPrivateIP, MAX_DEPTH } from "../cli.js"; + +// Recursively-generated, schema-shaped objects: keys are drawn from real JSON +// Schema keywords and leaves include adversarial strings (prototype-pollution +// vectors, catastrophic-backtracking regexes, remote $ref URLs). This is the +// untrusted attack surface the analysis engine ingests, so the properties below +// assert the engine's output CONTRACT rather than any specific finding. + +const SCHEMA_KEYWORDS = [ + "type", + "properties", + "patternProperties", + "required", + "$ref", + "$dynamicRef", + "pattern", + "minimum", + "maximum", + "minLength", + "maxLength", + "items", + "const", + "enum", + "default", + "$defs", + "additionalProperties", + "propertyNames", + "allOf", + "anyOf", + "minItems", + "maxItems", + "dependentRequired", +]; + +const TYPE_NAMES = [ + "string", + "integer", + "number", + "array", + "object", + "boolean", + "null", +]; + +const ADVERSARIAL_STRINGS = [ + "__proto__", + "constructor", + "prototype", + ".*", + "(a+)+$", + "^(a|a)*$", + "^([a-z]+)*$", + "https://x.example.com/s.json", + "http://127.0.0.1/s.json", + "#/$defs/x", + "", +]; + +const leafValue = fc.oneof( + fc.string({ maxLength: 12 }), + fc.integer({ min: -1000, max: 1000 }), + fc.boolean(), + fc.constant(null), + fc.constantFrom(...TYPE_NAMES), + fc.constantFrom(...ADVERSARIAL_STRINGS), +); + +// A bounded, recursive arbitrary producing schema-shaped objects. Object keys +// are biased toward real keywords; values are nested schema-shaped objects, +// arrays of them, or leaf values. +const schemaArb = fc.letrec((tie) => ({ + node: fc.oneof( + { maxDepth: 5, withCrossShrink: true }, + leafValue, + fc.array(tie("node"), { maxLength: 4 }), + fc.dictionary( + fc.oneof( + fc.constantFrom(...SCHEMA_KEYWORDS), + fc.constantFrom(...ADVERSARIAL_STRINGS), + fc.string({ maxLength: 8 }), + ), + tie("node"), + { maxKeys: 6 }, + ), + ), +})).node; + +// Documented input-validation errors analyze() is allowed to throw. Any OTHER +// throw is an engine bug and fails the property. +const isDocumentedThrow = (err) => + err instanceof TypeError || err instanceof RangeError; + +const assertErrorContract = (errors) => { + for (const err of errors) { + if (typeof err.instancePath !== "string") { + throw new Error( + `error.instancePath not a string: ${JSON.stringify(err)}`, + ); + } + if (typeof err.schemaPath !== "string") { + throw new Error(`error.schemaPath not a string: ${JSON.stringify(err)}`); + } + if (typeof err.keyword !== "string") { + throw new Error(`error.keyword not a string: ${JSON.stringify(err)}`); + } + } +}; + +test("fuzz: analyze() never throws (except documented) and resolves to an Array", async () => { + await fc.assert( + fc.asyncProperty(schemaArb, async (schema) => { + let errors; + try { + // offline:true => NO network/DNS is ever performed on untrusted input. + errors = await analyze(schema, { + offline: true, + analysisTimeoutMs: 2000, + }); + } catch (err) { + if (isDocumentedThrow(err)) return; + throw new Error( + `analyze threw undocumented ${err?.constructor?.name}: ${err?.message} for ${JSON.stringify(schema)}`, + ); + } + if (!Array.isArray(errors)) { + throw new Error( + `analyze did not resolve to an Array for ${JSON.stringify(schema)}`, + ); + } + assertErrorContract(errors); + }), + { numRuns: 1000 }, + ); +}); + +test("fuzz: crawlSchema() never throws and honours its result contract", () => { + fc.assert( + fc.property(schemaArb, (schema) => { + let result; + try { + result = crawlSchema(schema); + } catch (err) { + throw new Error( + `crawlSchema threw ${err?.constructor?.name}: ${err?.message} for ${JSON.stringify(schema)}`, + ); + } + if (result === null || typeof result !== "object") { + throw new Error( + `crawlSchema did not return an object for ${JSON.stringify(schema)}`, + ); + } + if (!Array.isArray(result.errors)) { + throw new Error("crawlSchema().errors is not an Array"); + } + if (!Array.isArray(result.refs)) { + throw new Error("crawlSchema().refs is not an Array"); + } + if (typeof result.depth !== "number") { + throw new Error("crawlSchema().depth is not a number"); + } + // The crawler bails the moment depth would exceed MAX_DEPTH, so the + // reported depth can reach MAX_DEPTH+1 (the over-cap level that triggered + // the bail) but never more. + if (result.depth > MAX_DEPTH + 1) { + throw new Error( + `crawlSchema().depth ${result.depth} exceeds MAX_DEPTH+1 (${MAX_DEPTH + 1})`, + ); + } + assertErrorContract(result.errors); + }), + { numRuns: 1000 }, + ); +}); + +test("fuzz: isPrivateIP() never throws and always returns a boolean", () => { + const ipv4Arb = fc + .tuple( + fc.integer({ min: 0, max: 300 }), + fc.integer({ min: 0, max: 300 }), + fc.integer({ min: 0, max: 300 }), + fc.integer({ min: 0, max: 300 }), + ) + .map((octets) => octets.join(".")); + + const hexGroup = fc + .integer({ min: 0, max: 0x1ffff }) + .map((n) => n.toString(16)); + const ipv6Arb = fc + .array(hexGroup, { minLength: 0, maxLength: 9 }) + .map((groups) => groups.join(":")); + + fc.assert( + fc.property(fc.oneof(ipv4Arb, ipv6Arb), (ip) => { + let out; + try { + out = isPrivateIP(ip); + } catch (err) { + throw new Error( + `isPrivateIP threw ${err?.constructor?.name}: ${err?.message} for "${ip}"`, + ); + } + if (typeof out !== "boolean") { + throw new Error(`isPrivateIP("${ip}") returned non-boolean: ${out}`); + } + }), + { numRuns: 1000 }, + ); +}); diff --git a/tests/cli.ip.test.js b/tests/cli.ip.test.js index ef1aeca..b43276c 100644 --- a/tests/cli.ip.test.js +++ b/tests/cli.ip.test.js @@ -110,3 +110,156 @@ describe("isPrivateIP IPv6 extended", () => { strictEqual(isPrivateIP("0:0:0:0:0:ffff:7f00:1"), true); }); }); + +// Each address sits just outside one reserved range, so it is public only while +// that range's bounds are intact. They lock the exact CIDR edges: broadening any +// single conjunct (an octet test flipped to always-true) would misclassify one of +// these as private, which is an SSRF allow-list hole. +describe("isPrivateIP near-miss public addresses", () => { + const nearMissPublic = [ + ["10.2.3.999", "octet above 255 voids the dotted-quad match"], + ["10.2.3.-5", "negative octet voids the dotted-quad match"], + ["100.0.0.1", "100.x below CGN b>=64"], + ["100.200.0.1", "100.x above CGN b<=127"], + ["1.254.0.1", "b=254 but a!=169 (not link-local)"], + ["169.0.0.1", "a=169 but b!=254 (not link-local)"], + ["172.0.0.1", "172.x below private b>=16"], + ["172.200.0.1", "172.x above private b<=31"], + ["192.5.0.1", "a=192 but b not 0/168"], + ["192.0.5.1", "192.0.x with third octet not 0/2"], + ["192.5.2.1", "third octet 2 but b!=0 (not TEST-NET-1)"], + ["198.0.0.1", "a=198 but b not 18/19/51"], + ["198.0.100.1", "third octet 100 but b!=51 (not TEST-NET-2)"], + ["198.51.5.1", "a=198 b=51 but third octet!=100"], + ["203.5.113.1", "third octet 113 but b!=0 (not TEST-NET-3)"], + ["203.0.5.1", "a=203 b=0 but third octet!=113"], + // Wrong first octet but otherwise-matching range: the leading `a === N` + // check must hold, or the whole range collapses to "any IP whose other + // octets match" (e.g. a CGN match for any b in 64..127). + ["8.100.0.1", "b in CGN range but a!=100"], + ["8.20.0.1", "b in 172-private range but a!=172"], + ["8.0.2.1", "b=0 third=2 but a!=192 (not TEST-NET-1)"], + ["8.0.0.1", "b=0 third=0 but a!=192 (not IETF 192.0.0.0/24)"], + ["8.168.0.1", "b=168 but a!=192 (not 192.168/16)"], + ["8.18.0.1", "b in benchmark range but a!=198"], + ["8.51.100.1", "b=51 third=100 but a!=198 (not TEST-NET-2)"], + ["8.0.113.1", "b=0 third=113 but a!=203 (not TEST-NET-3)"], + ["fc00:1", "two-group malformed IPv6 is not expanded to a ULA"], + ["::ffff:0808:0808", "IPv4-mapped hex resolving to public 8.8.8.8"], + ]; + for (const [ip, desc] of nearMissPublic) { + test(`should allow public: ${ip} (${desc})`, () => { + strictEqual(isPrivateIP(ip), false); + }); + } +}); + +// Addresses sitting exactly on an inclusive CIDR edge: tightening a `<=`/`>=` to a +// strict comparison, or breaking IPv6 leading-zero/`::` normalization, would drop +// them from the private set. +describe("isPrivateIP inclusive boundaries and IPv6 normalization", () => { + const boundaryPrivate = [ + ["100.127.0.1", "CGN upper bound b<=127"], + ["172.31.0.1", "172.16.0.0/12 upper bound b<=31"], + ["::0001", "compressed loopback with leading-zero group"], + ["::0:1", "compressed loopback via right-hand groups"], + // Compressed IPv4-mapped loopback: the per-group zero strip in the `::` + // branch must be anchored (^), or "7f00" loses an interior zero and the + // mapped IPv4 decodes to a public address. + ["::ffff:7f00:1", "compressed IPv4-mapped loopback (::ffff:7f00:1)"], + // Malformed 5-hex-digit link-local group: an over-long group parses to a + // value > 0xffff, which the first-group guard rejects fail-closed. + ["fe800::1", "malformed fe80-prefixed group still private (fail-closed)"], + ]; + for (const [ip, desc] of boundaryPrivate) { + test(`should detect private: ${ip} (${desc})`, () => { + strictEqual(isPrivateIP(ip), true); + }); + } +}); + +// IPv6 ranges added: NAT64 64:ff9b::/96, 6to4 2002::/16, link-local fe80::/10, +// site-local fec0::/10, documentation 2001:db8::/32. Each row pins one boundary +// edge so broadening a prefix/range conjunct misclassifies it as a SSRF hole. +describe("isPrivateIP IPv6 extended ranges", () => { + const privateRows = [ + // NAT64 64:ff9b::/96 embeds an IPv4 in the last two hex groups. + ["64:ff9b::7f00:1", "NAT64 embeds 127.0.0.1 (private)"], + // 6to4 2002::/16 embeds an IPv4 in groups 1 and 2. + ["2002:7f00:1::", "6to4 embeds 127.0.0.1 (private)"], + // Link-local fe80::/10 spans fe80-febf, not just literal fe80. + ["fe80::", "link-local lower edge fe80"], + ["febf::", "link-local upper edge febf"], + // Site-local fec0::/10 spans fec0-feff. + ["fec0::", "site-local lower edge fec0"], + ["feff::", "site-local upper edge feff"], + ["ff00::", "multicast lower edge (existing)"], + // Documentation 2001:db8::/32. + ["2001:db8::", "documentation 2001:db8::/32"], + ]; + for (const [ip, desc] of privateRows) { + test(`should detect private: ${ip} (${desc})`, () => { + strictEqual(isPrivateIP(ip), true); + }); + } + + const publicRows = [ + ["64:ff9b::808:808", "NAT64 embeds 8.8.8.8 (public)"], + ["64:ff9a::7f00:1", "wrong NAT64 prefix (group1 != ff9b)"], + ["2002:808:808::", "6to4 embeds 8.8.8.8 (public)"], + ["fe7f::", "just below link-local fe80"], + ["2001:db7::", "just below documentation db8"], + ["2001:db9::", "just above documentation db8"], + ]; + for (const [ip, desc] of publicRows) { + test(`should allow public: ${ip} (${desc})`, () => { + strictEqual(isPrivateIP(ip), false); + }); + } + + // Fail-closed: malformed hex in the embedded NAT64/6to4 IPv4 groups parses to + // NaN; bit-math on NaN would forge a public-looking IPv4, so block instead. + test("NAT64 with malformed embedded hex is private (fail-closed)", () => { + strictEqual(isPrivateIP("64:ff9b::zzzz:1"), true); + }); + + test("6to4 with malformed embedded hex is private (fail-closed)", () => { + strictEqual(isPrivateIP("2002:zzzz:1::"), true); + }); + + // Full-form (no `::`) addresses with leading-zero groups exercise the non-`::` + // per-group leading-zero strip. The strip must be anchored `^0+(?=.)` (keep at + // least one digit) so an interior group like "0db8" normalizes to "db8": + // - mutating the regex to `^0+(?!.)` or the replacement to a literal leaves + // "0db8" intact, so the 2001:db8 documentation match fails and the address + // is misclassified as PUBLIC. Asserting it is PRIVATE kills both mutants. + test("full-form 2001:0db8 documentation address (no ::) is private after leading-zero strip", () => { + strictEqual(isPrivateIP("2001:0db8:0000:0000:0000:0000:0000:0001"), true); + }); + + // A full-form PUBLIC address whose first group is NOT 2001 (and second not db8) + // must stay public. Forcing the `groups[0] === "2001" && groups[1] === "db8"` + // documentation conjunct to `true` would misclassify it as private; asserting + // public kills that ConditionalExpression mutant. + test("full-form public IPv6 (2606:4700:..) is not private (kills 2001/db8 conditional)", () => { + strictEqual(isPrivateIP("2606:4700:4700:0000:0000:0000:0000:1111"), false); + }); + + // The documentation match requires BOTH conjuncts: a public address whose + // SECOND group is "db8" but whose FIRST group is NOT 2001 (here 2003) must stay + // public. Mutating the left conjunct `groups[0] === "2001"` to `true` would + // classify it private on the db8 second group alone; asserting public kills it. + test("full-form db8 second group but non-2001 first group stays public (kills left conjunct)", () => { + strictEqual(isPrivateIP("2003:0db8:0000:0000:0000:0000:0000:0001"), false); + }); + + // Non-hex first group (not fc/fd/ff) parses to NaN; fail-closed to private. + test("malformed non-hex first group is private (fail-closed)", () => { + strictEqual(isPrivateIP("gggg::1"), true); + }); + + // Over-long first group (> 0xffff) is malformed IPv6; fail-closed to private. + test("over-long first group is private (fail-closed)", () => { + strictEqual(isPrivateIP("10000::1"), true); + }); +}); diff --git a/tests/cli.perf.js b/tests/cli.perf.js new file mode 100644 index 0000000..782b3a9 --- /dev/null +++ b/tests/cli.perf.js @@ -0,0 +1,73 @@ +import test from "node:test"; +import { Bench } from "tinybench"; +import { analyze, crawlSchema, MAX_DEPTH } from "../cli.js"; + +// Benchmarks the ANALYSIS ENGINE (analyze / crawlSchema), the code that ingests +// untrusted input. Like index.perf.js these are tracked numbers, not gated +// thresholds: the suite only fails if a benchmark throws. All runs use +// offline:true so no network/DNS occurs. + +test("perf: analyze engine benchmarks", async () => { + const suite = new Bench({ name: "sast-json-schema-engine" }); + + const smallCleanSchema = { + $schema: "https://json-schema.org/draft/2020-12/schema", + type: "object", + properties: { + name: { type: "string", maxLength: 100, pattern: "^[a-z]+$" }, + age: { type: "integer", minimum: 0, maximum: 150 }, + }, + required: ["name"], + unevaluatedProperties: false, + }; + + // 200 distinct SIMPLE, safe patternProperties keys. This is the regression + // watch for the heap circuit breaker: analyze() calls process.memoryUsage() + // before EVERY pattern, so this case measures that per-pattern overhead on a + // realistic many-pattern schema that should never trip the breaker. + const manyPatternProps = {}; + for (let i = 0; i < 200; i++) { + // Each pattern is distinct and trivially safe (anchored literal prefix). + manyPatternProps[`^k${i}_[a-z0-9]{1,8}$`] = { + type: "string", + maxLength: 64, + }; + } + const manyPatternsSchema = { + $schema: "https://json-schema.org/draft/2020-12/schema", + type: "object", + patternProperties: manyPatternProps, + }; + + // A linear chain of nested object properties reaching close to MAX_DEPTH. + // Each `properties`+child level adds two depth steps, so build ~MAX_DEPTH-2 + // levels to stay just under the bail cap (so the full tree is crawled). + const buildDeepSchema = (levels) => { + let node = { type: "string", maxLength: 10 }; + for (let i = 0; i < levels; i++) { + node = { + type: "object", + properties: { child: node }, + }; + } + return node; + }; + const deepSchema = buildDeepSchema(Math.floor((MAX_DEPTH - 2) / 2)); + + suite + .add("analyze small clean schema", async () => { + await analyze(smallCleanSchema, { offline: true }); + }) + .add( + "analyze 200 simple patternProperties (heap-breaker watch)", + async () => { + await analyze(manyPatternsSchema, { offline: true }); + }, + ) + .add("crawlSchema deeply-nested schema near MAX_DEPTH", () => { + crawlSchema(deepSchema); + }); + + await suite.run(); + console.table(suite.table()); +}); diff --git a/tests/cli.run.test.js b/tests/cli.run.test.js new file mode 100644 index 0000000..3e2ab13 --- /dev/null +++ b/tests/cli.run.test.js @@ -0,0 +1,572 @@ +import { ok, rejects, strictEqual } from "node:assert"; +import { describe, test } from "node:test"; +import { run } from "../cli.js"; +import pkg from "../package.json" with { type: "json" }; + +// Drives the CLI entrypoint in-process with injected I/O, so the whole arg +// parsing / file reading / output formatting path is unit-testable (the spawned +// subprocess tests in cli.test.js can't attribute coverage to it). +const CLEAN = JSON.stringify({ + $schema: "https://json-schema.org/draft/2020-12/schema", + $id: "https://example.test/clean.json", + type: "string", + maxLength: 10, + pattern: "^[a-z]+$", +}); +const DIRTY = JSON.stringify({ + $schema: "https://json-schema.org/draft/2020-12/schema", + $id: "https://example.test/dirty.json", + type: "string", +}); + +// files: map matched by path suffix -> file content; sizes: suffix -> byte size. +const runCli = async (argv, { files = {}, sizes = {} } = {}) => { + const out = { log: [], error: [], write: [] }; + const match = (map, p) => { + const key = Object.keys(map).find((k) => p.endsWith(k)); + return key === undefined ? undefined : map[key]; + }; + const io = { + log: (m) => out.log.push(String(m)), + error: (m) => out.error.push(String(m)), + write: (s) => out.write.push(String(s)), + readFile: async (p) => { + const c = match(files, p); + if (c === undefined) { + const e = new Error(`ENOENT: no such file, open '${p}'`); + throw e; + } + return c; + }, + stat: async (p) => { + const s = match(sizes, p); + if (s === undefined) { + if (match(files, p) !== undefined) + return { size: match(files, p).length }; + throw new Error(`ENOENT: no such file, stat '${p}'`); + } + return { size: s }; + }, + }; + const code = await run(argv, io); + return { code, ...out }; +}; + +describe("run() argument handling", () => { + test("--help prints usage and exits 0", async () => { + const r = await runCli(["--help"]); + strictEqual(r.code, 0); + ok(r.log.join("\n").includes("Usage: sast-json-schema [options] ")); + ok(r.log.join("\n").includes("--format ")); + }); + + test("-h is the help alias", async () => { + const r = await runCli(["-h"]); + strictEqual(r.code, 0); + ok(r.log.join("\n").includes("Usage:")); + }); + + test("--version prints the package version and exits 0", async () => { + const r = await runCli(["--version"]); + strictEqual(r.code, 0); + strictEqual(r.log.join(""), pkg.version); + }); + + test("-v is the version alias", async () => { + const r = await runCli(["-v"]); + strictEqual(r.code, 0); + strictEqual(r.log.join(""), pkg.version); + }); + + test("an unknown flag is a usage error (exit 2)", async () => { + const r = await runCli(["--no-such-flag", "x.json"]); + strictEqual(r.code, 2); + ok(r.error.join("\n").startsWith("Error: ")); + }); + + test("an invalid --format is rejected", async () => { + const r = await runCli(["--format", "xml", "x.json"]); + strictEqual(r.code, 2); + ok( + r.error + .join("\n") + .includes('--format must be "human", "json", or "sarif", got "xml"'), + ); + }); + + test("an invalid --lang is rejected", async () => { + const r = await runCli(["--lang", "elvish", "x.json"]); + strictEqual(r.code, 2); + ok(r.error.join("\n").includes("--lang must be one of")); + ok(r.error.join("\n").includes('got "elvish"')); + }); + + test("a missing file argument is a usage error", async () => { + const r = await runCli([]); + strictEqual(r.code, 2); + ok(r.error.join("\n").includes("missing required argument ")); + }); +}); + +describe("run() file handling", () => { + test("an unreadable file exits 2", async () => { + const r = await runCli(["missing.json"]); + strictEqual(r.code, 2); + ok(r.error.join("\n").includes('cannot read file "missing.json"')); + }); + + test("invalid JSON in the file exits 2", async () => { + const r = await runCli(["bad.json"], { + files: { "bad.json": "{not json" }, + }); + strictEqual(r.code, 2); + ok(r.error.join("\n").includes('invalid JSON in file "bad.json"')); + }); + + test("a file larger than the size limit exits 2 at the gate", async () => { + const r = await runCli(["big.json", "--max-schema-size", "10"], { + files: { "big.json": CLEAN }, + sizes: { "big.json": 100 }, + }); + strictEqual(r.code, 2); + ok(r.error.join("\n").includes("schema file exceeds 10 byte size limit")); + }); + + test("an invalid --max-schema-size defers to analyze (no misleading gate error)", async () => { + // 3.5 is invalid; the file gate falls back to the default and lets analyze() + // raise the TypeError, surfaced as an "analyzing schema" error. + const r = await runCli(["s.json", "--max-schema-size", "3.5"], { + files: { "s.json": CLEAN }, + sizes: { "s.json": 50 }, + }); + strictEqual(r.code, 2); + ok(r.error.join("\n").includes("analyzing schema")); + ok(r.error.join("\n").includes("maxSchemaSize")); + }); + + test("an unsupported $schema surfaces as an analyze error (exit 2)", async () => { + const r = await runCli(["s.json"], { + files: { "s.json": JSON.stringify({ $schema: "http://bogus/v1" }) }, + }); + strictEqual(r.code, 2); + ok(r.error.join("\n").includes("analyzing schema")); + }); +}); + +describe("run() output formats", () => { + test("a clean schema reports no issues and exits 0 (human)", async () => { + const r = await runCli(["clean.json", "--offline"], { + files: { "clean.json": CLEAN }, + }); + strictEqual(r.code, 0); + ok(r.log.join("\n").includes("clean.json has no issues")); + }); + + test("a schema with issues exits 1 (human)", async () => { + const r = await runCli(["dirty.json", "--offline"], { + files: { "dirty.json": DIRTY }, + }); + strictEqual(r.code, 1); + ok(r.log.join("\n").includes("dirty.json has issues")); + // the human format dumps the error array as pretty JSON + ok(r.log.join("\n").includes("instancePath")); + }); + + test("json format with no issues writes [] and exits 0", async () => { + const r = await runCli(["clean.json", "--offline", "--format", "json"], { + files: { "clean.json": CLEAN }, + }); + strictEqual(r.code, 0); + strictEqual(r.write.join("").trim(), "[]"); + strictEqual(r.error.length, 0); + }); + + test("json format with issues writes the array and exits 1", async () => { + const r = await runCli(["dirty.json", "--offline", "--format", "json"], { + files: { "dirty.json": DIRTY }, + }); + strictEqual(r.code, 1); + const parsed = JSON.parse(r.write.join("")); + ok(Array.isArray(parsed) && parsed.length > 0); + ok(r.error.join("\n").includes("issue(s)")); + }); + + test("sarif format with issues writes SARIF 2.1.0 and exits 1", async () => { + const r = await runCli(["dirty.json", "--offline", "--format", "sarif"], { + files: { "dirty.json": DIRTY }, + }); + strictEqual(r.code, 1); + const sarif = JSON.parse(r.write.join("")); + strictEqual(sarif.version, "2.1.0"); + ok(sarif.runs[0].results.length > 0); + ok(r.error.join("\n").includes("issue(s)")); + }); + + test("sarif format with no issues writes an empty-results SARIF and exits 0", async () => { + const r = await runCli(["clean.json", "--offline", "--format", "sarif"], { + files: { "clean.json": CLEAN }, + }); + strictEqual(r.code, 0); + const sarif = JSON.parse(r.write.join("")); + strictEqual(sarif.runs[0].results.length, 0); + strictEqual(r.error.length, 0); + }); +}); + +describe("run() option plumbing", () => { + test("--ignore suppresses a matching finding", async () => { + const dirty = await runCli(["d.json", "--offline"], { + files: { "d.json": DIRTY }, + }); + strictEqual(dirty.code, 1); + // Discover an instancePath to ignore, then suppress it. + const errs = JSON.parse( + ( + await runCli(["d.json", "--offline", "--format", "json"], { + files: { "d.json": DIRTY }, + }) + ).write.join(""), + ); + const target = errs[0].instancePath; + const r = await runCli( + ["d.json", "--offline", "--format", "json", "--ignore", target], + { files: { "d.json": DIRTY } }, + ); + const remaining = JSON.parse(r.write.join("")); + ok(!remaining.some((e) => e.instancePath === target)); + }); + + test("--ref-schema-files marks its $id hostname safe (skips that SSRF host)", async () => { + const schema = JSON.stringify({ + $schema: "https://json-schema.org/draft/2020-12/schema", + $id: "https://example.test/root.json", + $ref: "https://safe-ref-host.invalid/x.json", + }); + const refSchema = JSON.stringify({ + $id: "https://safe-ref-host.invalid/ref.json", + }); + const r = await runCli( + [ + "s.json", + "--dns-total-timeout-ms", + "0", + "--format", + "json", + "-r", + "ref.json", + ], + { files: { "s.json": schema, "ref.json": refSchema } }, + ); + const errs = JSON.parse(r.write.join("")); + ok( + !errs.some((e) => e.keyword === "ssrf"), + "the ref-schema-files $id hostname must be treated as safe", + ); + }); + + test("a non-URL $id in a ref-schema-file is ignored (no crash)", async () => { + const schema = JSON.stringify({ + $schema: "https://json-schema.org/draft/2020-12/schema", + $id: "https://example.test/root.json", + type: "string", + maxLength: 5, + pattern: "^[a-z]+$", + }); + const r = await runCli(["s.json", "--offline", "-r", "ref.json"], { + files: { + "s.json": schema, + "ref.json": JSON.stringify({ $id: "not-a-url" }), + }, + }); + strictEqual(r.code, 0); + }); +}); + +const ENUM_BIG = JSON.stringify({ + $schema: "https://json-schema.org/draft/2020-12/schema", + $id: "https://example.test/enum.json", + type: "string", + maxLength: 100, + enum: Array.from({ length: 2000 }, (_, i) => `v${i}`), +}); +const PROPS_BIG = (() => { + const props = {}; + for (let i = 0; i < 1100; i++) + props[`p${i}`] = { type: "string", maxLength: 10, pattern: "^[a-z]+$" }; + return JSON.stringify({ + $schema: "https://json-schema.org/draft/2020-12/schema", + $id: "https://example.test/props.json", + type: "object", + properties: props, + required: ["p0"], + unevaluatedProperties: false, + maxProperties: 2000, + }); +})(); +const REMOTE = JSON.stringify({ + $schema: "https://json-schema.org/draft/2020-12/schema", + $id: "https://example.test/remote.json", + $ref: "https://run-ssrf-host.invalid/x.json", +}); + +const jsonErrors = (r) => JSON.parse(r.write.join("")); + +// Re-uses the runCli helper and CLEAN/DIRTY fixtures defined above. +describe("run() default I/O wiring", () => { + test("--version uses the default logger (no injected io)", async () => { + strictEqual(await run(["--version"]), 0); + }); + test("an arg error uses the default error logger", async () => { + strictEqual(await run(["--nope", "x.json"]), 2); + }); + test("reading a real fixture uses default readFile/stat/write", async () => { + // No io injected: exercises the real fs + process.stdout.write defaults. + const code = await run([ + "tests/fixtures/boolean.json", + "--offline", + "--format", + "json", + ]); + ok(code === 0 || code === 1, `expected 0 or 1, got ${code}`); + }); +}); + +describe("run() readJsonFile read failure", () => { + test("a file that stats but cannot be read exits 2", async () => { + // stat succeeds (size provided) but readFile has no entry -> throws. + const r = await runCli(["x.json"], { sizes: { "x.json": 10 } }); + strictEqual(r.code, 2); + ok(r.error.join("\n").includes('cannot read file "x.json"')); + }); +}); + +describe("run() offline defaults to false", () => { + test("a remote $ref is SSRF-checked when --offline is omitted", async () => { + const r = await runCli( + ["s.json", "--dns-total-timeout-ms", "0", "--format", "json"], + { files: { "s.json": REMOTE } }, + ); + ok( + jsonErrors(r).some((e) => e.keyword === "ssrf"), + "default (non-offline) run must perform the SSRF check", + ); + }); +}); + +describe("run() --lang error lists the languages", () => { + test("the error enumerates langs comma-separated", async () => { + const r = await runCli(["--lang", "elvish", "x.json"]); + ok(r.error.join("\n").includes("js, py")); + }); +}); + +describe("run() file-size gate boundaries", () => { + test("--max-schema-size 0 fails at the gate (not analyze)", async () => { + const r = await runCli(["s.json", "--max-schema-size", "0"], { + files: { "s.json": CLEAN }, + sizes: { "s.json": 50 }, + }); + strictEqual(r.code, 2); + ok(r.error.join("\n").includes("schema file exceeds 0 byte size limit")); + }); + test("a negative --max-schema-size defers to analyze", async () => { + // `=` form so parseArgs reads -1 as the value, not a flag. + const r = await runCli(["s.json", "--max-schema-size=-1"], { + files: { "s.json": CLEAN }, + sizes: { "s.json": 50 }, + }); + strictEqual(r.code, 2); + ok(r.error.join("\n").includes("analyzing schema")); + }); + test("a file exactly at the limit passes the gate (strict >)", async () => { + const size = CLEAN.length; + const r = await runCli(["s.json", "--max-schema-size", String(size)], { + files: { "s.json": CLEAN }, + sizes: { "s.json": size }, + }); + strictEqual(r.code, 0); + ok(r.log.join("\n").includes("no issues")); + }); +}); + +describe("run() override option plumbing", () => { + test("--override-max-items is forwarded to analyze", async () => { + const without = jsonErrors( + await runCli(["e.json", "--offline", "--format", "json"], { + files: { "e.json": ENUM_BIG }, + }), + ); + ok(without.some((x) => x.keyword === "maxItems")); + const withOv = jsonErrors( + await runCli( + [ + "e.json", + "--offline", + "--format", + "json", + "--override-max-items", + "5000", + ], + { files: { "e.json": ENUM_BIG } }, + ), + ); + ok(!withOv.some((x) => x.keyword === "maxItems")); + }); + + test("--override-max-properties is forwarded to analyze", async () => { + const withOv = jsonErrors( + await runCli( + [ + "p.json", + "--offline", + "--format", + "json", + "--override-max-properties", + "5000", + ], + { files: { "p.json": PROPS_BIG } }, + ), + ); + ok(!withOv.some((x) => x.keyword === "maxProperties")); + }); + + test("--override-max-depth is forwarded to analyze", async () => { + // a nested schema so maxDepth 0 actually trips depth-exceeded. + const nested = JSON.stringify({ + $schema: "https://json-schema.org/draft/2020-12/schema", + $id: "https://example.test/nested.json", + type: "object", + properties: { a: { type: "string", maxLength: 10, pattern: "^[a-z]+$" } }, + required: ["a"], + unevaluatedProperties: false, + maxProperties: 5, + }); + const r = await runCli( + ["c.json", "--offline", "--format", "json", "--override-max-depth", "0"], + { files: { "c.json": nested } }, + ); + ok(jsonErrors(r).some((x) => x.keyword === "depth")); + }); + + test("--analysis-timeout-ms is forwarded to analyze", async () => { + const r = await runCli( + ["c.json", "--offline", "--format", "json", "--analysis-timeout-ms", "0"], + { files: { "c.json": CLEAN } }, + ); + ok(jsonErrors(r).some((x) => x.keyword === "timeout")); + }); + + test("--max-ssrf-hostnames is forwarded to analyze", async () => { + const r = await runCli( + ["s.json", "--format", "json", "--max-ssrf-hostnames", "0"], + { files: { "s.json": REMOTE } }, + ); + const ssrf = jsonErrors(r).find((x) => x.keyword === "ssrf"); + ok(ssrf, "max-ssrf-hostnames 0 must trip the hostname cap"); + ok(ssrf.message.includes("too many distinct")); + }); +}); + +describe("run() propagates unexpected (non-CliExit) errors", () => { + test("an io error during output is not swallowed", async () => { + await rejects( + run(["clean.json", "--offline"], { + readFile: async () => CLEAN, + stat: async () => ({ size: CLEAN.length }), + log: () => { + throw new Error("boom"); + }, + }), + ); + }); +}); + +describe("run() default I/O actually writes to the console", () => { + test("default log goes to console.log", async () => { + const orig = console.log; + const cap = []; + console.log = (m) => cap.push(String(m)); + try { + await run(["--version"]); + } finally { + console.log = orig; + } + ok(cap.join("").includes(pkg.version)); + }); + + test("default error goes to console.error", async () => { + const orig = console.error; + const cap = []; + console.error = (m) => cap.push(String(m)); + try { + await run(["--bad-flag", "x.json"]); + } finally { + console.error = orig; + } + ok(cap.join("").startsWith("Error: ")); + }); + + test("default write goes to process.stdout.write", async () => { + const orig = process.stdout.write; + const cap = []; + process.stdout.write = (s) => { + cap.push(String(s)); + return true; + }; + try { + await run([ + "tests/fixtures/boolean.json", + "--offline", + "--format", + "json", + ]); + } finally { + process.stdout.write = orig; + } + // the test runner also writes to stdout, so look for the JSON chunk. + ok(cap.some((s) => s.trimStart().startsWith("["))); + }); +}); + +describe("run() ref-schema-files and dns budget specifics", () => { + test("an unreadable --ref-schema-files file errors with its label", async () => { + const r = await runCli(["s.json", "--offline", "-r", "no-ref.json"], { + files: { + "s.json": JSON.stringify({ + $schema: "https://json-schema.org/draft/2020-12/schema", + $id: "https://example.test/s.json", + type: "string", + maxLength: 5, + pattern: "^[a-z]+$", + }), + }, + }); + strictEqual(r.code, 2); + ok( + r.error + .join("\n") + .includes('cannot read --ref-schema-files file "no-ref.json"'), + ); + }); + + test("--dns-total-timeout-ms 0 produces a budget-exceeded ssrf finding", async () => { + const r = await runCli( + ["s.json", "--dns-total-timeout-ms", "0", "--format", "json"], + { files: { "s.json": REMOTE } }, + ); + const ssrf = jsonErrors(r).find((e) => e.keyword === "ssrf"); + ok(ssrf, "expected an ssrf finding"); + ok( + ssrf.message.includes("budget"), + "dnsTotalTimeoutMs:0 must fail closed on the budget (proves the key is read)", + ); + }); + + test("without --max-schema-size a normal file passes the size gate", async () => { + const r = await runCli(["clean.json", "--offline"], { + files: { "clean.json": CLEAN }, + }); + strictEqual(r.code, 0); + ok(!r.error.join("\n").includes("byte size limit")); + }); +}); diff --git a/tests/cli.sast.test.js b/tests/cli.sast.test.js index 5169467..c2ddb16 100644 --- a/tests/cli.sast.test.js +++ b/tests/cli.sast.test.js @@ -1,6 +1,6 @@ -import { ok, strictEqual } from "node:assert"; +import { ok, strictEqual, throws } from "node:assert"; import { describe, test } from "node:test"; -import sast, { analyze, MAX_SCHEMA_SIZE } from "../cli.js"; +import sast, { analyze, crawlSchema, MAX_SCHEMA_SIZE } from "../cli.js"; test("sast should return a validate function", () => { const validate = sast(); @@ -244,3 +244,37 @@ describe("sast validate vs analyze consistency", () => { ); }); }); + +describe("sast() $schema URL normalization", () => { + test("a trailing '#' on the $schema URL still resolves the draft", () => { + // kills the `.replace(/#$/, "")` step + const validate = sast({ + $schema: "https://json-schema.org/draft/2020-12/schema#", + }); + strictEqual(typeof validate, "function"); + }); + + test("a protocol-relative $schema URL still resolves the draft", () => { + // kills the `.replace(/^(?:https?:)?\/\//, "")` step + const validate = sast({ + $schema: "//json-schema.org/draft/2020-12/schema", + }); + strictEqual(typeof validate, "function"); + }); + + test("an unsupported $schema throws naming the value", () => { + throws( + () => sast({ $schema: "http://bogus.example/v1" }), + /Unsupported \$schema: http:\/\/bogus\.example\/v1/, + ); + }); +}); + +describe("resolveDangerousNames unknown language", () => { + test("an unknown lang throws listing the valid options comma-separated", () => { + throws( + () => crawlSchema({ type: "object" }, 32, { lang: "klingon" }), + /unknown lang "klingon", expected one of: js, py, /, + ); + }); +}); From cee2efb291c8a6d8b229c081e4b4b9631b163c28 Mon Sep 17 00:00:00 2001 From: will Farrell Date: Fri, 12 Jun 2026 13:13:14 -0600 Subject: [PATCH 09/13] chore: version bump Signed-off-by: will Farrell --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index e93d507..f356750 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "sast-json-schema", - "version": "0.4.1", + "version": "0.5.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "sast-json-schema", - "version": "0.4.1", + "version": "0.5.0", "license": "MIT", "workspaces": [ ".github" diff --git a/package.json b/package.json index f348192..4b3475c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "sast-json-schema", - "version": "0.4.1", + "version": "0.5.0", "description": "Meta-schema for the Static Application Security Testing (SAST) of JSON Schemas", "type": "module", "engines": { From 8941ead9428b9087849ecdae64de2af091013d6a Mon Sep 17 00:00:00 2001 From: will Farrell Date: Fri, 12 Jun 2026 13:16:56 -0600 Subject: [PATCH 10/13] chore: dep update Signed-off-by: will Farrell --- biome.json | 4 +- package-lock.json | 1304 +++++++++++++++++++++++++++++++++++---------- 2 files changed, 1022 insertions(+), 286 deletions(-) diff --git a/biome.json b/biome.json index 5873611..78967f6 100644 --- a/biome.json +++ b/biome.json @@ -1,5 +1,5 @@ { - "$schema": "https://biomejs.dev/schemas/2.4.15/schema.json", + "$schema": "https://biomejs.dev/schemas/2.5.0/schema.json", "vcs": { "enabled": false, "clientKind": "git", @@ -16,7 +16,7 @@ "linter": { "enabled": true, "rules": { - "recommended": true + "preset": "recommended" } }, "javascript": { diff --git a/package-lock.json b/package-lock.json index f356750..1d05be7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -50,6 +50,8 @@ }, "node_modules/@apidevtools/json-schema-ref-parser": { "version": "15.3.5", + "resolved": "https://registry.npmjs.org/@apidevtools/json-schema-ref-parser/-/json-schema-ref-parser-15.3.5.tgz", + "integrity": "sha512-orNOYXw3hYXxxisXMldjzjBzqqTLBPbwOtHg7ovBPvfBHDue1qM9YJENZ3W2BQuS+7z4ThogMbEzEsov57Itkg==", "dev": true, "license": "MIT", "dependencies": { @@ -64,6 +66,8 @@ }, "node_modules/@babel/code-frame": { "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz", + "integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==", "dev": true, "license": "MIT", "dependencies": { @@ -77,6 +81,8 @@ }, "node_modules/@babel/compat-data": { "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.7.tgz", + "integrity": "sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==", "dev": true, "license": "MIT", "engines": { @@ -85,6 +91,8 @@ }, "node_modules/@babel/core": { "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.7.tgz", + "integrity": "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==", "dev": true, "license": "MIT", "dependencies": { @@ -114,6 +122,8 @@ }, "node_modules/@babel/core/node_modules/semver": { "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true, "license": "ISC", "bin": { @@ -122,6 +132,8 @@ }, "node_modules/@babel/generator": { "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.7.tgz", + "integrity": "sha512-DkXD5OJQaAQIdZ1bt3UZdEnHAn9Imd3IVBdX03UFe+ony9Ojw5pzr9YVKGDY1jt+Gcn/FnGkNf8r+Vj5NOJWtQ==", "dev": true, "license": "MIT", "dependencies": { @@ -137,6 +149,8 @@ }, "node_modules/@babel/helper-annotate-as-pure": { "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.29.7.tgz", + "integrity": "sha512-OoK6239jHPuSQOoS0kfTVKn0b/rVTk0seKq4Gd2UMLtmOVLjDC0ki3e+c90Trqv2gMfvJFqkiljrr568+qddiw==", "dev": true, "license": "MIT", "dependencies": { @@ -148,6 +162,8 @@ }, "node_modules/@babel/helper-compilation-targets": { "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.29.7.tgz", + "integrity": "sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==", "dev": true, "license": "MIT", "dependencies": { @@ -163,6 +179,8 @@ }, "node_modules/@babel/helper-compilation-targets/node_modules/semver": { "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true, "license": "ISC", "bin": { @@ -171,6 +189,8 @@ }, "node_modules/@babel/helper-create-class-features-plugin": { "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.29.7.tgz", + "integrity": "sha512-IY3ZD9Tmooqr3TUhc3DUWxiuo8xx1DWLhd5M7hQ+ZWJamqM2BbalrBJb2MisSLoYorOj75U03qULCxQTY9r3hg==", "dev": true, "license": "MIT", "dependencies": { @@ -191,6 +211,8 @@ }, "node_modules/@babel/helper-create-class-features-plugin/node_modules/semver": { "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true, "license": "ISC", "bin": { @@ -199,6 +221,8 @@ }, "node_modules/@babel/helper-globals": { "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.29.7.tgz", + "integrity": "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==", "dev": true, "license": "MIT", "engines": { @@ -207,6 +231,8 @@ }, "node_modules/@babel/helper-member-expression-to-functions": { "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.29.7.tgz", + "integrity": "sha512-j+7JYmk1JYDtACIGj0QJqqWZjoUpMoEikQGADMaHgCMCSDqd2+P32rfcibUNrGOMWrlzK1WJBdxrB3JJQZwWtg==", "dev": true, "license": "MIT", "dependencies": { @@ -219,6 +245,8 @@ }, "node_modules/@babel/helper-module-imports": { "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.29.7.tgz", + "integrity": "sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==", "dev": true, "license": "MIT", "dependencies": { @@ -231,6 +259,8 @@ }, "node_modules/@babel/helper-module-transforms": { "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.29.7.tgz", + "integrity": "sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==", "dev": true, "license": "MIT", "dependencies": { @@ -247,6 +277,8 @@ }, "node_modules/@babel/helper-optimise-call-expression": { "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.29.7.tgz", + "integrity": "sha512-+kmGVjcT9RGYzoDwdwEqEvGgKe3BYq+O1iGzjFubaNgZHwYHP6lsF2Yghf4kEuv9BV7tYDZ913aBW9am6YKong==", "dev": true, "license": "MIT", "dependencies": { @@ -258,6 +290,8 @@ }, "node_modules/@babel/helper-plugin-utils": { "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.29.7.tgz", + "integrity": "sha512-G7sHYigPY17oO5SYWnfD/0MTBwVR781S/JI643e/JhUYgVgWE/61SoW3NH9KWUKyKq5LVh3npif99Wkt6j86Jw==", "dev": true, "license": "MIT", "engines": { @@ -266,6 +300,8 @@ }, "node_modules/@babel/helper-replace-supers": { "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.29.7.tgz", + "integrity": "sha512-atfGXWSeCiF4DnKZIfmJfQRkSw9b9gNNXR1kqKjbhG4pGYCOnkp8OcTB8E3NXjBu8NpheSnOeNKz8KT7UNFTmQ==", "dev": true, "license": "MIT", "dependencies": { @@ -282,6 +318,8 @@ }, "node_modules/@babel/helper-skip-transparent-expression-wrappers": { "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.29.7.tgz", + "integrity": "sha512-brcMGQaVzIeUb+6/bs1Av0f8YuNNjKY2JyvfRCsFuFsdKccEQ5Ges2y74D74NZ1Rz8lKJ9ksJkfqwQFJ/iNEyQ==", "dev": true, "license": "MIT", "dependencies": { @@ -294,6 +332,8 @@ }, "node_modules/@babel/helper-string-parser": { "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz", + "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==", "dev": true, "license": "MIT", "engines": { @@ -302,6 +342,8 @@ }, "node_modules/@babel/helper-validator-identifier": { "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", + "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", "dev": true, "license": "MIT", "engines": { @@ -310,6 +352,8 @@ }, "node_modules/@babel/helper-validator-option": { "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.29.7.tgz", + "integrity": "sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==", "dev": true, "license": "MIT", "engines": { @@ -318,6 +362,8 @@ }, "node_modules/@babel/helpers": { "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.7.tgz", + "integrity": "sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==", "dev": true, "license": "MIT", "dependencies": { @@ -330,6 +376,8 @@ }, "node_modules/@babel/parser": { "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.7.tgz", + "integrity": "sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==", "dev": true, "license": "MIT", "dependencies": { @@ -344,6 +392,8 @@ }, "node_modules/@babel/plugin-proposal-decorators": { "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-decorators/-/plugin-proposal-decorators-7.29.7.tgz", + "integrity": "sha512-EtU0Hi3GvrTqD56xKmZvV/uCXK2ZbwVNPNLAquVItcAZpUhkXwWlo3Fmj0c2LxgSf2I8IDULeAepwNP1OefLXg==", "dev": true, "license": "MIT", "dependencies": { @@ -360,6 +410,8 @@ }, "node_modules/@babel/plugin-syntax-decorators": { "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-decorators/-/plugin-syntax-decorators-7.29.7.tgz", + "integrity": "sha512-9MTTLbF39X6sqM92JPEsoI7++26hjZvzkxKZy64aMhWLH2mPkJ/Q3AV4QLmls3R14FpSpkOwQQfUh962JGQxxg==", "dev": true, "license": "MIT", "dependencies": { @@ -374,6 +426,8 @@ }, "node_modules/@babel/plugin-syntax-jsx": { "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.29.7.tgz", + "integrity": "sha512-TSu8+mHCoEaaCDEZ0I3+6mvTBYR4PCxQwf2z9/r5Tbztv6NaLR3B9thGTTxX2WGuGHJqRiAbKPeGTJ5XWXVg6A==", "dev": true, "license": "MIT", "dependencies": { @@ -388,6 +442,8 @@ }, "node_modules/@babel/plugin-syntax-typescript": { "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.29.7.tgz", + "integrity": "sha512-ngr+82Sh0xMz25TPCZi+nC2iTzjfCdWS2ONXTp/PtSCHCgaCNBpdMqgvJ2ccdLlClVZ7sisIgB914j/JFe+RZA==", "dev": true, "license": "MIT", "dependencies": { @@ -402,6 +458,8 @@ }, "node_modules/@babel/plugin-transform-destructuring": { "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.29.7.tgz", + "integrity": "sha512-iPX8aD6H9zV5s7ZsqTdNocPN/MGQ5sSMnElKrktxjJRMnB2jN/1p2+R7GkfD6CAYoVFqy5A4XnSIUeGgJzIWpg==", "dev": true, "license": "MIT", "dependencies": { @@ -417,6 +475,8 @@ }, "node_modules/@babel/plugin-transform-explicit-resource-management": { "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-explicit-resource-management/-/plugin-transform-explicit-resource-management-7.29.7.tgz", + "integrity": "sha512-Rstj7coNz8sE+7Ju7ihpHLI564lsK5pUpNNlvptCIC/16E/S5hbl6n3kESPKdNRmqEWlpn5xpS5Q2dvXBsySLw==", "dev": true, "license": "MIT", "dependencies": { @@ -432,6 +492,8 @@ }, "node_modules/@babel/plugin-transform-modules-commonjs": { "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.29.7.tgz", + "integrity": "sha512-j0vCldybPC5b5dwCQOJ21uKtHzt7hxLygJTg9eF1ScfaikEDNfzn94XoW5Fi+seBR0nCyL23xaBFFkq7dTM8XQ==", "dev": true, "license": "MIT", "dependencies": { @@ -447,6 +509,8 @@ }, "node_modules/@babel/plugin-transform-typescript": { "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.29.7.tgz", + "integrity": "sha512-jK52h8LaLc7JarhQV2ofeFMts4H7vnOXnqZNA6fYglBTZewRBE51KWt3BUltW1P+KoPsYkHoJeXePuz4zo2LMw==", "dev": true, "license": "MIT", "dependencies": { @@ -465,6 +529,8 @@ }, "node_modules/@babel/preset-typescript": { "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.28.5.tgz", + "integrity": "sha512-+bQy5WOI2V6LJZpPVxY+yp66XdZ2yifu0Mc1aP5CQKgjn4QM5IN2i5fAZ4xKop47pr8rpVhiAeu+nDQa12C8+g==", "dev": true, "license": "MIT", "dependencies": { @@ -483,6 +549,8 @@ }, "node_modules/@babel/template": { "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.29.7.tgz", + "integrity": "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==", "dev": true, "license": "MIT", "dependencies": { @@ -496,6 +564,8 @@ }, "node_modules/@babel/traverse": { "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.7.tgz", + "integrity": "sha512-EhlfNQtZ+NK22w5BM61ciuiq1m58ed33Wr1Xan//ZRTy6hgjnwyCffRYwzsGXdASJSUJ1guZILsErh1eQcl+zw==", "dev": true, "license": "MIT", "dependencies": { @@ -513,6 +583,8 @@ }, "node_modules/@babel/types": { "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.7.tgz", + "integrity": "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==", "dev": true, "license": "MIT", "dependencies": { @@ -524,7 +596,9 @@ } }, "node_modules/@biomejs/biome": { - "version": "2.4.15", + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@biomejs/biome/-/biome-2.5.0.tgz", + "integrity": "sha512-4kURkd9hAPrdDM3C9n82ycYgx8hvQcW6MjKTEejruj8rK0N8P3OPpdy8BvI8kt3KWY4ycF5XtDOrktetEfhfuw==", "dev": true, "license": "MIT OR Apache-2.0", "bin": { @@ -538,18 +612,20 @@ "url": "https://opencollective.com/biome" }, "optionalDependencies": { - "@biomejs/cli-darwin-arm64": "2.4.15", - "@biomejs/cli-darwin-x64": "2.4.15", - "@biomejs/cli-linux-arm64": "2.4.15", - "@biomejs/cli-linux-arm64-musl": "2.4.15", - "@biomejs/cli-linux-x64": "2.4.15", - "@biomejs/cli-linux-x64-musl": "2.4.15", - "@biomejs/cli-win32-arm64": "2.4.15", - "@biomejs/cli-win32-x64": "2.4.15" + "@biomejs/cli-darwin-arm64": "2.5.0", + "@biomejs/cli-darwin-x64": "2.5.0", + "@biomejs/cli-linux-arm64": "2.5.0", + "@biomejs/cli-linux-arm64-musl": "2.5.0", + "@biomejs/cli-linux-x64": "2.5.0", + "@biomejs/cli-linux-x64-musl": "2.5.0", + "@biomejs/cli-win32-arm64": "2.5.0", + "@biomejs/cli-win32-x64": "2.5.0" } }, "node_modules/@biomejs/cli-darwin-arm64": { - "version": "2.4.15", + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-arm64/-/cli-darwin-arm64-2.5.0.tgz", + "integrity": "sha512-Mn3Fwi3SA5fgmfCPqmzpWF2DLZnms3BVAhM088nTnGrTZmHS3wwIjcoZPqpXeNgd3DrrLH6xp8vTLIBuJoZiXw==", "cpu": [ "arm64" ], @@ -564,9 +640,9 @@ } }, "node_modules/@biomejs/cli-darwin-x64": { - "version": "2.4.15", - "resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-x64/-/cli-darwin-x64-2.4.15.tgz", - "integrity": "sha512-/5KHXYMfSJs1fNXiX30xFtI8JcCFV6zaVVLxOa0M2sfqBKHkpQhRTv94yxQWxeTY2lzo2OuTlNvPC+hDQt2wcQ==", + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-x64/-/cli-darwin-x64-2.5.0.tgz", + "integrity": "sha512-rg3VPL5P8mYro6pqlXYXuJWph21slVp3SZtAqWSrkZs40d2gTzYmHF8E/X1iTID25btmNKltNDJ926sqVBp7DQ==", "cpu": [ "x64" ], @@ -581,9 +657,9 @@ } }, "node_modules/@biomejs/cli-linux-arm64": { - "version": "2.4.15", - "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64/-/cli-linux-arm64-2.4.15.tgz", - "integrity": "sha512-owaAMZD/T4LrD0ELNCk0Km3qrRHuM0X6EAyVE1FSqGY0rbLoiDLrO4Us2tllm6cAeB2Ioa9C2C08NZPdr8+0Ug==", + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64/-/cli-linux-arm64-2.5.0.tgz", + "integrity": "sha512-tl+LW8fdD96/xdeWtWwc82LIOc5CoY7N2AsogLTp5R4ECErYt+8Jl/N68ezN9vzSiqPTxw6vjcihoLPYKZHrlw==", "cpu": [ "arm64" ], @@ -601,9 +677,9 @@ } }, "node_modules/@biomejs/cli-linux-arm64-musl": { - "version": "2.4.15", - "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64-musl/-/cli-linux-arm64-musl-2.4.15.tgz", - "integrity": "sha512-ZPcxznxm0pogHBLZhYntyR3sR+MrZjqJIKEr7ZqVen0Rl+P/4upVmfYXjftizi9RoqZntg33fv/1fbdhbYXpEQ==", + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64-musl/-/cli-linux-arm64-musl-2.5.0.tgz", + "integrity": "sha512-vQdM4oSGaf7ZNeGO9w5+Y8SBtyser9M6znxYbm7Ec8wInxJu1WiKxFYZW5Auj2d80bcVvefuGGRxoFOE0eee8g==", "cpu": [ "arm64" ], @@ -621,9 +697,9 @@ } }, "node_modules/@biomejs/cli-linux-x64": { - "version": "2.4.15", - "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64/-/cli-linux-x64-2.4.15.tgz", - "integrity": "sha512-0jj7THz12GbUOLmMibktK6DZjqz2zV64KFxyBtcFTKPiiOIY0a7vns1elpO1dERvxpsZ5ik0oFfz0oGwFde1+g==", + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64/-/cli-linux-x64-2.5.0.tgz", + "integrity": "sha512-zpEGf4RQbFEh8Vt7OmavLyyOzRbtcE9osCqrS1kfvt8jDvxwhKXLSf7n0ebr/ov0RJ9ssP+lhs6C8a9WwFvrQA==", "cpu": [ "x64" ], @@ -641,9 +717,9 @@ } }, "node_modules/@biomejs/cli-linux-x64-musl": { - "version": "2.4.15", - "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64-musl/-/cli-linux-x64-musl-2.4.15.tgz", - "integrity": "sha512-CNq/9W38SYSH023lfcQ4KKU8K0YX8T//FZUhcgtMMRABDojx5XsMV7jlweAvGSl389wJQB29Qo6Zb/a+jdvt+w==", + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64-musl/-/cli-linux-x64-musl-2.5.0.tgz", + "integrity": "sha512-+9hIcMngJ+yGUahXqZuZ8CoWKJE9SAZsFsM3QDvXpNsLbXZ9lqVzgBhOk/jTSYkOA0GLP9eu3teukqpLUojHMg==", "cpu": [ "x64" ], @@ -661,9 +737,9 @@ } }, "node_modules/@biomejs/cli-win32-arm64": { - "version": "2.4.15", - "resolved": "https://registry.npmjs.org/@biomejs/cli-win32-arm64/-/cli-win32-arm64-2.4.15.tgz", - "integrity": "sha512-ouhkYdlhp/1GghEJPdWwD/Vi3gQ1nFxuSpMolWsbq3Lsq3QUR4jl6UdhhscdCugKU5vOEuMiJhvKj66O0OCq+w==", + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@biomejs/cli-win32-arm64/-/cli-win32-arm64-2.5.0.tgz", + "integrity": "sha512-jB0wAvTLI4itx5VidqVUejPQFhRUxiZ9l9FvZ26D5fl6t3qme+ZB4PD3bTSeL1vZ8NI2Rx/zj6H9zcESuGHKGw==", "cpu": [ "arm64" ], @@ -678,9 +754,9 @@ } }, "node_modules/@biomejs/cli-win32-x64": { - "version": "2.4.15", - "resolved": "https://registry.npmjs.org/@biomejs/cli-win32-x64/-/cli-win32-x64-2.4.15.tgz", - "integrity": "sha512-zBrGq5mx5wwpnow4+2BxUvleDM+GNd4sLbPaMapsSLQLD0NGRCquqPBTgN+7XkUteHvj7M+BstuI8tmnV7+HgQ==", + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@biomejs/cli-win32-x64/-/cli-win32-x64-2.5.0.tgz", + "integrity": "sha512-VT/lF+GId+67j8aDfLkxdxNoVApsPSTbyAtB3jJq0IWTrY77WXfbPfpngxq0bA6JCEv/7k8C9qWjDRKRznDlyw==", "cpu": [ "x64" ], @@ -695,16 +771,16 @@ } }, "node_modules/@commitlint/cli": { - "version": "21.0.1", - "resolved": "https://registry.npmjs.org/@commitlint/cli/-/cli-21.0.1.tgz", - "integrity": "sha512-8vq10krmbJwBkvzXKhbs4o4JQEVscd3pqOlWuDUaDBwbeL694/P33UC29tZQFTAgPU9fVJ2+f2m3zw16yKWxHg==", + "version": "21.0.2", + "resolved": "https://registry.npmjs.org/@commitlint/cli/-/cli-21.0.2.tgz", + "integrity": "sha512-YMmfLbqBg+ZRvvmPhc+cilSQFrh/AgzVgCT1U/OifmUZEwPbvCtA8rN//YNaF9d5eoZphxVMGYtmwA2QgQORgg==", "dev": true, "license": "MIT", "dependencies": { "@commitlint/format": "^21.0.1", - "@commitlint/lint": "^21.0.1", - "@commitlint/load": "^21.0.1", - "@commitlint/read": "^21.0.1", + "@commitlint/lint": "^21.0.2", + "@commitlint/load": "^21.0.2", + "@commitlint/read": "^21.0.2", "@commitlint/types": "^21.0.1", "tinyexec": "^1.0.0", "yargs": "^18.0.0" @@ -717,7 +793,9 @@ } }, "node_modules/@commitlint/config-conventional": { - "version": "21.0.1", + "version": "21.0.2", + "resolved": "https://registry.npmjs.org/@commitlint/config-conventional/-/config-conventional-21.0.2.tgz", + "integrity": "sha512-P/ZRhryQmkj0Z0dY9FOoRwe3xkwJyyAdtXwt01NT2kuZttcG2CNYp1q5Ci3u+nDT2jcbJRw2kt13Czl1qKNPfg==", "dev": true, "license": "MIT", "dependencies": { @@ -730,6 +808,8 @@ }, "node_modules/@commitlint/config-validator": { "version": "21.0.1", + "resolved": "https://registry.npmjs.org/@commitlint/config-validator/-/config-validator-21.0.1.tgz", + "integrity": "sha512-Zd2UFdndeMMaW2O96HK0tdfT4gOImUvidMpAd/pws2zZ4m1nrAZ/9b/v2JYuE8fs86GpXv9F7LNaIuCIWhY+pA==", "dev": true, "license": "MIT", "dependencies": { @@ -742,6 +822,8 @@ }, "node_modules/@commitlint/ensure": { "version": "21.0.1", + "resolved": "https://registry.npmjs.org/@commitlint/ensure/-/ensure-21.0.1.tgz", + "integrity": "sha512-jJ1037967wU7YN/xkv+iRlOBlmaOXPhPO5KQSqya6GyXzBlwuLzELBFao16DVg9dZyqmNrhewzwZ3SAibetHBQ==", "dev": true, "license": "MIT", "dependencies": { @@ -754,6 +836,8 @@ }, "node_modules/@commitlint/execute-rule": { "version": "21.0.1", + "resolved": "https://registry.npmjs.org/@commitlint/execute-rule/-/execute-rule-21.0.1.tgz", + "integrity": "sha512-RifH+FmImozKBE6mozhF4K3r2RRKP7SMi/Q/zLCmExtp5e05lhHOUYqGBlFBAGNHaZxU/WYw1XuugYK9jQzqnA==", "dev": true, "license": "MIT", "engines": { @@ -762,6 +846,8 @@ }, "node_modules/@commitlint/format": { "version": "21.0.1", + "resolved": "https://registry.npmjs.org/@commitlint/format/-/format-21.0.1.tgz", + "integrity": "sha512-ksmG2+cHGtuDPQQbhBbC4unwm444+6TiPw0d1bKf67hntgZqZ8E0g1MuYKUuyT5IH4IMmXZhKq22/Z3jBvtQIw==", "dev": true, "license": "MIT", "dependencies": { @@ -773,7 +859,9 @@ } }, "node_modules/@commitlint/is-ignored": { - "version": "21.0.1", + "version": "21.0.2", + "resolved": "https://registry.npmjs.org/@commitlint/is-ignored/-/is-ignored-21.0.2.tgz", + "integrity": "sha512-H5z4t8PC9tUsmZ/o+EptM3Nq8sTFtskAShdcqxCoyzklW5eaVT5xbrDAET2uypzir9Vsj4ZZmBtyKjYe2XqgeQ==", "dev": true, "license": "MIT", "dependencies": { @@ -785,13 +873,15 @@ } }, "node_modules/@commitlint/lint": { - "version": "21.0.1", + "version": "21.0.2", + "resolved": "https://registry.npmjs.org/@commitlint/lint/-/lint-21.0.2.tgz", + "integrity": "sha512-PnUmLYGeGLfW8oVatR9KpNxSHYAnJOEWlMZzfdeFOUq6WUrFx1fGQaWCWJqMoIll/xPM+GdfJV+tKHZVHhl0Fg==", "dev": true, "license": "MIT", "dependencies": { - "@commitlint/is-ignored": "^21.0.1", - "@commitlint/parse": "^21.0.1", - "@commitlint/rules": "^21.0.1", + "@commitlint/is-ignored": "^21.0.2", + "@commitlint/parse": "^21.0.2", + "@commitlint/rules": "^21.0.2", "@commitlint/types": "^21.0.1" }, "engines": { @@ -799,7 +889,9 @@ } }, "node_modules/@commitlint/load": { - "version": "21.0.1", + "version": "21.0.2", + "resolved": "https://registry.npmjs.org/@commitlint/load/-/load-21.0.2.tgz", + "integrity": "sha512-lwUE70hN0/qE/ZRROhbaX65ly/FF12DrqfReLCESo37M0OQCFAf2jRS+2tSCSORq+bm4Kdju7qNDj46uc1QzTA==", "dev": true, "license": "MIT", "dependencies": { @@ -818,7 +910,9 @@ } }, "node_modules/@commitlint/message": { - "version": "21.0.1", + "version": "21.0.2", + "resolved": "https://registry.npmjs.org/@commitlint/message/-/message-21.0.2.tgz", + "integrity": "sha512-5n4aqHGD/FNnom/D5L8i7cYtV+xjuXcBL832C3w9VglEsZzIsoHpJsvxzJ7cgiOsOdc/2jU4t5+7qMHh7GBX3g==", "dev": true, "license": "MIT", "engines": { @@ -826,7 +920,9 @@ } }, "node_modules/@commitlint/parse": { - "version": "21.0.1", + "version": "21.0.2", + "resolved": "https://registry.npmjs.org/@commitlint/parse/-/parse-21.0.2.tgz", + "integrity": "sha512-QVZJhGHTm+oiuWyEKOCTQ0ZM3mfJ0eGWFeHuj7WzSKEth+UukcCHac9GD8pgdFlg/qGkFWOtyaNd1T8REgagaw==", "dev": true, "license": "MIT", "dependencies": { @@ -839,11 +935,13 @@ } }, "node_modules/@commitlint/read": { - "version": "21.0.1", + "version": "21.0.2", + "resolved": "https://registry.npmjs.org/@commitlint/read/-/read-21.0.2.tgz", + "integrity": "sha512-BtsrnLVycSSKf4Q0gMch4giCj5NNlmcbhc8ra5vONgGtP2IjRDo33bEFtr5Pm+2N+5fXGWb2MksWPrspPfdhdw==", "dev": true, "license": "MIT", "dependencies": { - "@commitlint/top-level": "^21.0.1", + "@commitlint/top-level": "^21.0.2", "@commitlint/types": "^21.0.1", "git-raw-commits": "^5.0.0", "tinyexec": "^1.0.0" @@ -854,6 +952,8 @@ }, "node_modules/@commitlint/resolve-extends": { "version": "21.0.1", + "resolved": "https://registry.npmjs.org/@commitlint/resolve-extends/-/resolve-extends-21.0.1.tgz", + "integrity": "sha512-0DhjYWL6uYrY16Efa032fYk3woGJDU4AGWiG1XXltT9AMUNYKyb5cIZU2ivbaMZ3+kKFqUjikD2cjh66Sbh/Sg==", "dev": true, "license": "MIT", "dependencies": { @@ -868,12 +968,14 @@ } }, "node_modules/@commitlint/rules": { - "version": "21.0.1", + "version": "21.0.2", + "resolved": "https://registry.npmjs.org/@commitlint/rules/-/rules-21.0.2.tgz", + "integrity": "sha512-k6tQ69Td7t2qUSIbik8D3TL1q3ZJpkEbV+yLogDzCRAdOxJm4ndhtBNREsLA1/puRfWvzS9eioF2w43WT+hHgQ==", "dev": true, "license": "MIT", "dependencies": { "@commitlint/ensure": "^21.0.1", - "@commitlint/message": "^21.0.1", + "@commitlint/message": "^21.0.2", "@commitlint/to-lines": "^21.0.1", "@commitlint/types": "^21.0.1" }, @@ -883,6 +985,8 @@ }, "node_modules/@commitlint/to-lines": { "version": "21.0.1", + "resolved": "https://registry.npmjs.org/@commitlint/to-lines/-/to-lines-21.0.1.tgz", + "integrity": "sha512-bd1BFII7p1EQZre9Kaj+kKaMFP3cFCdt21K7DItVux9XP5WjLgJ0/Uy1pJJh9aPwVJ6SKg62PxqlZaHI8hQAXw==", "dev": true, "license": "MIT", "engines": { @@ -890,7 +994,9 @@ } }, "node_modules/@commitlint/top-level": { - "version": "21.0.1", + "version": "21.0.2", + "resolved": "https://registry.npmjs.org/@commitlint/top-level/-/top-level-21.0.2.tgz", + "integrity": "sha512-s9KKM+e+mXgFeIh4n7KmOGAVT3mkJ3Fp1bBYHIK5pjeUwlEMzp/tZfb5u0Poa680AsQTXMEMRxZi1vQ9m2X5ug==", "dev": true, "license": "MIT", "dependencies": { @@ -902,6 +1008,8 @@ }, "node_modules/@commitlint/types": { "version": "21.0.1", + "resolved": "https://registry.npmjs.org/@commitlint/types/-/types-21.0.1.tgz", + "integrity": "sha512-4u7w8jcoCUFWhjWnASYzZHAP34OqOtuFBN87nQmFvqda03YU0T6z+yB4w0gSAMpekiRqqGk5rt+qSlW+a2vSEg==", "dev": true, "license": "MIT", "dependencies": { @@ -914,6 +1022,8 @@ }, "node_modules/@conventional-changelog/git-client": { "version": "2.7.0", + "resolved": "https://registry.npmjs.org/@conventional-changelog/git-client/-/git-client-2.7.0.tgz", + "integrity": "sha512-j7A8/LBEQ+3rugMzPXoKYzyUPpw/0CBQCyvtTR7Lmu4olG4yRC/Tfkq79Mr3yuPs0SUitlO2HwGP3gitMJnRFw==", "dev": true, "license": "MIT", "dependencies": { @@ -938,9 +1048,9 @@ } }, "node_modules/@esbuild/aix-ppc64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.0.tgz", - "integrity": "sha512-lhRUCeuOyJQURhTxl4WkpFTjIsbDayJHih5kZC1giwE+MhIzAb7mEsQMqMf18rHLsrb5qI1tafG20mLxEWcWlA==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.1.tgz", + "integrity": "sha512-Svl7tq8k/08+p6CXPpRjQ1fKX+1odH/BQbb48fV6fj3CWHhsoIOoY87w1oHXm0qEpkIK3ZfVgp0hed3XBXzXMQ==", "cpu": [ "ppc64" ], @@ -955,9 +1065,9 @@ } }, "node_modules/@esbuild/android-arm": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.0.tgz", - "integrity": "sha512-wqh0ByljabXLKHeWXYLqoJ5jKC4XBaw6Hk08OfMrCRd2nP2ZQ5eleDZC41XHyCNgktBGYMbqnrJKq/K/lzPMSQ==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.1.tgz", + "integrity": "sha512-0k2F129Xdio1TdJfzJ8sy1Q47vUD2NnwdhiAf7drUN1EBTfPf4hsFCtmMgu/6m8JSzsBrlmVjudMBQqOfG8usQ==", "cpu": [ "arm" ], @@ -972,9 +1082,9 @@ } }, "node_modules/@esbuild/android-arm64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.0.tgz", - "integrity": "sha512-+WzIXQOSaGs33tLEgYPYe/yQHf0WTU0X42Jca3y8NWMbUVhp7rUnw+vAsRC/QiDrdD31IszMrZy+qwPOPjd+rw==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.1.tgz", + "integrity": "sha512-34EGEbCIAgosYz6goLcopX6Mo7NyGv9tfwEM2/7Ce2VcVRk568iSvniGWcUXIy7wEDR1wzolcxcriFVrWYcwBg==", "cpu": [ "arm64" ], @@ -989,9 +1099,9 @@ } }, "node_modules/@esbuild/android-x64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.0.tgz", - "integrity": "sha512-+VJggoaKhk2VNNqVL7f6S189UzShHC/mR9EE8rDdSkdpN0KflSwWY/gWjDrNxxisg8Fp1ZCD9jLMo4m0OUfeUA==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.1.tgz", + "integrity": "sha512-dbwY7ltSMDWsRatcRpCnES4F+im88OCUgGZjy52shC7GqHRE/cYlxNbB4Z4UpJswpcc4Qxd2oE/ufM0p61IKng==", "cpu": [ "x64" ], @@ -1006,7 +1116,9 @@ } }, "node_modules/@esbuild/darwin-arm64": { - "version": "0.28.0", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.1.tgz", + "integrity": "sha512-TZbWkQY7kvTAXbXUT7uVACR5cMHsDiSz9z7ZKAX/RTq/WJEk3QyRr0wZpNhBDX+/0CtdqUIJlOiodQcta6tY3Q==", "cpu": [ "arm64" ], @@ -1021,9 +1133,9 @@ } }, "node_modules/@esbuild/darwin-x64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.0.tgz", - "integrity": "sha512-fyzLm/DLDl/84OCfp2f/XQ4flmORsjU7VKt8HLjvIXChJoFFOIL6pLJPH4Yhd1n1gGFF9mPwtlN5Wf82DZs+LQ==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.1.tgz", + "integrity": "sha512-zfdzgK9ACBNZLI/CyHTOx81SyNbM6YXn7rxSgX97VjyiPl9W1i4Ka4fgKECEoFCKGpvBj5qArWIGgQjOwkgskQ==", "cpu": [ "x64" ], @@ -1038,9 +1150,9 @@ } }, "node_modules/@esbuild/freebsd-arm64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.0.tgz", - "integrity": "sha512-l9GeW5UZBT9k9brBYI+0WDffcRxgHQD8ShN2Ur4xWq/NFzUKm3k5lsH4PdaRgb2w7mI9u61nr2gI2mLI27Nh3Q==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.1.tgz", + "integrity": "sha512-wG2EA8ENdEI0qhkSZMjfqrdY+ziCYCPMmtZjjIwOmXFjmyzEHn+UUxk5of+SYsjtfs3VpnlC7QLzSI5hY/rOAw==", "cpu": [ "arm64" ], @@ -1055,9 +1167,9 @@ } }, "node_modules/@esbuild/freebsd-x64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.0.tgz", - "integrity": "sha512-BXoQai/A0wPO6Es3yFJ7APCiKGc1tdAEOgeTNy3SsB491S3aHn4S4r3e976eUnPdU+NbdtmBuLncYir2tMU9Nw==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.1.tgz", + "integrity": "sha512-i7dZ9vQgnvSCzi/rYCXNgtF/U+eKZNJBzu3eTQbRgHnM7tNSizLOkRFAl3qzVc/Op/u5YkHHa4pf/3DOYHthLQ==", "cpu": [ "x64" ], @@ -1072,9 +1184,9 @@ } }, "node_modules/@esbuild/linux-arm": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.0.tgz", - "integrity": "sha512-CjaaREJagqJp7iTaNQjjidaNbCKYcd4IDkzbwwxtSvjI7NZm79qiHc8HqciMddQ6CKvJT6aBd8lO9kN/ZudLlw==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.1.tgz", + "integrity": "sha512-qVXBOHQS+d5Y722GwJzJUtOLlX7km3CraOaGormF1pDtPd2C/l1SHRPgjLunLGe51Sh5YYWKMFDyV4SxgMQYTQ==", "cpu": [ "arm" ], @@ -1089,9 +1201,9 @@ } }, "node_modules/@esbuild/linux-arm64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.0.tgz", - "integrity": "sha512-RVyzfb3FWsGA55n6WY0MEIEPURL1FcbhFE6BffZEMEekfCzCIMtB5yyDcFnVbTnwk+CLAgTujmV/Lgvih56W+A==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.1.tgz", + "integrity": "sha512-yHs+0uc8+nvEAfAfxrWQKK5peSNzBc4PegcMO0EJ2hT71uA7vB8Ihg2e77R2P7SG5uYjPbHlLLmve4LLLRCf0g==", "cpu": [ "arm64" ], @@ -1106,9 +1218,9 @@ } }, "node_modules/@esbuild/linux-ia32": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.0.tgz", - "integrity": "sha512-KBnSTt1kxl9x70q+ydterVdl+Cn0H18ngRMRCEQfrbqdUuntQQ0LoMZv47uB97NljZFzY6HcfqEZ2SAyIUTQBQ==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.1.tgz", + "integrity": "sha512-d1z4ZuP0ajrfz/FhGT4vv278rX8KnPPJx8i5+AtK7TYbx9Le9F1hyzurZpkEyjkGa9dUGhQow4C1NmeGvqxN2w==", "cpu": [ "ia32" ], @@ -1123,9 +1235,9 @@ } }, "node_modules/@esbuild/linux-loong64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.0.tgz", - "integrity": "sha512-zpSlUce1mnxzgBADvxKXX5sl8aYQHo2ezvMNI8I0lbblJtp8V4odlm3Yzlj7gPyt3T8ReksE6bK+pT3WD+aJRg==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.1.tgz", + "integrity": "sha512-M5sRjUVZrkm1OAPR3dlOYzNmN+loZKGVi1VUQGrwuqLcbR6qeAz+famMhjASeH3YVKvZz+zT1jlh/keC3Rj/lg==", "cpu": [ "loong64" ], @@ -1140,9 +1252,9 @@ } }, "node_modules/@esbuild/linux-mips64el": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.0.tgz", - "integrity": "sha512-2jIfP6mmjkdmeTlsX/9vmdmhBmKADrWqN7zcdtHIeNSCH1SqIoNI63cYsjQR8J+wGa4Y5izRcSHSm8K3QWmk3w==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.1.tgz", + "integrity": "sha512-mRObBZeHh2OxcBFPWE/FjylkRgZdYuiTR3vaTozquCGOH14iP9oN4x4Ge81CoIDYQrXmIxpFumJBu5MtZpnQJQ==", "cpu": [ "mips64el" ], @@ -1157,9 +1269,9 @@ } }, "node_modules/@esbuild/linux-ppc64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.0.tgz", - "integrity": "sha512-bc0FE9wWeC0WBm49IQMPSPILRocGTQt3j5KPCA8os6VprfuJ7KD+5PzESSrJ6GmPIPJK965ZJHTUlSA6GNYEhg==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.1.tgz", + "integrity": "sha512-slScBsMAb3GFDcdrCgLwZtPYRoH2H/youv10QiZyRjmsP48fznoveWytSgCI/R0ZcUgpc0ZhIUEx6LHts8yrfQ==", "cpu": [ "ppc64" ], @@ -1174,9 +1286,9 @@ } }, "node_modules/@esbuild/linux-riscv64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.0.tgz", - "integrity": "sha512-SQPZOwoTTT/HXFXQJG/vBX8sOFagGqvZyXcgLA3NhIqcBv1BJU1d46c0rGcrij2B56Z2rNiSLaZOYW5cUk7yLQ==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.1.tgz", + "integrity": "sha512-kw0owk1o0GFETUJyW0jc0G4Yzs0BHZn0JDZ8JRT088vjJYX777BAs1fDGxAC+q831qOs2DTC96mNsG2opdfyyQ==", "cpu": [ "riscv64" ], @@ -1191,9 +1303,9 @@ } }, "node_modules/@esbuild/linux-s390x": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.0.tgz", - "integrity": "sha512-SCfR0HN8CEEjnYnySJTd2cw0k9OHB/YFzt5zgJEwa+wL/T/raGWYMBqwDNAC6dqFKmJYZoQBRfHjgwLHGSrn3Q==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.1.tgz", + "integrity": "sha512-/lAIjX8aYFRByhh6L5rYtPEDRqa9de/4V/juOXcta5frjvzXO4/sqEtyytse0g3zZFuWu5cDN0MkLz2qRDD2Ag==", "cpu": [ "s390x" ], @@ -1208,9 +1320,9 @@ } }, "node_modules/@esbuild/linux-x64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.0.tgz", - "integrity": "sha512-us0dSb9iFxIi8srnpl931Nvs65it/Jd2a2K3qs7fz2WfGPHqzfzZTfec7oxZJRNPXPnNYZtanmRc4AL/JwVzHQ==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.1.tgz", + "integrity": "sha512-u/anNYF2mmVOEDwLtnQ1wOr3EZ9sTNGLWrsYGYwHWzGA3Si84IOkHXlbWTD1NB+9/1lcnweYKO54uhxZydNzfA==", "cpu": [ "x64" ], @@ -1225,9 +1337,9 @@ } }, "node_modules/@esbuild/netbsd-arm64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.0.tgz", - "integrity": "sha512-CR/RYotgtCKwtftMwJlUU7xCVNg3lMYZ0RzTmAHSfLCXw3NtZtNpswLEj/Kkf6kEL3Gw+BpOekRX0BYCtklhUw==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.1.tgz", + "integrity": "sha512-oks0DYbLwWMmaakTsCb+zL4E+aHRVLom9IJZOAthMQEPiQmydXHkziYEsGYRx0uNV/IjEKGAV941JzH02pflqw==", "cpu": [ "arm64" ], @@ -1242,9 +1354,9 @@ } }, "node_modules/@esbuild/netbsd-x64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.0.tgz", - "integrity": "sha512-nU1yhmYutL+fQ71Kxnhg8uEOdC0pwEW9entHykTgEbna2pw2dkbFSMeqjjyHZoCmt8SBkOSvV+yNmm94aUrrqw==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.1.tgz", + "integrity": "sha512-aeL6lAnN89Hz43Mlh1G8ARasbuoYvSITDEx0tHh5b7jJnHcssqgjy9Yx430GDpmCa6OyrKoS0aNRjKundRizGg==", "cpu": [ "x64" ], @@ -1259,9 +1371,9 @@ } }, "node_modules/@esbuild/openbsd-arm64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.0.tgz", - "integrity": "sha512-cXb5vApOsRsxsEl4mcZ1XY3D4DzcoMxR/nnc4IyqYs0rTI8ZKmW6kyyg+11Z8yvgMfAEldKzP7AdP64HnSC/6g==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.1.tgz", + "integrity": "sha512-MEFJe5C3R8pwXdZ5Y21oo6m7ePiS0d9pWucn99O/wvyJZChoIQKrQDxKrGeW8F5+T0okTHesAmDeiHDTIq0V/Q==", "cpu": [ "arm64" ], @@ -1276,9 +1388,9 @@ } }, "node_modules/@esbuild/openbsd-x64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.0.tgz", - "integrity": "sha512-8wZM2qqtv9UP3mzy7HiGYNH/zjTA355mpeuA+859TyR+e+Tc08IHYpLJuMsfpDJwoLo1ikIJI8jC3GFjnRClzA==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.1.tgz", + "integrity": "sha512-i/ZLIOafE0Z8cI/XANJAixoJL/uRAoS2xOA3rb0xN+KK0K177cMAsQYkzHtBrtMXAKuAc7HGgcWiZ/sRC1Nxgw==", "cpu": [ "x64" ], @@ -1293,9 +1405,9 @@ } }, "node_modules/@esbuild/openharmony-arm64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.0.tgz", - "integrity": "sha512-FLGfyizszcef5C3YtoyQDACyg95+dndv79i2EekILBofh5wpCa1KuBqOWKrEHZg3zrL3t5ouE5jgr94vA+Wb2w==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.1.tgz", + "integrity": "sha512-ge+Z7EXFNt2BO1oAMsVpiQ8EwndV9i1xXerAeTIK7AtPs3bKFXQM7nlRxDSIUIMeueR1CNXxqztLzdNeReKBJg==", "cpu": [ "arm64" ], @@ -1310,9 +1422,9 @@ } }, "node_modules/@esbuild/sunos-x64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.0.tgz", - "integrity": "sha512-1ZgjUoEdHZZl/YlV76TSCz9Hqj9h9YmMGAgAPYd+q4SicWNX3G5GCyx9uhQWSLcbvPW8Ni7lj4gDa1T40akdlw==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.1.tgz", + "integrity": "sha512-BEjgtECkL3vY+SaSQ6nzVfiALUeFxpawyp8Jmf5PtYhf1Ug40N1h/hxlhts+f1FvSvarEigdxS3BlSMI2PJLcQ==", "cpu": [ "x64" ], @@ -1327,9 +1439,9 @@ } }, "node_modules/@esbuild/win32-arm64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.0.tgz", - "integrity": "sha512-Q9StnDmQ/enxnpxCCLSg0oo4+34B9TdXpuyPeTedN/6+iXBJ4J+zwfQI28u/Jl40nOYAxGoNi7mFP40RUtkmUA==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.1.tgz", + "integrity": "sha512-lCv9eK/H6ZJWbE7bh2nw54CZ9M2nupBxJcTsdk/QQnWkdSjKGuxmmH8/GWrlT1eMmZfn4dGcCjRte397WqfQXA==", "cpu": [ "arm64" ], @@ -1344,9 +1456,9 @@ } }, "node_modules/@esbuild/win32-ia32": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.0.tgz", - "integrity": "sha512-zF3ag/gfiCe6U2iczcRzSYJKH1DCI+ByzSENHlM2FcDbEeo5Zd2C86Aq0tKUYAJJ1obRP84ymxIAksZUcdztHA==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.1.tgz", + "integrity": "sha512-zvb/mB2bSCoJOpoCBgYKKpX6YM6mJBlBUVUtVj41DlZJVEB6/0CKlRYxP5wWl1C1ILiCoAU5wZZ4q1P3qeS6Eg==", "cpu": [ "ia32" ], @@ -1361,9 +1473,9 @@ } }, "node_modules/@esbuild/win32-x64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.0.tgz", - "integrity": "sha512-pEl1bO9mfAmIC+tW5btTmrKaujg3zGtUmWNdCw/xs70FBjwAL3o9OEKNHvNmnyylD6ubxUERiEhdsL0xBQ9efw==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.1.tgz", + "integrity": "sha512-bm4Mowrv+GXMlpWX++EcXw/iLyd1o3+bJkC2DkWXYVvgZCqD/bSj9ctZeAMC3cIxgjRVR2Dufaiu4YPxr5gW1A==", "cpu": [ "x64" ], @@ -1379,6 +1491,8 @@ }, "node_modules/@fluent/syntax": { "version": "0.19.0", + "resolved": "https://registry.npmjs.org/@fluent/syntax/-/syntax-0.19.0.tgz", + "integrity": "sha512-5D2qVpZrgpjtqU4eNOcWGp1gnUCgjfM+vKGE2y03kKN6z5EBhtx0qdRFbg8QuNNj8wXNoX93KJoYb+NqoxswmQ==", "dev": true, "license": "Apache-2.0", "engines": { @@ -1387,25 +1501,29 @@ } }, "node_modules/@inquirer/ansi": { - "version": "2.0.6", + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@inquirer/ansi/-/ansi-2.0.7.tgz", + "integrity": "sha512-3eTuUO1vH2cZm2ZKHeQxnOqlTi9EfZDGgIe3BL3I4u+rJHocr9Fz86M4fjYABPvFnQG/gGK551HqDiIcETwU6Q==", "dev": true, "license": "MIT", "engines": { - "node": ">=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0" + "node": ">=23.5.0 || ^22.13.0 || ^20.17.0" } }, "node_modules/@inquirer/checkbox": { - "version": "5.2.0", + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/@inquirer/checkbox/-/checkbox-5.2.1.tgz", + "integrity": "sha512-b6xmA/VlTe0ZgDQHDui+Nav470u7u49nRd8/iuhOcQPO9Ch7lGuogydhi2VOmNlZ+zXcM8IcPuNSwQcdJaF/kw==", "dev": true, "license": "MIT", "dependencies": { - "@inquirer/ansi": "^2.0.6", - "@inquirer/core": "^11.2.0", - "@inquirer/figures": "^2.0.6", - "@inquirer/type": "^4.0.6" + "@inquirer/ansi": "^2.0.7", + "@inquirer/core": "^11.2.1", + "@inquirer/figures": "^2.0.7", + "@inquirer/type": "^4.0.7" }, "engines": { - "node": ">=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0" + "node": ">=23.5.0 || ^22.13.0 || ^20.17.0" }, "peerDependencies": { "@types/node": ">=18" @@ -1417,15 +1535,17 @@ } }, "node_modules/@inquirer/confirm": { - "version": "6.1.0", + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/@inquirer/confirm/-/confirm-6.1.1.tgz", + "integrity": "sha512-eb8DBZcz/2qHWQda4rk2JiQk5h9QV/cVHi1yjt0f69WFZMRFn0sJTye3EAP8icut8UDMjQPsaH5KbcOogefrFQ==", "dev": true, "license": "MIT", "dependencies": { - "@inquirer/core": "^11.2.0", - "@inquirer/type": "^4.0.6" + "@inquirer/core": "^11.2.1", + "@inquirer/type": "^4.0.7" }, "engines": { - "node": ">=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0" + "node": ">=23.5.0 || ^22.13.0 || ^20.17.0" }, "peerDependencies": { "@types/node": ">=18" @@ -1437,20 +1557,22 @@ } }, "node_modules/@inquirer/core": { - "version": "11.2.0", + "version": "11.2.1", + "resolved": "https://registry.npmjs.org/@inquirer/core/-/core-11.2.1.tgz", + "integrity": "sha512-Qd6GJT1yVyrZZCfN8W2qKF5ApmqryXRhRKCuip8h01x2w/esJQ2XIYc6f9abMIHgKQdBfFTSOdbHRLAhuM09UA==", "dev": true, "license": "MIT", "dependencies": { - "@inquirer/ansi": "^2.0.6", - "@inquirer/figures": "^2.0.6", - "@inquirer/type": "^4.0.6", + "@inquirer/ansi": "^2.0.7", + "@inquirer/figures": "^2.0.7", + "@inquirer/type": "^4.0.7", "cli-width": "^4.1.0", "fast-wrap-ansi": "^0.2.0", - "mute-stream": "^4.0.0", + "mute-stream": "^3.0.0", "signal-exit": "^4.1.0" }, "engines": { - "node": ">=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0" + "node": ">=23.5.0 || ^22.13.0 || ^20.17.0" }, "peerDependencies": { "@types/node": ">=18" @@ -1462,16 +1584,18 @@ } }, "node_modules/@inquirer/editor": { - "version": "5.2.0", + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/@inquirer/editor/-/editor-5.2.2.tgz", + "integrity": "sha512-ZRVd/oD+sYsUd5zVm0NflqEzlqfYCyHNsqkHl2oWXEUHs12tCbcSFi+wVFEvD8+LGRaMUsVrE7qeo6lSG/S1Vg==", "dev": true, "license": "MIT", "dependencies": { - "@inquirer/core": "^11.2.0", - "@inquirer/external-editor": "^3.0.1", - "@inquirer/type": "^4.0.6" + "@inquirer/core": "^11.2.1", + "@inquirer/external-editor": "^3.0.3", + "@inquirer/type": "^4.0.7" }, "engines": { - "node": ">=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0" + "node": ">=23.5.0 || ^22.13.0 || ^20.17.0" }, "peerDependencies": { "@types/node": ">=18" @@ -1483,15 +1607,17 @@ } }, "node_modules/@inquirer/expand": { - "version": "5.1.0", + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/@inquirer/expand/-/expand-5.1.1.tgz", + "integrity": "sha512-YmQpenjbFSHAK3sOd44puHh3V1KXXr+JiNpUztoSQ4drLh2rTVzTap/YtlAVu/5xavifIlBfNEzJ/neZJ1a/1g==", "dev": true, "license": "MIT", "dependencies": { - "@inquirer/core": "^11.2.0", - "@inquirer/type": "^4.0.6" + "@inquirer/core": "^11.2.1", + "@inquirer/type": "^4.0.7" }, "engines": { - "node": ">=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0" + "node": ">=23.5.0 || ^22.13.0 || ^20.17.0" }, "peerDependencies": { "@types/node": ">=18" @@ -1503,7 +1629,9 @@ } }, "node_modules/@inquirer/external-editor": { - "version": "3.0.1", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@inquirer/external-editor/-/external-editor-3.0.3.tgz", + "integrity": "sha512-6thf5I8q7lZwzGLAxPaaGEREEkZ3nyePPDQ1oyobblxmEE8mqTLguScP7pDjUTAibiyb4hfXl+qjUEJ+di/aNA==", "dev": true, "license": "MIT", "dependencies": { @@ -1511,7 +1639,7 @@ "iconv-lite": "^0.7.2" }, "engines": { - "node": ">=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0" + "node": ">=23.5.0 || ^22.13.0 || ^20.17.0" }, "peerDependencies": { "@types/node": ">=18" @@ -1523,23 +1651,27 @@ } }, "node_modules/@inquirer/figures": { - "version": "2.0.6", + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@inquirer/figures/-/figures-2.0.7.tgz", + "integrity": "sha512-aJ8TBPOGB6f/2qziPfElISTCEd5XOYTFckA2SGjhNmiKzfK/u4ot3v0DUzGVdUnKjN10EqnnEPck36BkyfLnJw==", "dev": true, "license": "MIT", "engines": { - "node": ">=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0" + "node": ">=23.5.0 || ^22.13.0 || ^20.17.0" } }, "node_modules/@inquirer/input": { - "version": "5.1.0", + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/@inquirer/input/-/input-5.1.2.tgz", + "integrity": "sha512-9K/DDBSQpOyZSkt6sOVP9Vo0TR7atX2kuILsUu0x3wVcVbe97lJwIJKMLdMw25tDYuXl/qp6erT0Xs1rfmcfZg==", "dev": true, "license": "MIT", "dependencies": { - "@inquirer/core": "^11.2.0", - "@inquirer/type": "^4.0.6" + "@inquirer/core": "^11.2.1", + "@inquirer/type": "^4.0.7" }, "engines": { - "node": ">=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0" + "node": ">=23.5.0 || ^22.13.0 || ^20.17.0" }, "peerDependencies": { "@types/node": ">=18" @@ -1551,15 +1683,17 @@ } }, "node_modules/@inquirer/number": { - "version": "4.1.0", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/@inquirer/number/-/number-4.1.1.tgz", + "integrity": "sha512-XF4IXAbPnGPgw0wsbC/i2tPcyfdZgDpUlhsqU0SfT4IRIGWha6Xm9VRgN5yYxJq+jnyXlfXI/nQ3ulfk0iEICA==", "dev": true, "license": "MIT", "dependencies": { - "@inquirer/core": "^11.2.0", - "@inquirer/type": "^4.0.6" + "@inquirer/core": "^11.2.1", + "@inquirer/type": "^4.0.7" }, "engines": { - "node": ">=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0" + "node": ">=23.5.0 || ^22.13.0 || ^20.17.0" }, "peerDependencies": { "@types/node": ">=18" @@ -1571,16 +1705,18 @@ } }, "node_modules/@inquirer/password": { - "version": "5.1.0", + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/@inquirer/password/-/password-5.1.1.tgz", + "integrity": "sha512-3XBfF7DAsp5qeDsvN5Rd1HmbNokVvEQoUM0QLrRcybC9nX96w3Pbmu7qUsb3IT3J3jBvs2+mTXaKHOUsgHMLzg==", "dev": true, "license": "MIT", "dependencies": { - "@inquirer/ansi": "^2.0.6", - "@inquirer/core": "^11.2.0", - "@inquirer/type": "^4.0.6" + "@inquirer/ansi": "^2.0.7", + "@inquirer/core": "^11.2.1", + "@inquirer/type": "^4.0.7" }, "engines": { - "node": ">=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0" + "node": ">=23.5.0 || ^22.13.0 || ^20.17.0" }, "peerDependencies": { "@types/node": ">=18" @@ -1592,23 +1728,25 @@ } }, "node_modules/@inquirer/prompts": { - "version": "8.5.0", + "version": "8.5.2", + "resolved": "https://registry.npmjs.org/@inquirer/prompts/-/prompts-8.5.2.tgz", + "integrity": "sha512-IYR/3C/paEVVQYQvdDlFZVjRCJVYHHON0XXMH91KO9GSxs0TdKYWlUdvfQl2EfAHDxUaN3IBffkE/BDTh5nJ6g==", "dev": true, "license": "MIT", "dependencies": { - "@inquirer/checkbox": "^5.2.0", - "@inquirer/confirm": "^6.1.0", - "@inquirer/editor": "^5.2.0", - "@inquirer/expand": "^5.1.0", - "@inquirer/input": "^5.1.0", - "@inquirer/number": "^4.1.0", - "@inquirer/password": "^5.1.0", - "@inquirer/rawlist": "^5.3.0", - "@inquirer/search": "^4.2.0", - "@inquirer/select": "^5.2.0" + "@inquirer/checkbox": "^5.2.1", + "@inquirer/confirm": "^6.1.1", + "@inquirer/editor": "^5.2.2", + "@inquirer/expand": "^5.1.1", + "@inquirer/input": "^5.1.2", + "@inquirer/number": "^4.1.1", + "@inquirer/password": "^5.1.1", + "@inquirer/rawlist": "^5.3.1", + "@inquirer/search": "^4.2.1", + "@inquirer/select": "^5.2.1" }, "engines": { - "node": ">=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0" + "node": ">=23.5.0 || ^22.13.0 || ^20.17.0" }, "peerDependencies": { "@types/node": ">=18" @@ -1620,15 +1758,17 @@ } }, "node_modules/@inquirer/rawlist": { - "version": "5.3.0", + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/@inquirer/rawlist/-/rawlist-5.3.1.tgz", + "integrity": "sha512-QqdTqQddL3qPX/PPrjobpsO25NZ4dWXgTLenrR445L2ptLEYE6Z+PD5c5CNDJNx4ugRgELAIpSIJxZaO2jJ2Og==", "dev": true, "license": "MIT", "dependencies": { - "@inquirer/core": "^11.2.0", - "@inquirer/type": "^4.0.6" + "@inquirer/core": "^11.2.1", + "@inquirer/type": "^4.0.7" }, "engines": { - "node": ">=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0" + "node": ">=23.5.0 || ^22.13.0 || ^20.17.0" }, "peerDependencies": { "@types/node": ">=18" @@ -1640,16 +1780,18 @@ } }, "node_modules/@inquirer/search": { - "version": "4.2.0", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@inquirer/search/-/search-4.2.1.tgz", + "integrity": "sha512-xJj8QWKRSrfKoBIITLZK61dD3zwo0Rz11fgDImku30/Oe81zMdIdGgrLY2h6RkJ+KZ/GhNYIRMKnH/62qBTA5g==", "dev": true, "license": "MIT", "dependencies": { - "@inquirer/core": "^11.2.0", - "@inquirer/figures": "^2.0.6", - "@inquirer/type": "^4.0.6" + "@inquirer/core": "^11.2.1", + "@inquirer/figures": "^2.0.7", + "@inquirer/type": "^4.0.7" }, "engines": { - "node": ">=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0" + "node": ">=23.5.0 || ^22.13.0 || ^20.17.0" }, "peerDependencies": { "@types/node": ">=18" @@ -1661,17 +1803,19 @@ } }, "node_modules/@inquirer/select": { - "version": "5.2.0", + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/@inquirer/select/-/select-5.2.1.tgz", + "integrity": "sha512-FlDndEUww8m7BfukO2nJa25vhD+H5jxxCv4oGioKqzyWz3nPHhhw4LKdYRSlXuAx7DsdWia7iyaBPKKS95Evfw==", "dev": true, "license": "MIT", "dependencies": { - "@inquirer/ansi": "^2.0.6", - "@inquirer/core": "^11.2.0", - "@inquirer/figures": "^2.0.6", - "@inquirer/type": "^4.0.6" + "@inquirer/ansi": "^2.0.7", + "@inquirer/core": "^11.2.1", + "@inquirer/figures": "^2.0.7", + "@inquirer/type": "^4.0.7" }, "engines": { - "node": ">=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0" + "node": ">=23.5.0 || ^22.13.0 || ^20.17.0" }, "peerDependencies": { "@types/node": ">=18" @@ -1683,11 +1827,13 @@ } }, "node_modules/@inquirer/type": { - "version": "4.0.6", + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/@inquirer/type/-/type-4.0.7.tgz", + "integrity": "sha512-t28inv14nMQ1PhKpsJPY+kEs/c00qzeCOS2gTNRyTjG5d6qsVA2fItxW4hkvGZ5lvanGLdtCzVIx5dwdRpN1+g==", "dev": true, "license": "MIT", "engines": { - "node": ">=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0" + "node": ">=23.5.0 || ^22.13.0 || ^20.17.0" }, "peerDependencies": { "@types/node": ">=18" @@ -1700,6 +1846,8 @@ }, "node_modules/@jridgewell/gen-mapping": { "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", "dev": true, "license": "MIT", "dependencies": { @@ -1709,6 +1857,8 @@ }, "node_modules/@jridgewell/remapping": { "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", "dev": true, "license": "MIT", "dependencies": { @@ -1718,6 +1868,8 @@ }, "node_modules/@jridgewell/resolve-uri": { "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", "dev": true, "license": "MIT", "engines": { @@ -1726,11 +1878,15 @@ }, "node_modules/@jridgewell/sourcemap-codec": { "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", "dev": true, "license": "MIT" }, "node_modules/@jridgewell/trace-mapping": { "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", "dev": true, "license": "MIT", "dependencies": { @@ -1740,6 +1896,8 @@ }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", "dev": true, "license": "MIT", "dependencies": { @@ -1752,6 +1910,8 @@ }, "node_modules/@nodelib/fs.stat": { "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", "dev": true, "license": "MIT", "engines": { @@ -1760,6 +1920,8 @@ }, "node_modules/@nodelib/fs.walk": { "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", "dev": true, "license": "MIT", "dependencies": { @@ -1772,11 +1934,15 @@ }, "node_modules/@sec-ant/readable-stream": { "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@sec-ant/readable-stream/-/readable-stream-0.4.1.tgz", + "integrity": "sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg==", "dev": true, "license": "MIT" }, "node_modules/@silverbucket/ajv-formats-draft2019": { "version": "1.6.5", + "resolved": "https://registry.npmjs.org/@silverbucket/ajv-formats-draft2019/-/ajv-formats-draft2019-1.6.5.tgz", + "integrity": "sha512-TUN/aSGMt3mZF45oy+kfCKtnKjBbSRVYFJxZKnGCPbKynPI2tsGyLgD9GEwFS0NLPbX0hUYHYeyFVoJ33HlMCw==", "dev": true, "license": "MIT", "dependencies": { @@ -1790,11 +1956,15 @@ }, "node_modules/@silverbucket/iana-schemes": { "version": "1.4.4", + "resolved": "https://registry.npmjs.org/@silverbucket/iana-schemes/-/iana-schemes-1.4.4.tgz", + "integrity": "sha512-2iUk6DlqdpQQKs3qrCZDf4LQLH1GPtUXmYZFeq0NWw1WkGY2iJBP4aeYZ5v+3ITxtQA3M0t/Sua8AKu+o4O0KA==", "dev": true, "license": "MIT" }, "node_modules/@simple-libs/child-process-utils": { "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@simple-libs/child-process-utils/-/child-process-utils-1.0.2.tgz", + "integrity": "sha512-/4R8QKnd/8agJynkNdJmNw2MBxuFTRcNFnE5Sg/G+jkSsV8/UBgULMzhizWWW42p8L5H7flImV2ATi79Ove2Tw==", "dev": true, "license": "MIT", "dependencies": { @@ -1809,6 +1979,8 @@ }, "node_modules/@simple-libs/stream-utils": { "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@simple-libs/stream-utils/-/stream-utils-1.2.0.tgz", + "integrity": "sha512-KxXvfapcixpz6rVEB6HPjOUZT22yN6v0vI0urQSk1L8MlEWPDFCZkhw2xmkyoTGYeFw7tWTZd7e3lVzRZRN/EA==", "dev": true, "license": "MIT", "engines": { @@ -1820,6 +1992,8 @@ }, "node_modules/@sindresorhus/merge-streams": { "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/merge-streams/-/merge-streams-4.0.0.tgz", + "integrity": "sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ==", "dev": true, "license": "MIT", "engines": { @@ -1831,6 +2005,8 @@ }, "node_modules/@stryker-mutator/api": { "version": "9.6.1", + "resolved": "https://registry.npmjs.org/@stryker-mutator/api/-/api-9.6.1.tgz", + "integrity": "sha512-g8VNoFWQWbx0pdal3Vt8jVCZW+v3sc3gi94iI0GVtVgUGTqphAjJF6EAruPTx0lqvtonsaAxn5TD36hcG1d6Wg==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -1845,6 +2021,8 @@ }, "node_modules/@stryker-mutator/core": { "version": "9.6.1", + "resolved": "https://registry.npmjs.org/@stryker-mutator/core/-/core-9.6.1.tgz", + "integrity": "sha512-WMgnvf+Wyh/yiruhNZwc8w8DlzmmjXhPjSn5MR8RhAXzlnWji8TQrUYgBUkHk9bEgSaIlB3KZHm37iiU5Q2cLQ==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -1884,6 +2062,8 @@ }, "node_modules/@stryker-mutator/core/node_modules/ajv": { "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", + "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", "dev": true, "license": "MIT", "dependencies": { @@ -1897,41 +2077,10 @@ "url": "https://github.com/sponsors/epoberezkin" } }, - "node_modules/@stryker-mutator/core/node_modules/balanced-match": { - "version": "4.0.4", - "dev": true, - "license": "MIT", - "engines": { - "node": "18 || 20 || >=22" - } - }, - "node_modules/@stryker-mutator/core/node_modules/brace-expansion": { - "version": "5.0.6", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^4.0.2" - }, - "engines": { - "node": "18 || 20 || >=22" - } - }, - "node_modules/@stryker-mutator/core/node_modules/minimatch": { - "version": "10.2.5", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "brace-expansion": "^5.0.5" - }, - "engines": { - "node": "18 || 20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/@stryker-mutator/instrumenter": { "version": "9.6.1", + "resolved": "https://registry.npmjs.org/@stryker-mutator/instrumenter/-/instrumenter-9.6.1.tgz", + "integrity": "sha512-5K8wH4Pthly25c2uKKik4Dfcoeou7sbJdFS6u3QIYHlulgFVDJwtEMWTZGkZfs7IiUEXIDNa0keRACq5jn5AvA==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -1954,6 +2103,8 @@ }, "node_modules/@stryker-mutator/instrumenter/node_modules/semver": { "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", "dev": true, "license": "ISC", "bin": { @@ -1965,11 +2116,15 @@ }, "node_modules/@stryker-mutator/util": { "version": "9.6.1", + "resolved": "https://registry.npmjs.org/@stryker-mutator/util/-/util-9.6.1.tgz", + "integrity": "sha512-Lk/ALVctJjFv1vvwR+CFoKzDCWvsBlq7flDUnmnpuwTrGbm156EdZD1Jjq4o8KdOap0ezUZqQNE9OAI1m2+pUQ==", "dev": true, "license": "Apache-2.0" }, "node_modules/@types/glob": { "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.2.0.tgz", + "integrity": "sha512-ZUxbzKl0IfJILTS6t7ip5fQQM/J3TJYubDm3nMbgubNNYS62eXeUpoLUC8/7fJNiFYHTrGPQn7hspDUzIHX3UA==", "dev": true, "license": "MIT", "dependencies": { @@ -1979,17 +2134,23 @@ }, "node_modules/@types/json-schema": { "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", "dev": true, "license": "MIT", "peer": true }, "node_modules/@types/minimatch": { "version": "5.1.2", + "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-5.1.2.tgz", + "integrity": "sha512-K0VQKziLUWkVKiRVrx4a40iPaxTUefQmjtkQofBkYRcoaaL/8rhwDWww9qWbrgicNOgnpIsMxyNIUM4+n6dUIA==", "dev": true, "license": "MIT" }, "node_modules/@types/node": { - "version": "25.9.1", + "version": "25.9.3", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.9.3.tgz", + "integrity": "sha512-603BddQMv3pUcr4U2dhujk83N2tTDVr/34wII2B6bJy6g+8WD6yUb11jszNs0gdi4PesVWl7ABt8nYMVpnLUcg==", "dev": true, "license": "MIT", "dependencies": { @@ -2002,6 +2163,8 @@ }, "node_modules/@yarnpkg/parsers": { "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@yarnpkg/parsers/-/parsers-3.0.3.tgz", + "integrity": "sha512-mQZgUSgFurUtA07ceMjxrWkYz8QtDuYkvPlu0ZqncgjopQ0t6CNEo/OSealkmnagSUx8ZD5ewvezUwUuMqutQg==", "dev": true, "license": "BSD-2-Clause", "dependencies": { @@ -2014,6 +2177,8 @@ }, "node_modules/@yarnpkg/parsers/node_modules/argparse": { "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", "dev": true, "license": "MIT", "dependencies": { @@ -2022,6 +2187,8 @@ }, "node_modules/@yarnpkg/parsers/node_modules/js-yaml": { "version": "3.14.2", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz", + "integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==", "dev": true, "license": "MIT", "dependencies": { @@ -2034,6 +2201,8 @@ }, "node_modules/ajv": { "version": "8.20.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", + "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.3", @@ -2048,6 +2217,8 @@ }, "node_modules/ajv-cmd": { "version": "0.13.3", + "resolved": "https://registry.npmjs.org/ajv-cmd/-/ajv-cmd-0.13.3.tgz", + "integrity": "sha512-H1LArE4kclZ5JdWLKUZd1ji3UjO6FSpCW1MZ6J0X89BqwT+5W70KD4TpfvhcM7isSoQvo6sslA/rZiCH1HzM6g==", "dev": true, "license": "MIT", "workspaces": [ @@ -2079,6 +2250,8 @@ }, "node_modules/ajv-errors": { "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ajv-errors/-/ajv-errors-3.0.0.tgz", + "integrity": "sha512-V3wD15YHfHz6y0KdhYFjyy9vWtEVALT9UrxfN3zqlI6dMioHnJrqOYfyPKol3oqrnCM9uwkcdCwkJ0WUcbLMTQ==", "dev": true, "license": "MIT", "peerDependencies": { @@ -2087,6 +2260,8 @@ }, "node_modules/ajv-formats": { "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz", + "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==", "dev": true, "license": "MIT", "dependencies": { @@ -2103,6 +2278,8 @@ }, "node_modules/ajv-ftl-i18n": { "version": "0.2.1", + "resolved": "https://registry.npmjs.org/ajv-ftl-i18n/-/ajv-ftl-i18n-0.2.1.tgz", + "integrity": "sha512-ThWHxPmLaJYKrl1mMWAYizGqZG1MrHcMgQ4MId2YFAjRn73GrZqSMX+sifQAHXAva0RZVeGHxVqkrWGizDFo6Q==", "dev": true, "license": "MIT", "workspaces": [ @@ -2125,6 +2302,8 @@ }, "node_modules/ajv-i18n": { "version": "4.2.0", + "resolved": "https://registry.npmjs.org/ajv-i18n/-/ajv-i18n-4.2.0.tgz", + "integrity": "sha512-v/ei2UkCEeuKNXh8RToiFsUclmU+G57LO1Oo22OagNMENIw+Yb8eMwvHu7Vn9fmkjJyv6XclhJ8TbuigSglPkg==", "dev": true, "license": "MIT", "peerDependencies": { @@ -2133,6 +2312,8 @@ }, "node_modules/ajv-keywords": { "version": "5.1.0", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", + "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", "dev": true, "license": "MIT", "dependencies": { @@ -2144,6 +2325,8 @@ }, "node_modules/angular-html-parser": { "version": "10.4.0", + "resolved": "https://registry.npmjs.org/angular-html-parser/-/angular-html-parser-10.4.0.tgz", + "integrity": "sha512-++nLNyZwRfHqFh7akH5Gw/JYizoFlMRz0KRigfwfsLqV8ZqlcVRb1LkPEWdYvEKDnbktknM2J4BXaYUGrQZPww==", "dev": true, "license": "MIT", "engines": { @@ -2152,6 +2335,8 @@ }, "node_modules/ansi-regex": { "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", "dev": true, "license": "MIT", "engines": { @@ -2163,6 +2348,8 @@ }, "node_modules/ansi-styles": { "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", "dev": true, "license": "MIT", "engines": { @@ -2174,16 +2361,22 @@ }, "node_modules/argparse": { "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", "dev": true, "license": "Python-2.0" }, "node_modules/array-ify": { "version": "1.0.0", + "resolved": "https://registry.npmjs.org/array-ify/-/array-ify-1.0.0.tgz", + "integrity": "sha512-c5AMf34bKdvPhQ7tBGhqkgKNUzMr4WUs+WDtC2ZUGOUncbxKMTvqxYctiseW3+L4bA8ec+GcZ6/A/FW4m8ukng==", "dev": true, "license": "MIT" }, "node_modules/array-union": { "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", "dev": true, "license": "MIT", "engines": { @@ -2191,12 +2384,19 @@ } }, "node_modules/balanced-match": { - "version": "1.0.2", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", "dev": true, - "license": "MIT" + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } }, "node_modules/baseline-browser-mapping": { - "version": "2.10.32", + "version": "2.10.37", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.37.tgz", + "integrity": "sha512-girxaJ7WZssDOFhzCGZTDKoTa1gk6A1TbflaYTpykLJ4UU9Fz9kx1aREM8JCuoVHbL8X8T/mJg7w2oYSq72Oig==", "dev": true, "license": "Apache-2.0", "bin": { @@ -2207,16 +2407,22 @@ } }, "node_modules/brace-expansion": { - "version": "1.1.14", + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.6.tgz", + "integrity": "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==", "dev": true, "license": "MIT", "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" } }, "node_modules/braces": { "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", "dev": true, "license": "MIT", "dependencies": { @@ -2228,6 +2434,8 @@ }, "node_modules/browserslist": { "version": "4.28.2", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.2.tgz", + "integrity": "sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==", "dev": true, "funding": [ { @@ -2260,6 +2468,8 @@ }, "node_modules/call-bind-apply-helpers": { "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", "dev": true, "license": "MIT", "dependencies": { @@ -2272,6 +2482,8 @@ }, "node_modules/call-bound": { "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", "dev": true, "license": "MIT", "dependencies": { @@ -2287,6 +2499,8 @@ }, "node_modules/callsites": { "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", "dev": true, "license": "MIT", "engines": { @@ -2295,6 +2509,8 @@ }, "node_modules/camelcase": { "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", "dev": true, "license": "MIT", "engines": { @@ -2302,7 +2518,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001793", + "version": "1.0.30001799", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001799.tgz", + "integrity": "sha512-hG1bReV+OUU+MOqK4t/ZWI0tZOyz3rqS9XuhOUz1cIcbwBKjOyJEJuw9ER5JuNyqxNk8u/JUVbGibBOL1yrjFw==", "dev": true, "funding": [ { @@ -2322,6 +2540,8 @@ }, "node_modules/chalk": { "version": "5.6.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", + "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", "dev": true, "license": "MIT", "engines": { @@ -2333,16 +2553,22 @@ }, "node_modules/change-case": { "version": "5.4.4", + "resolved": "https://registry.npmjs.org/change-case/-/change-case-5.4.4.tgz", + "integrity": "sha512-HRQyTk2/YPEkt9TnUPbOpr64Uw3KOicFWPVBb+xiHvd6eBx/qPr9xqfBFDT8P2vWsvvz4jbEkfDe71W3VyNu2w==", "dev": true, "license": "MIT" }, "node_modules/chardet": { "version": "2.1.1", + "resolved": "https://registry.npmjs.org/chardet/-/chardet-2.1.1.tgz", + "integrity": "sha512-PsezH1rqdV9VvyNhxxOW32/d75r01NY7TQCmOqomRo15ZSOKbpTFVsfjghxo6JloQUCGnH4k1LGu0R4yCLlWQQ==", "dev": true, "license": "MIT" }, "node_modules/cli-width": { "version": "4.1.0", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-4.1.0.tgz", + "integrity": "sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==", "dev": true, "license": "ISC", "engines": { @@ -2351,6 +2577,8 @@ }, "node_modules/cliui": { "version": "9.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-9.0.1.tgz", + "integrity": "sha512-k7ndgKhwoQveBL+/1tqGJYNz097I7WOvwbmmU2AR5+magtbjPWQTS1C5vzGkBC8Ym8UWRzfKUzUUqFLypY4Q+w==", "dev": true, "license": "ISC", "dependencies": { @@ -2364,6 +2592,8 @@ }, "node_modules/color-convert": { "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", "dev": true, "license": "MIT", "dependencies": { @@ -2372,11 +2602,15 @@ }, "node_modules/color-name": { "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", "dev": true, "license": "MIT" }, "node_modules/commander": { "version": "14.0.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.3.tgz", + "integrity": "sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw==", "dev": true, "license": "MIT", "engines": { @@ -2385,6 +2619,8 @@ }, "node_modules/compare-func": { "version": "2.0.0", + "resolved": "https://registry.npmjs.org/compare-func/-/compare-func-2.0.0.tgz", + "integrity": "sha512-zHig5N+tPWARooBnb0Zx1MFcdfpyJrfTJ3Y5L+IFvUm8rM74hHz66z0gw0x4tijh5CorKkKUCnW82R2vmpeCRA==", "dev": true, "license": "MIT", "dependencies": { @@ -2394,11 +2630,15 @@ }, "node_modules/concat-map": { "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", "dev": true, "license": "MIT" }, "node_modules/conventional-changelog-angular": { "version": "8.3.1", + "resolved": "https://registry.npmjs.org/conventional-changelog-angular/-/conventional-changelog-angular-8.3.1.tgz", + "integrity": "sha512-6gfI3otXK5Ph5DfCOI1dblr+kN3FAm5a97hYoQkqNZxOaYa5WKfXH+AnpsmS+iUH2mgVC2Cg2Qw9m5OKcmNrIg==", "dev": true, "license": "ISC", "dependencies": { @@ -2410,6 +2650,8 @@ }, "node_modules/conventional-changelog-conventionalcommits": { "version": "9.3.1", + "resolved": "https://registry.npmjs.org/conventional-changelog-conventionalcommits/-/conventional-changelog-conventionalcommits-9.3.1.tgz", + "integrity": "sha512-dTYtpIacRpcZgrvBYvBfArMmK2xvIpv2TaxM0/ZI5CBtNUzvF2x0t15HsbRABWprS6UPmvj+PzHVjSx4qAVKyw==", "dev": true, "license": "ISC", "dependencies": { @@ -2421,6 +2663,8 @@ }, "node_modules/conventional-commits-parser": { "version": "6.4.0", + "resolved": "https://registry.npmjs.org/conventional-commits-parser/-/conventional-commits-parser-6.4.0.tgz", + "integrity": "sha512-tvRg7FIBNlyPzjdG8wWRlPHQJJHI7DylhtRGeU9Lq+JuoPh5BKpPRX83ZdLrvXuOSu5Eo/e7SzOQhU4Hd2Miuw==", "dev": true, "license": "MIT", "dependencies": { @@ -2436,11 +2680,15 @@ }, "node_modules/convert-source-map": { "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", "dev": true, "license": "MIT" }, "node_modules/cosmiconfig": { - "version": "9.0.1", + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.2.tgz", + "integrity": "sha512-gtTZxTDau1wL7Y7zifc2dd8jHSK/k6BTx/2Xp/BpdlAdnlYWFVt7qhJqgwi7637yRwRQ3qL4ZidbB4I8tA5VOg==", "dev": true, "license": "MIT", "dependencies": { @@ -2466,6 +2714,8 @@ }, "node_modules/cosmiconfig-typescript-loader": { "version": "6.3.0", + "resolved": "https://registry.npmjs.org/cosmiconfig-typescript-loader/-/cosmiconfig-typescript-loader-6.3.0.tgz", + "integrity": "sha512-Akr82WH1Wfqatyiqpj8HDkO2o2KmJRu1FhKfSNJP3K4IdXwHfEyL7MOb62i1AGQVLtIQM+iCE9CGOtrfhR+mmA==", "dev": true, "license": "MIT", "dependencies": { @@ -2482,6 +2732,8 @@ }, "node_modules/cross-spawn": { "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", "dev": true, "license": "MIT", "dependencies": { @@ -2495,6 +2747,8 @@ }, "node_modules/debug": { "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", "dev": true, "license": "MIT", "dependencies": { @@ -2511,6 +2765,8 @@ }, "node_modules/decamelize": { "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", "dev": true, "license": "MIT", "engines": { @@ -2519,6 +2775,8 @@ }, "node_modules/des.js": { "version": "1.1.0", + "resolved": "https://registry.npmjs.org/des.js/-/des.js-1.1.0.tgz", + "integrity": "sha512-r17GxjhUCjSRy8aiJpr8/UadFIzMzJGexI3Nmz4ADi9LYSFx4gTBp80+NaX/YsXWWLhpZ7v/v/ubEc/bCNfKwg==", "dev": true, "license": "MIT", "dependencies": { @@ -2528,11 +2786,15 @@ }, "node_modules/diff-match-patch": { "version": "1.0.5", + "resolved": "https://registry.npmjs.org/diff-match-patch/-/diff-match-patch-1.0.5.tgz", + "integrity": "sha512-IayShXAgj/QMXgB0IWmKx+rOPuGMhqm5w6jvFxmVenXKIzRqTAAsbBPT3kWQeGANj3jGgvcvv4yK6SxqYmikgw==", "dev": true, "license": "Apache-2.0" }, "node_modules/dir-glob": { "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", "dev": true, "license": "MIT", "dependencies": { @@ -2544,11 +2806,15 @@ }, "node_modules/discontinuous-range": { "version": "1.0.0", + "resolved": "https://registry.npmjs.org/discontinuous-range/-/discontinuous-range-1.0.0.tgz", + "integrity": "sha512-c68LpLbO+7kP/b1Hr1qs8/BJ09F5khZGTxqxZuhzxpmwJKOgRFHJWIb9/KmqnqHhLdO55aOxFH/EGBvUQbL/RQ==", "dev": true, "license": "MIT" }, "node_modules/dot-prop": { "version": "5.3.0", + "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.3.0.tgz", + "integrity": "sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==", "dev": true, "license": "MIT", "dependencies": { @@ -2560,6 +2826,8 @@ }, "node_modules/dunder-proto": { "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", "dev": true, "license": "MIT", "dependencies": { @@ -2572,17 +2840,23 @@ } }, "node_modules/electron-to-chromium": { - "version": "1.5.364", + "version": "1.5.372", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.372.tgz", + "integrity": "sha512-M3yhbAlilnwqC8D21t28UCDGHyitShTmmLRU/H+b74P6Ski16Nb9HONYEaVpMj/pwC7BEo5B95FpjODLCWbtfA==", "dev": true, "license": "ISC" }, "node_modules/emoji-regex": { "version": "10.6.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", + "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", "dev": true, "license": "MIT" }, "node_modules/env-paths": { "version": "2.2.1", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", + "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", "dev": true, "license": "MIT", "engines": { @@ -2591,6 +2865,8 @@ }, "node_modules/error-ex": { "version": "1.3.4", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz", + "integrity": "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==", "dev": true, "license": "MIT", "dependencies": { @@ -2599,6 +2875,8 @@ }, "node_modules/es-define-property": { "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", "dev": true, "license": "MIT", "engines": { @@ -2607,6 +2885,8 @@ }, "node_modules/es-errors": { "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", "dev": true, "license": "MIT", "engines": { @@ -2615,6 +2895,8 @@ }, "node_modules/es-object-atoms": { "version": "1.1.2", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz", + "integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==", "dev": true, "license": "MIT", "dependencies": { @@ -2625,7 +2907,9 @@ } }, "node_modules/es-toolkit": { - "version": "1.46.1", + "version": "1.47.1", + "resolved": "https://registry.npmjs.org/es-toolkit/-/es-toolkit-1.47.1.tgz", + "integrity": "sha512-5RAqEwf4P4E17p+W75KLOWw/nOvKZzSQpxM32IpI2KZLaVonjTrZ0Ai5ghMaVI9eKC2p8eoQgcBdkEDgzFk6+Q==", "dev": true, "license": "MIT", "workspaces": [ @@ -2634,7 +2918,9 @@ ] }, "node_modules/esbuild": { - "version": "0.28.0", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.1.tgz", + "integrity": "sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw==", "dev": true, "hasInstallScript": true, "license": "MIT", @@ -2645,36 +2931,38 @@ "node": ">=18" }, "optionalDependencies": { - "@esbuild/aix-ppc64": "0.28.0", - "@esbuild/android-arm": "0.28.0", - "@esbuild/android-arm64": "0.28.0", - "@esbuild/android-x64": "0.28.0", - "@esbuild/darwin-arm64": "0.28.0", - "@esbuild/darwin-x64": "0.28.0", - "@esbuild/freebsd-arm64": "0.28.0", - "@esbuild/freebsd-x64": "0.28.0", - "@esbuild/linux-arm": "0.28.0", - "@esbuild/linux-arm64": "0.28.0", - "@esbuild/linux-ia32": "0.28.0", - "@esbuild/linux-loong64": "0.28.0", - "@esbuild/linux-mips64el": "0.28.0", - "@esbuild/linux-ppc64": "0.28.0", - "@esbuild/linux-riscv64": "0.28.0", - "@esbuild/linux-s390x": "0.28.0", - "@esbuild/linux-x64": "0.28.0", - "@esbuild/netbsd-arm64": "0.28.0", - "@esbuild/netbsd-x64": "0.28.0", - "@esbuild/openbsd-arm64": "0.28.0", - "@esbuild/openbsd-x64": "0.28.0", - "@esbuild/openharmony-arm64": "0.28.0", - "@esbuild/sunos-x64": "0.28.0", - "@esbuild/win32-arm64": "0.28.0", - "@esbuild/win32-ia32": "0.28.0", - "@esbuild/win32-x64": "0.28.0" + "@esbuild/aix-ppc64": "0.28.1", + "@esbuild/android-arm": "0.28.1", + "@esbuild/android-arm64": "0.28.1", + "@esbuild/android-x64": "0.28.1", + "@esbuild/darwin-arm64": "0.28.1", + "@esbuild/darwin-x64": "0.28.1", + "@esbuild/freebsd-arm64": "0.28.1", + "@esbuild/freebsd-x64": "0.28.1", + "@esbuild/linux-arm": "0.28.1", + "@esbuild/linux-arm64": "0.28.1", + "@esbuild/linux-ia32": "0.28.1", + "@esbuild/linux-loong64": "0.28.1", + "@esbuild/linux-mips64el": "0.28.1", + "@esbuild/linux-ppc64": "0.28.1", + "@esbuild/linux-riscv64": "0.28.1", + "@esbuild/linux-s390x": "0.28.1", + "@esbuild/linux-x64": "0.28.1", + "@esbuild/netbsd-arm64": "0.28.1", + "@esbuild/netbsd-x64": "0.28.1", + "@esbuild/openbsd-arm64": "0.28.1", + "@esbuild/openbsd-x64": "0.28.1", + "@esbuild/openharmony-arm64": "0.28.1", + "@esbuild/sunos-x64": "0.28.1", + "@esbuild/win32-arm64": "0.28.1", + "@esbuild/win32-ia32": "0.28.1", + "@esbuild/win32-x64": "0.28.1" } }, "node_modules/escalade": { "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", "dev": true, "license": "MIT", "engines": { @@ -2683,6 +2971,8 @@ }, "node_modules/esprima": { "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", "dev": true, "license": "BSD-2-Clause", "bin": { @@ -2695,6 +2985,8 @@ }, "node_modules/execa": { "version": "9.6.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-9.6.1.tgz", + "integrity": "sha512-9Be3ZoN4LmYR90tUoVu2te2BsbzHfhJyfEiAVfz7N5/zv+jduIfLrV2xdQXOHbaD6KgpGdO9PRPM1Y4Q9QkPkA==", "dev": true, "license": "MIT", "dependencies": { @@ -2743,10 +3035,14 @@ }, "node_modules/fast-deep-equal": { "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", "license": "MIT" }, "node_modules/fast-glob": { "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", "dev": true, "license": "MIT", "dependencies": { @@ -2762,11 +3058,15 @@ }, "node_modules/fast-string-truncated-width": { "version": "3.0.3", + "resolved": "https://registry.npmjs.org/fast-string-truncated-width/-/fast-string-truncated-width-3.0.3.tgz", + "integrity": "sha512-0jjjIEL6+0jag3l2XWWizO64/aZVtpiGE3t0Zgqxv0DPuxiMjvB3M24fCyhZUO4KomJQPj3LTSUnDP3GpdwC0g==", "dev": true, "license": "MIT" }, "node_modules/fast-string-width": { "version": "3.0.2", + "resolved": "https://registry.npmjs.org/fast-string-width/-/fast-string-width-3.0.2.tgz", + "integrity": "sha512-gX8LrtNEI5hq8DVUfRQMbr5lpaS4nMIWV+7XEbXk2b8kiQIizgnlr12B4dA3ZEx3308ze0O4Q1R+cHts8kyUJg==", "dev": true, "license": "MIT", "dependencies": { @@ -2775,6 +3075,8 @@ }, "node_modules/fast-uri": { "version": "3.1.2", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.2.tgz", + "integrity": "sha512-rVjf7ArG3LTk+FS6Yw81V1DLuZl1bRbNrev6Tmd/9RaroeeRRJhAt7jg/6YFxbvAQXUCavSoZhPPj6oOx+5KjQ==", "funding": [ { "type": "github", @@ -2789,6 +3091,8 @@ }, "node_modules/fast-wrap-ansi": { "version": "0.2.2", + "resolved": "https://registry.npmjs.org/fast-wrap-ansi/-/fast-wrap-ansi-0.2.2.tgz", + "integrity": "sha512-7F2Fl+TjRSenLqlU3UjSH0iyqopqoZIu7eZVpEirP2g1GtWa2G/ecEmBdgz31+Mxr+ELclgg6sokpSFIQiZ02Q==", "dev": true, "license": "MIT", "dependencies": { @@ -2797,6 +3101,8 @@ }, "node_modules/fastq": { "version": "1.20.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz", + "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==", "dev": true, "license": "ISC", "dependencies": { @@ -2805,6 +3111,8 @@ }, "node_modules/figures": { "version": "6.1.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-6.1.0.tgz", + "integrity": "sha512-d+l3qxjSesT4V7v2fh+QnmFnUWv9lSpjarhShNTgBOfA0ttejbQUAlHLitbjkoRiDulW0OPoQPYIGhIC8ohejg==", "dev": true, "license": "MIT", "dependencies": { @@ -2819,6 +3127,8 @@ }, "node_modules/fill-range": { "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", "dev": true, "license": "MIT", "dependencies": { @@ -2830,6 +3140,8 @@ }, "node_modules/find-up": { "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", "dev": true, "license": "MIT", "dependencies": { @@ -2841,6 +3153,8 @@ }, "node_modules/fluent-transpiler": { "version": "0.4.1", + "resolved": "https://registry.npmjs.org/fluent-transpiler/-/fluent-transpiler-0.4.1.tgz", + "integrity": "sha512-9CNRxPbnMDTt1hdtD2YPdbyH/p5o3xRgkcsZm0dgER+Kg2k3mdvDb4BJ6lVeFx+cGKp6GWPxcZ2n8R4b45JqJw==", "dev": true, "license": "MIT", "workspaces": [ @@ -2864,6 +3178,8 @@ }, "node_modules/fs-extra": { "version": "8.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", + "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", "dev": true, "license": "MIT", "dependencies": { @@ -2877,11 +3193,15 @@ }, "node_modules/fs.realpath": { "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", "dev": true, "license": "ISC" }, "node_modules/function-bind": { "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", "dev": true, "license": "MIT", "funding": { @@ -2890,6 +3210,8 @@ }, "node_modules/gensync": { "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", "dev": true, "license": "MIT", "engines": { @@ -2898,6 +3220,8 @@ }, "node_modules/get-caller-file": { "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", "dev": true, "license": "ISC", "engines": { @@ -2906,6 +3230,8 @@ }, "node_modules/get-east-asian-width": { "version": "1.6.0", + "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.6.0.tgz", + "integrity": "sha512-QRbvDIbx6YklUe6RxeTeleMR0yv3cYH6PsPZHcnVn7xv7zO1BHN8r0XETu8n6Ye3Q+ahtSarc3WgtNWmehIBfA==", "dev": true, "license": "MIT", "engines": { @@ -2917,6 +3243,8 @@ }, "node_modules/get-intrinsic": { "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", "dev": true, "license": "MIT", "dependencies": { @@ -2940,6 +3268,8 @@ }, "node_modules/get-proto": { "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", "dev": true, "license": "MIT", "dependencies": { @@ -2952,6 +3282,8 @@ }, "node_modules/get-stream": { "version": "9.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-9.0.1.tgz", + "integrity": "sha512-kVCxPF3vQM/N0B1PmoqVUqgHP+EeVjmZSQn+1oCRPxd2P21P2F19lIgbR3HBosbB1PUhOAoctJnfEn2GbN2eZA==", "dev": true, "license": "MIT", "dependencies": { @@ -2967,6 +3299,8 @@ }, "node_modules/git-raw-commits": { "version": "5.0.1", + "resolved": "https://registry.npmjs.org/git-raw-commits/-/git-raw-commits-5.0.1.tgz", + "integrity": "sha512-Y+csSm2GD/PCSh6Isd/WiMjNAydu0VBiG9J7EdQsNA5P9uXvLayqjmTsNlK5Gs9IhblFZqOU0yid5Il5JPoLiQ==", "dev": true, "license": "MIT", "dependencies": { @@ -2982,6 +3316,8 @@ }, "node_modules/gitignore-to-glob": { "version": "0.3.0", + "resolved": "https://registry.npmjs.org/gitignore-to-glob/-/gitignore-to-glob-0.3.0.tgz", + "integrity": "sha512-mk74BdnK7lIwDHnotHddx1wsjMOFIThpLY3cPNniJ/2fA/tlLzHnFxIdR+4sLOu5KGgQJdij4kjJ2RoUNnCNMA==", "dev": true, "license": "MIT", "engines": { @@ -2990,6 +3326,9 @@ }, "node_modules/glob": { "version": "5.0.15", + "resolved": "https://registry.npmjs.org/glob/-/glob-5.0.15.tgz", + "integrity": "sha512-c9IPMazfRITpmAAKi22dK1VKxGDX9ehhqfABDriL/lzO92xcUKEJPQHrVA/2YHSNFB4iFlykVmWvwo48nr3OxA==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", "dev": true, "license": "ISC", "dependencies": { @@ -3005,6 +3344,8 @@ }, "node_modules/glob-parent": { "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", "dev": true, "license": "ISC", "dependencies": { @@ -3014,8 +3355,41 @@ "node": ">= 6" } }, + "node_modules/glob/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/glob/node_modules/brace-expansion": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.15.tgz", + "integrity": "sha512-EwOCDEex4quD37XhqM3omwtMoJjr//isUZz1JopUNWms+4Z2ViyM/k1YIRePpoVNnQhENnxtFjLaxNHrT7xIUg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/glob/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/global-directory": { "version": "5.0.0", + "resolved": "https://registry.npmjs.org/global-directory/-/global-directory-5.0.0.tgz", + "integrity": "sha512-1pgFdhK3J2LeM+dVf2Pd424yHx2ou338lC0ErNP2hPx4j8eW1Sp0XqSjNxtk6Tc4Kr5wlWtSvz8cn2yb7/SG/w==", "dev": true, "license": "MIT", "dependencies": { @@ -3030,6 +3404,8 @@ }, "node_modules/globby": { "version": "10.0.2", + "resolved": "https://registry.npmjs.org/globby/-/globby-10.0.2.tgz", + "integrity": "sha512-7dUi7RvCoT/xast/o/dLN53oqND4yk0nsHkhRgn9w65C4PofCLOoJ39iSOg+qVDdWQPIEj+eszMHQ+aLVwwQSg==", "dev": true, "license": "MIT", "dependencies": { @@ -3046,8 +3422,29 @@ "node": ">=8" } }, + "node_modules/globby/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/globby/node_modules/brace-expansion": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.15.tgz", + "integrity": "sha512-EwOCDEex4quD37XhqM3omwtMoJjr//isUZz1JopUNWms+4Z2ViyM/k1YIRePpoVNnQhENnxtFjLaxNHrT7xIUg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, "node_modules/globby/node_modules/glob": { "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", "dev": true, "license": "ISC", "dependencies": { @@ -3065,8 +3462,23 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/globby/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/gopd": { "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", "dev": true, "license": "MIT", "engines": { @@ -3078,11 +3490,15 @@ }, "node_modules/graceful-fs": { "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", "dev": true, "license": "ISC" }, "node_modules/has-symbols": { "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", "dev": true, "license": "MIT", "engines": { @@ -3094,6 +3510,8 @@ }, "node_modules/hasown": { "version": "2.0.4", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz", + "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==", "dev": true, "license": "MIT", "dependencies": { @@ -3105,6 +3523,8 @@ }, "node_modules/human-signals": { "version": "8.0.1", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-8.0.1.tgz", + "integrity": "sha512-eKCa6bwnJhvxj14kZk5NCPc6Hb6BdsU9DZcOnmQKSnO1VKrfV0zCvtttPZUsBvjmNDn8rpcJfpwSYnHBjc95MQ==", "dev": true, "license": "Apache-2.0", "engines": { @@ -3113,6 +3533,8 @@ }, "node_modules/husky": { "version": "9.1.7", + "resolved": "https://registry.npmjs.org/husky/-/husky-9.1.7.tgz", + "integrity": "sha512-5gs5ytaNjBrh5Ow3zrvdUUY+0VxIuWVL4i9irt6friV+BqdCfmV11CQTWMiBYWHbXhco+J1kHfTOUkePhCDvMA==", "dev": true, "license": "MIT", "bin": { @@ -3127,6 +3549,8 @@ }, "node_modules/iconv-lite": { "version": "0.7.2", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz", + "integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==", "dev": true, "license": "MIT", "dependencies": { @@ -3142,6 +3566,8 @@ }, "node_modules/ignore": { "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", "dev": true, "license": "MIT", "engines": { @@ -3150,6 +3576,8 @@ }, "node_modules/import-fresh": { "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", "dev": true, "license": "MIT", "dependencies": { @@ -3165,6 +3593,8 @@ }, "node_modules/import-fresh/node_modules/resolve-from": { "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", "dev": true, "license": "MIT", "engines": { @@ -3173,6 +3603,9 @@ }, "node_modules/inflight": { "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", "dev": true, "license": "ISC", "dependencies": { @@ -3182,11 +3615,15 @@ }, "node_modules/inherits": { "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", "dev": true, "license": "ISC" }, "node_modules/ini": { "version": "6.0.0", + "resolved": "https://registry.npmjs.org/ini/-/ini-6.0.0.tgz", + "integrity": "sha512-IBTdIkzZNOpqm7q3dRqJvMaldXjDHWkEDfrwGEQTs5eaQMWV+djAhR+wahyNNMAa+qpbDUhBMVt4ZKNwpPm7xQ==", "dev": true, "license": "ISC", "engines": { @@ -3195,11 +3632,15 @@ }, "node_modules/is-arrayish": { "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", "dev": true, "license": "MIT" }, "node_modules/is-extglob": { "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", "dev": true, "license": "MIT", "engines": { @@ -3208,6 +3649,8 @@ }, "node_modules/is-fullwidth-code-point": { "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w==", "dev": true, "license": "MIT", "engines": { @@ -3216,6 +3659,8 @@ }, "node_modules/is-glob": { "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", "dev": true, "license": "MIT", "dependencies": { @@ -3227,6 +3672,8 @@ }, "node_modules/is-number": { "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", "dev": true, "license": "MIT", "engines": { @@ -3235,6 +3682,8 @@ }, "node_modules/is-obj": { "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", + "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==", "dev": true, "license": "MIT", "engines": { @@ -3243,6 +3692,8 @@ }, "node_modules/is-plain-obj": { "version": "4.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", + "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==", "dev": true, "license": "MIT", "engines": { @@ -3254,6 +3705,8 @@ }, "node_modules/is-stream": { "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-4.0.1.tgz", + "integrity": "sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A==", "dev": true, "license": "MIT", "engines": { @@ -3265,6 +3718,8 @@ }, "node_modules/is-unicode-supported": { "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-2.1.0.tgz", + "integrity": "sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==", "dev": true, "license": "MIT", "engines": { @@ -3276,11 +3731,15 @@ }, "node_modules/isexe": { "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", "dev": true, "license": "ISC" }, "node_modules/jiti": { "version": "2.6.1", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz", + "integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==", "dev": true, "license": "MIT", "bin": { @@ -3289,17 +3748,33 @@ }, "node_modules/js-md4": { "version": "0.3.2", + "resolved": "https://registry.npmjs.org/js-md4/-/js-md4-0.3.2.tgz", + "integrity": "sha512-/GDnfQYsltsjRswQhN9fhv3EMw2sCpUdrdxyWDOUK7eyD++r3gRhzgiQgc/x4MAv2i1iuQ4lxO5mvqM3vj4bwA==", "dev": true, "license": "MIT" }, "node_modules/js-tokens": { "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", "dev": true, "license": "MIT" }, "node_modules/js-yaml": { - "version": "4.1.1", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.2.0.tgz", + "integrity": "sha512-ePWsvanv0DWuDRsW8dnt+R4jQ31SCRCQ7hhNcPXZPsoBZiemuZNYGf7adZdqX2D86j6rvKp3RpCxVTSb8WQlOw==", "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/puzrin" + }, + { + "type": "github", + "url": "https://github.com/sponsors/nodeca" + } + ], "license": "MIT", "dependencies": { "argparse": "^2.0.1" @@ -3310,6 +3785,8 @@ }, "node_modules/jsesc": { "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", "license": "MIT", "bin": { "jsesc": "bin/jsesc" @@ -3320,16 +3797,22 @@ }, "node_modules/json-parse-even-better-errors": { "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", "dev": true, "license": "MIT" }, "node_modules/json-rpc-2.0": { "version": "1.7.1", + "resolved": "https://registry.npmjs.org/json-rpc-2.0/-/json-rpc-2.0-1.7.1.tgz", + "integrity": "sha512-JqZjhjAanbpkXIzFE7u8mE/iFblawwlXtONaCvRqI+pyABVz7B4M1EUNpyVW+dZjqgQ2L5HFmZCmOCgUKm00hg==", "dev": true, "license": "MIT" }, "node_modules/json-schema-test-esm": { "version": "3.0.0", + "resolved": "https://registry.npmjs.org/json-schema-test-esm/-/json-schema-test-esm-3.0.0.tgz", + "integrity": "sha512-8c/vIVzLDsduZWsZJ+VOST7HEMnhFbAyvO+PHSPp2TzlHR7z0rCMKaIOHQzZHgJf+Oqrue+TtqiTAFvZ0O8KYg==", "dev": true, "license": "MIT", "dependencies": { @@ -3338,10 +3821,14 @@ }, "node_modules/json-schema-traverse": { "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", "license": "MIT" }, "node_modules/json5": { "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", "dev": true, "license": "MIT", "bin": { @@ -3353,6 +3840,8 @@ }, "node_modules/jsonfile": { "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", "dev": true, "license": "MIT", "optionalDependencies": { @@ -3361,6 +3850,8 @@ }, "node_modules/license-check-and-add": { "version": "4.0.5", + "resolved": "https://registry.npmjs.org/license-check-and-add/-/license-check-and-add-4.0.5.tgz", + "integrity": "sha512-FySnMi3Kf/vO5jka8tcbVF1FhDFb8PWsQ8pg5Y7U/zkQgta+fIrJGcGHO58WFjfKlgvhneG1uQ00Fpxzhau3QA==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -3376,6 +3867,8 @@ }, "node_modules/license-check-and-add/node_modules/ansi-regex": { "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz", + "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==", "dev": true, "license": "MIT", "engines": { @@ -3384,6 +3877,8 @@ }, "node_modules/license-check-and-add/node_modules/ansi-styles": { "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", "dev": true, "license": "MIT", "dependencies": { @@ -3395,6 +3890,8 @@ }, "node_modules/license-check-and-add/node_modules/cliui": { "version": "5.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz", + "integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==", "dev": true, "license": "ISC", "dependencies": { @@ -3405,11 +3902,15 @@ }, "node_modules/license-check-and-add/node_modules/emoji-regex": { "version": "7.0.3", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", + "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", "dev": true, "license": "MIT" }, "node_modules/license-check-and-add/node_modules/string-width": { "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", "dev": true, "license": "MIT", "dependencies": { @@ -3423,6 +3924,8 @@ }, "node_modules/license-check-and-add/node_modules/strip-ansi": { "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", "dev": true, "license": "MIT", "dependencies": { @@ -3434,6 +3937,8 @@ }, "node_modules/license-check-and-add/node_modules/wrap-ansi": { "version": "5.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz", + "integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==", "dev": true, "license": "MIT", "dependencies": { @@ -3447,11 +3952,15 @@ }, "node_modules/license-check-and-add/node_modules/y18n": { "version": "4.0.3", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", + "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==", "dev": true, "license": "ISC" }, "node_modules/license-check-and-add/node_modules/yargs": { "version": "13.3.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.3.2.tgz", + "integrity": "sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw==", "dev": true, "license": "MIT", "dependencies": { @@ -3469,6 +3978,8 @@ }, "node_modules/license-check-and-add/node_modules/yargs-parser": { "version": "13.1.2", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.2.tgz", + "integrity": "sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg==", "dev": true, "license": "ISC", "dependencies": { @@ -3478,11 +3989,15 @@ }, "node_modules/lines-and-columns": { "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", "dev": true, "license": "MIT" }, "node_modules/locate-path": { "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", "dev": true, "license": "MIT", "dependencies": { @@ -3495,6 +4010,8 @@ }, "node_modules/lockfile-lint": { "version": "5.0.0", + "resolved": "https://registry.npmjs.org/lockfile-lint/-/lockfile-lint-5.0.0.tgz", + "integrity": "sha512-QcVIVITLZAhWYHU2wbNSOMgwc6EN4Y2sy6mjgS5aikYyRzgDIfotXUsCrm38En+3fZpc58Yu7DF9dNeT/goi1A==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -3513,6 +4030,8 @@ }, "node_modules/lockfile-lint-api": { "version": "5.9.2", + "resolved": "https://registry.npmjs.org/lockfile-lint-api/-/lockfile-lint-api-5.9.2.tgz", + "integrity": "sha512-3QhxWxl3jT9GcMxuCnTsU8Tz5U6U1lKBlKBu2zOYOz/x3ONUoojEtky3uzoaaDgExcLqIX0Aqv2I7TZXE383CQ==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -3526,6 +4045,8 @@ }, "node_modules/lockfile-lint/node_modules/ansi-regex": { "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", "dev": true, "license": "MIT", "engines": { @@ -3534,6 +4055,8 @@ }, "node_modules/lockfile-lint/node_modules/ansi-styles": { "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, "license": "MIT", "dependencies": { @@ -3548,6 +4071,8 @@ }, "node_modules/lockfile-lint/node_modules/cliui": { "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", "dev": true, "license": "ISC", "dependencies": { @@ -3561,6 +4086,8 @@ }, "node_modules/lockfile-lint/node_modules/color-convert": { "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, "license": "MIT", "dependencies": { @@ -3572,16 +4099,22 @@ }, "node_modules/lockfile-lint/node_modules/color-name": { "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true, "license": "MIT" }, "node_modules/lockfile-lint/node_modules/emoji-regex": { "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", "dev": true, "license": "MIT" }, "node_modules/lockfile-lint/node_modules/is-fullwidth-code-point": { "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", "dev": true, "license": "MIT", "engines": { @@ -3590,6 +4123,8 @@ }, "node_modules/lockfile-lint/node_modules/string-width": { "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "dev": true, "license": "MIT", "dependencies": { @@ -3603,6 +4138,8 @@ }, "node_modules/lockfile-lint/node_modules/strip-ansi": { "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "dev": true, "license": "MIT", "dependencies": { @@ -3614,6 +4151,8 @@ }, "node_modules/lockfile-lint/node_modules/wrap-ansi": { "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", "dev": true, "license": "MIT", "dependencies": { @@ -3630,6 +4169,8 @@ }, "node_modules/lockfile-lint/node_modules/yargs": { "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", "dev": true, "license": "MIT", "dependencies": { @@ -3647,6 +4188,8 @@ }, "node_modules/lockfile-lint/node_modules/yargs-parser": { "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", "dev": true, "license": "ISC", "engines": { @@ -3655,11 +4198,15 @@ }, "node_modules/lodash.groupby": { "version": "4.6.0", + "resolved": "https://registry.npmjs.org/lodash.groupby/-/lodash.groupby-4.6.0.tgz", + "integrity": "sha512-5dcWxm23+VAoz+awKmBaiBvzox8+RqMgFhi7UvX9DHZr2HdxHXM/Wrf8cfKpsW37RNrvtPn6hSwNqurSILbmJw==", "dev": true, "license": "MIT" }, "node_modules/lru-cache": { "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", "dev": true, "license": "ISC", "dependencies": { @@ -3668,6 +4215,8 @@ }, "node_modules/math-intrinsics": { "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", "dev": true, "license": "MIT", "engines": { @@ -3676,6 +4225,8 @@ }, "node_modules/meow": { "version": "13.2.0", + "resolved": "https://registry.npmjs.org/meow/-/meow-13.2.0.tgz", + "integrity": "sha512-pxQJQzB6djGPXh08dacEloMFopsOqGVRKFPYvPOt9XDZ1HasbgDZA74CJGreSU4G3Ak7EFJGoiH2auq+yXISgA==", "dev": true, "license": "MIT", "engines": { @@ -3687,6 +4238,8 @@ }, "node_modules/merge2": { "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", "dev": true, "license": "MIT", "engines": { @@ -3695,6 +4248,8 @@ }, "node_modules/micromatch": { "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", "dev": true, "license": "MIT", "dependencies": { @@ -3707,32 +4262,45 @@ }, "node_modules/minimalistic-assert": { "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", + "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==", "dev": true, "license": "ISC" }, "node_modules/minimatch": { - "version": "3.1.5", + "version": "10.2.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", + "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", "dev": true, - "license": "ISC", + "license": "BlueOak-1.0.0", "dependencies": { - "brace-expansion": "^1.1.7" + "brace-expansion": "^5.0.5" }, "engines": { - "node": "*" + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, "node_modules/moo": { "version": "0.5.3", + "resolved": "https://registry.npmjs.org/moo/-/moo-0.5.3.tgz", + "integrity": "sha512-m2fmM2dDm7GZQsY7KK2cme8agi+AAljILjQnof7p1ZMDe6dQ4bdnSMx0cPppudoeNv5hEFQirN6u+O4fDE0IWA==", "dev": true, "license": "BSD-3-Clause" }, "node_modules/ms": { "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "dev": true, "license": "MIT" }, "node_modules/mutation-server-protocol": { "version": "0.4.1", + "resolved": "https://registry.npmjs.org/mutation-server-protocol/-/mutation-server-protocol-0.4.1.tgz", + "integrity": "sha512-SBGK0j8hLDne7bktgThKI8kGvGTx3rY3LAeQTmOKZ5bVnL/7TorLMvcVF7dIPJCu5RNUWhkkuF53kurygYVt3g==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -3744,11 +4312,15 @@ }, "node_modules/mutation-testing-elements": { "version": "3.7.3", + "resolved": "https://registry.npmjs.org/mutation-testing-elements/-/mutation-testing-elements-3.7.3.tgz", + "integrity": "sha512-SMeIPxngJpfjfNYctFpYQQtlBlZaVO0aoB3FKdwrI8Ee/2bkyUuCZzAOCLv1U9fnmfA37dPFq0Owduoxs2XgGQ==", "dev": true, "license": "Apache-2.0" }, "node_modules/mutation-testing-metrics": { "version": "3.7.3", + "resolved": "https://registry.npmjs.org/mutation-testing-metrics/-/mutation-testing-metrics-3.7.3.tgz", + "integrity": "sha512-B8QrP0ZomErzTPNlhrzKWPNBln+3afwBZPHv0Q7N8wZZTYxMptzb/Gdm3ExXVmioVYrtZAtsDs7W/T/b2AixOQ==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -3757,19 +4329,25 @@ }, "node_modules/mutation-testing-report-schema": { "version": "3.7.3", + "resolved": "https://registry.npmjs.org/mutation-testing-report-schema/-/mutation-testing-report-schema-3.7.3.tgz", + "integrity": "sha512-BHm3MYq+ckO+t5CtlG8zpqxc75rdJCkxVlE+fGuGJM3F7tNCQ/OW2N+TQVHN3BHsYa84+BFc6g3AwDYkUsw2MA==", "dev": true, "license": "Apache-2.0" }, "node_modules/mute-stream": { - "version": "4.0.0", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-3.0.0.tgz", + "integrity": "sha512-dkEJPVvun4FryqBmZ5KhDo0K9iDXAwn08tMLDinNdRBNPcYEDiWYysLcc6k3mjTMlbP9KyylvRpd4wFtwrT9rw==", "dev": true, "license": "ISC", "engines": { - "node": "^22.22.2 || ^24.15.0 || >=26.0.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/nearley": { "version": "2.20.1", + "resolved": "https://registry.npmjs.org/nearley/-/nearley-2.20.1.tgz", + "integrity": "sha512-+Mc8UaAebFzgV+KpI5n7DasuuQCHA89dmwm7JXw3TV43ukfNQ9DnBH3Mdb2g/I4Fdxc26pwimBWvjIw0UAILSQ==", "dev": true, "license": "MIT", "dependencies": { @@ -3791,11 +4369,15 @@ }, "node_modules/nearley/node_modules/commander": { "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", "dev": true, "license": "MIT" }, "node_modules/node-releases": { - "version": "2.0.46", + "version": "2.0.47", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.47.tgz", + "integrity": "sha512-Uzmd6LXpouKo8EUK68IjH4+E01w/hXyV3R3g/geCJo+rXLNfh1xucB+LOzYEOQPSiUK3h/xZf0cQGcSsmyL2Og==", "dev": true, "license": "MIT", "engines": { @@ -3804,6 +4386,8 @@ }, "node_modules/npm-run-path": { "version": "6.0.0", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-6.0.0.tgz", + "integrity": "sha512-9qny7Z9DsQU8Ou39ERsPU4OZQlSTP47ShQzuKZ6PRXpYLtIFgl/DEBYEXKlvcEa+9tHVcK8CF81Y2V72qaZhWA==", "dev": true, "license": "MIT", "dependencies": { @@ -3819,6 +4403,8 @@ }, "node_modules/npm-run-path/node_modules/path-key": { "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", + "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", "dev": true, "license": "MIT", "engines": { @@ -3830,6 +4416,8 @@ }, "node_modules/object-hash": { "version": "3.0.0", + "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", + "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", "dev": true, "license": "MIT", "engines": { @@ -3838,6 +4426,8 @@ }, "node_modules/object-inspect": { "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", "dev": true, "license": "MIT", "engines": { @@ -3849,6 +4439,8 @@ }, "node_modules/once": { "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", "dev": true, "license": "ISC", "dependencies": { @@ -3857,6 +4449,8 @@ }, "node_modules/p-limit": { "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", "dev": true, "license": "MIT", "dependencies": { @@ -3871,6 +4465,8 @@ }, "node_modules/p-locate": { "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", "dev": true, "license": "MIT", "dependencies": { @@ -3882,6 +4478,8 @@ }, "node_modules/p-try": { "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", "dev": true, "license": "MIT", "engines": { @@ -3890,6 +4488,8 @@ }, "node_modules/parent-module": { "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", "dev": true, "license": "MIT", "dependencies": { @@ -3901,6 +4501,8 @@ }, "node_modules/parse-json": { "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", "dev": true, "license": "MIT", "dependencies": { @@ -3918,6 +4520,8 @@ }, "node_modules/parse-ms": { "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-ms/-/parse-ms-4.0.0.tgz", + "integrity": "sha512-TXfryirbmq34y8QBwgqCVLi+8oA3oWx2eAnSn62ITyEhEYaWRlVZ2DvMM9eZbMs/RfxPu/PK/aBLyGj4IrqMHw==", "dev": true, "license": "MIT", "engines": { @@ -3929,6 +4533,8 @@ }, "node_modules/path-exists": { "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==", "dev": true, "license": "MIT", "engines": { @@ -3937,6 +4543,8 @@ }, "node_modules/path-is-absolute": { "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", "dev": true, "license": "MIT", "engines": { @@ -3945,6 +4553,8 @@ }, "node_modules/path-key": { "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", "dev": true, "license": "MIT", "engines": { @@ -3953,6 +4563,8 @@ }, "node_modules/path-type": { "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", "dev": true, "license": "MIT", "engines": { @@ -3961,11 +4573,15 @@ }, "node_modules/picocolors": { "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", "dev": true, "license": "ISC" }, "node_modules/picomatch": { "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", "dev": true, "license": "MIT", "engines": { @@ -3977,6 +4593,8 @@ }, "node_modules/pretty-ms": { "version": "9.3.0", + "resolved": "https://registry.npmjs.org/pretty-ms/-/pretty-ms-9.3.0.tgz", + "integrity": "sha512-gjVS5hOP+M3wMm5nmNOucbIrqudzs9v/57bWRHQWLYklXqoXKrVfYW2W9+glfGsqtPgpiz5WwyEEB+ksXIx3gQ==", "dev": true, "license": "MIT", "dependencies": { @@ -3991,6 +4609,8 @@ }, "node_modules/progress": { "version": "2.0.3", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", "dev": true, "license": "MIT", "engines": { @@ -3999,6 +4619,8 @@ }, "node_modules/pure-rand": { "version": "8.4.0", + "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-8.4.0.tgz", + "integrity": "sha512-IoM8YF/jY0hiugFo/wOWqfmarlE6J0wc6fDK1PhftMk7MGhVZl88sZimmqBBFomLOCSmcCCpsfj7wXASCpvK9A==", "dev": true, "funding": [ { @@ -4014,6 +4636,8 @@ }, "node_modules/qs": { "version": "6.15.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.2.tgz", + "integrity": "sha512-Rzq0KEyX/w/tEybncDgdkZrJgVUsUMk3xjh3t5bv3S1HTAtg+uOYt72+ZfwiQwKdysThkTBdL/rTi6HDmX9Ddw==", "dev": true, "license": "BSD-3-Clause", "dependencies": { @@ -4028,6 +4652,8 @@ }, "node_modules/queue-microtask": { "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", "dev": true, "funding": [ { @@ -4047,11 +4673,15 @@ }, "node_modules/railroad-diagrams": { "version": "1.0.0", + "resolved": "https://registry.npmjs.org/railroad-diagrams/-/railroad-diagrams-1.0.0.tgz", + "integrity": "sha512-cz93DjNeLY0idrCNOH6PviZGRN9GJhsdm9hpn1YCS879fj4W+x5IFJhhkRZcwVgMmFF7R82UA/7Oh+R8lLZg6A==", "dev": true, "license": "CC0-1.0" }, "node_modules/randexp": { "version": "0.4.6", + "resolved": "https://registry.npmjs.org/randexp/-/randexp-0.4.6.tgz", + "integrity": "sha512-80WNmd9DA0tmZrw9qQa62GPPWfuXJknrmVmLcxvq4uZBdYqb1wYoKTmnlGUchvVWe0XiLupYkBoXVOxz3C8DYQ==", "dev": true, "license": "MIT", "dependencies": { @@ -4064,6 +4694,8 @@ }, "node_modules/redos-detector": { "version": "6.1.4", + "resolved": "https://registry.npmjs.org/redos-detector/-/redos-detector-6.1.4.tgz", + "integrity": "sha512-lPlka1rEH6kK42gtgokvvxMmpAvyc28DcRjQbGxeP5RJsJWgAduBOdGedVCDPdSyCr4ay/JDxq3nGYsJZyt8tA==", "license": "MIT", "dependencies": { "regjsparser": "0.13.0" @@ -4077,6 +4709,8 @@ }, "node_modules/regjsparser": { "version": "0.13.0", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.13.0.tgz", + "integrity": "sha512-NZQZdC5wOE/H3UT28fVGL+ikOZcEzfMGk/c3iN9UGxzWHMa1op7274oyiUVrAG4B2EuFhus8SvkaYnhvW92p9Q==", "license": "BSD-2-Clause", "dependencies": { "jsesc": "~3.1.0" @@ -4087,6 +4721,8 @@ }, "node_modules/require-directory": { "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", "dev": true, "license": "MIT", "engines": { @@ -4095,6 +4731,8 @@ }, "node_modules/require-from-string": { "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", "license": "MIT", "engines": { "node": ">=0.10.0" @@ -4102,11 +4740,15 @@ }, "node_modules/require-main-filename": { "version": "2.0.0", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", + "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", "dev": true, "license": "ISC" }, "node_modules/resolve-from": { "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", "dev": true, "license": "MIT", "engines": { @@ -4115,6 +4757,8 @@ }, "node_modules/ret": { "version": "0.1.15", + "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz", + "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==", "dev": true, "license": "MIT", "engines": { @@ -4123,6 +4767,8 @@ }, "node_modules/reusify": { "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", "dev": true, "license": "MIT", "engines": { @@ -4132,6 +4778,8 @@ }, "node_modules/run-parallel": { "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", "dev": true, "funding": [ { @@ -4154,6 +4802,8 @@ }, "node_modules/rxjs": { "version": "7.8.2", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz", + "integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -4162,11 +4812,15 @@ }, "node_modules/safer-buffer": { "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", "dev": true, "license": "MIT" }, "node_modules/sast-json-schema": { "version": "0.4.1", + "resolved": "https://registry.npmjs.org/sast-json-schema/-/sast-json-schema-0.4.1.tgz", + "integrity": "sha512-GnOPf8rCTfysRtOzCJPoBZu8xKnY4LZ24jxCmcxsLUkmOTpK/DrbrJ97Xio4xkIH6USdzEZ5ZSdpTX8Gj03EuQ==", "dev": true, "license": "MIT", "workspaces": [ @@ -4188,7 +4842,9 @@ } }, "node_modules/semver": { - "version": "7.8.1", + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.4.tgz", + "integrity": "sha512-rUCObTnP32Q08R2uuIrt7r9PlEonuTmtuXYcW6s5kjdlj3xbnwe+21yXptAUYcMAABLkYYTtnmzb3w3EDZfueA==", "dev": true, "license": "ISC", "bin": { @@ -4200,11 +4856,15 @@ }, "node_modules/set-blocking": { "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", "dev": true, "license": "ISC" }, "node_modules/shebang-command": { "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", "dev": true, "license": "MIT", "dependencies": { @@ -4216,6 +4876,8 @@ }, "node_modules/shebang-regex": { "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", "dev": true, "license": "MIT", "engines": { @@ -4223,13 +4885,15 @@ } }, "node_modules/side-channel": { - "version": "1.1.0", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.1.tgz", + "integrity": "sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==", "dev": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0", - "object-inspect": "^1.13.3", - "side-channel-list": "^1.0.0", + "object-inspect": "^1.13.4", + "side-channel-list": "^1.0.1", "side-channel-map": "^1.0.1", "side-channel-weakmap": "^1.0.2" }, @@ -4242,6 +4906,8 @@ }, "node_modules/side-channel-list": { "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz", + "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==", "dev": true, "license": "MIT", "dependencies": { @@ -4257,6 +4923,8 @@ }, "node_modules/side-channel-map": { "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", "dev": true, "license": "MIT", "dependencies": { @@ -4274,6 +4942,8 @@ }, "node_modules/side-channel-weakmap": { "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", "dev": true, "license": "MIT", "dependencies": { @@ -4292,6 +4962,8 @@ }, "node_modules/signal-exit": { "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", "dev": true, "license": "ISC", "engines": { @@ -4303,6 +4975,8 @@ }, "node_modules/slash": { "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", "dev": true, "license": "MIT", "engines": { @@ -4311,6 +4985,8 @@ }, "node_modules/smtp-address-parser": { "version": "1.1.0", + "resolved": "https://registry.npmjs.org/smtp-address-parser/-/smtp-address-parser-1.1.0.tgz", + "integrity": "sha512-Gz11jbNU0plrReU9Sj7fmshSBxxJ9ShdD2q4ktHIHo/rpTH6lFyQoYHYKINPJtPe8aHFnsbtW46Ls0tCCBsIZg==", "dev": true, "license": "MIT", "dependencies": { @@ -4322,6 +4998,8 @@ }, "node_modules/source-map": { "version": "0.7.6", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.6.tgz", + "integrity": "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==", "dev": true, "license": "BSD-3-Clause", "engines": { @@ -4330,11 +5008,15 @@ }, "node_modules/sprintf-js": { "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", "dev": true, "license": "BSD-3-Clause" }, "node_modules/string-width": { "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", "dev": true, "license": "MIT", "dependencies": { @@ -4351,6 +5033,8 @@ }, "node_modules/strip-ansi": { "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", "dev": true, "license": "MIT", "dependencies": { @@ -4365,6 +5049,8 @@ }, "node_modules/strip-final-newline": { "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-4.0.0.tgz", + "integrity": "sha512-aulFJcD6YK8V1G7iRB5tigAP4TsHBZZrOV8pjV++zdUwmeV8uzbY7yn6h9MswN62adStNZFuCIx4haBnRuMDaw==", "dev": true, "license": "MIT", "engines": { @@ -4385,7 +5071,9 @@ } }, "node_modules/tinyexec": { - "version": "1.1.2", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.2.4.tgz", + "integrity": "sha512-SHf/r48b7vOrjve9PxJo3MN5v5yuyjHvdUcrQffT3WXMUfnGmHDVbC4k3sHJaJTgZCwpUplIaAo5ANtMyp3YHg==", "dev": true, "license": "MIT", "engines": { @@ -4394,6 +5082,8 @@ }, "node_modules/to-regex-range": { "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", "dev": true, "license": "MIT", "dependencies": { @@ -4405,6 +5095,8 @@ }, "node_modules/tree-kill": { "version": "1.2.2", + "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", + "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", "dev": true, "license": "MIT", "bin": { @@ -4413,11 +5105,15 @@ }, "node_modules/tslib": { "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", "dev": true, "license": "0BSD" }, "node_modules/tunnel": { "version": "0.0.6", + "resolved": "https://registry.npmjs.org/tunnel/-/tunnel-0.0.6.tgz", + "integrity": "sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg==", "dev": true, "license": "MIT", "engines": { @@ -4426,6 +5122,8 @@ }, "node_modules/typed-inject": { "version": "5.0.0", + "resolved": "https://registry.npmjs.org/typed-inject/-/typed-inject-5.0.0.tgz", + "integrity": "sha512-0Ql2ORqBORLMdAW89TQKZsb1PQkFGImFfVmncXWe7a+AA3+7dh7Se9exxZowH4kbnlvKEFkMxUYdHUpjYWFJaA==", "dev": true, "license": "Apache-2.0", "engines": { @@ -4434,6 +5132,8 @@ }, "node_modules/typed-rest-client": { "version": "2.3.1", + "resolved": "https://registry.npmjs.org/typed-rest-client/-/typed-rest-client-2.3.1.tgz", + "integrity": "sha512-k4kX5Up6qA68D0Cby2AK+6+vM5k3qTxe+/3FqhnHRExjY5cfbOnzjQZbP/LXleF8hVoDvDqxlgk9KK83HoBZlQ==", "dev": true, "license": "MIT", "dependencies": { @@ -4449,6 +5149,8 @@ }, "node_modules/typescript": { "version": "6.0.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.3.tgz", + "integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==", "dev": true, "license": "Apache-2.0", "peer": true, @@ -4462,16 +5164,22 @@ }, "node_modules/underscore": { "version": "1.13.8", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.8.tgz", + "integrity": "sha512-DXtD3ZtEQzc7M8m4cXotyHR+FAS18C64asBYY5vqZexfYryNNnDc02W4hKg3rdQuqOYas1jkseX0+nZXjTXnvQ==", "dev": true, "license": "MIT" }, "node_modules/undici-types": { "version": "7.24.6", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.24.6.tgz", + "integrity": "sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg==", "dev": true, "license": "MIT" }, "node_modules/unicorn-magic": { "version": "0.3.0", + "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.3.0.tgz", + "integrity": "sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA==", "dev": true, "license": "MIT", "engines": { @@ -4483,6 +5191,8 @@ }, "node_modules/universalify": { "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", "dev": true, "license": "MIT", "engines": { @@ -4491,6 +5201,8 @@ }, "node_modules/update-browserslist-db": { "version": "1.2.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", "dev": true, "funding": [ { @@ -4520,16 +5232,22 @@ }, "node_modules/uri-js-replace": { "version": "1.0.1", + "resolved": "https://registry.npmjs.org/uri-js-replace/-/uri-js-replace-1.0.1.tgz", + "integrity": "sha512-W+C9NWNLFOoBI2QWDp4UT9pv65r2w5Cx+3sTYFvtMdDBxkKt1syCqsUdSFAChbEe1uK5TfS04wt/nGwmaeIQ0g==", "dev": true, "license": "MIT" }, "node_modules/weapon-regex": { "version": "1.3.6", + "resolved": "https://registry.npmjs.org/weapon-regex/-/weapon-regex-1.3.6.tgz", + "integrity": "sha512-wsf1m1jmMrso5nhwVFJJHSubEBf3+pereGd7+nBKtYJ18KoB/PWJOHS3WRkwS04VrOU0iJr2bZU+l1QaTJ+9nA==", "dev": true, "license": "Apache-2.0" }, "node_modules/which": { "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", "dev": true, "license": "ISC", "dependencies": { @@ -4544,11 +5262,15 @@ }, "node_modules/which-module": { "version": "2.0.1", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.1.tgz", + "integrity": "sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==", "dev": true, "license": "ISC" }, "node_modules/wrap-ansi": { "version": "9.0.2", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz", + "integrity": "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==", "dev": true, "license": "MIT", "dependencies": { @@ -4565,11 +5287,15 @@ }, "node_modules/wrappy": { "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", "dev": true, "license": "ISC" }, "node_modules/y18n": { "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", "dev": true, "license": "ISC", "engines": { @@ -4578,11 +5304,15 @@ }, "node_modules/yallist": { "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", "dev": true, "license": "ISC" }, "node_modules/yargs": { "version": "18.0.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-18.0.0.tgz", + "integrity": "sha512-4UEqdc2RYGHZc7Doyqkrqiln3p9X2DZVxaGbwhn2pi7MrRagKaOcIKe8L3OxYcbhXLgLFUS3zAYuQjKBQgmuNg==", "dev": true, "license": "MIT", "dependencies": { @@ -4599,6 +5329,8 @@ }, "node_modules/yargs-parser": { "version": "22.0.0", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-22.0.0.tgz", + "integrity": "sha512-rwu/ClNdSMpkSrUb+d6BRsSkLUq1fmfsY6TOpYzTwvwkg1/NRG85KBy3kq++A8LKQwX6lsu+aWad+2khvuXrqw==", "dev": true, "license": "ISC", "engines": { @@ -4607,6 +5339,8 @@ }, "node_modules/yoctocolors": { "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yoctocolors/-/yoctocolors-2.1.2.tgz", + "integrity": "sha512-CzhO+pFNo8ajLM2d2IW/R93ipy99LWjtwblvC1RsoSUMZgyLbYFr221TnSNT7GjGdYui6P459mw9JH/g/zW2ug==", "dev": true, "license": "MIT", "engines": { @@ -4618,6 +5352,8 @@ }, "node_modules/zod": { "version": "4.4.3", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.4.3.tgz", + "integrity": "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==", "dev": true, "license": "MIT", "funding": { From a88ff0ab09f0c0d54fe6375ae84f815523e38c8f Mon Sep 17 00:00:00 2001 From: will Farrell Date: Fri, 12 Jun 2026 13:44:24 -0600 Subject: [PATCH 11/13] test: clean up Signed-off-by: will Farrell --- cli.js | 63 ++++++++++------- tests/cli.analyze.test.js | 133 +++++++++++++++++++++++++++++++++++ tests/cli.crawl.test.js | 141 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 313 insertions(+), 24 deletions(-) diff --git a/cli.js b/cli.js index 8c7d570..d90737c 100755 --- a/cli.js +++ b/cli.js @@ -423,6 +423,10 @@ export const crawlSchema = (obj, maxDepth = MAX_DEPTH, options = {}) => { if (typeof obj !== "object" || obj === null) return result; const deadline = options.deadline; + // Injectable monotonic clock (defaults to the real wall clock). Reading the + // clock through this indirection lets tests drive the deadline branches + // deterministically (the same pattern as options.memoryUsage below). + const now = typeof options.now === "function" ? options.now : Date.now; const denylist = resolveDangerousNames(options.lang); const denySet = new Set(denylist); @@ -455,12 +459,18 @@ export const crawlSchema = (obj, maxDepth = MAX_DEPTH, options = {}) => { }); result.timedOut = true; }; - // True when a deadline is configured and has passed. - // Stryker disable next-line ConditionalExpression,EqualityOperator: a missing - // deadline makes Date.now() > undefined false anyway (so forcing the guard true - // is equivalent), and Date.now() is never exactly the deadline, so > vs >= - // cannot differ. Timing-only boundary. - const deadlinePassed = () => deadline != null && Date.now() > deadline; + // True when a deadline is configured and has passed. The `> deadline` boundary + // is exclusive (a clock reading EXACTLY at the deadline does NOT bail), pinned by + // the injected-clock deadline tests in cli.crawl.test.js, which also kill the + // whole-condition ConditionalExpression mutant (expired clock bails, future clock + // does not). The `deadline != null` guard sits on its own line so ONLY its + // genuinely-equivalent mutant is disabled. + const deadlineConfigured = () => + // Stryker disable next-line ConditionalExpression: forcing this `!= null` guard + // true is equivalent; when deadline is absent, now() > undefined is false anyway + // (deadlinePassed short-circuits the same way), so no input distinguishes it. + deadline != null; + const deadlinePassed = () => deadlineConfigured() && now() > deadline; // Returns true (and emits one #/redos-budget finding the first time) when the // total-pattern cap has been exceeded, so callers can skip further analysis. @@ -614,12 +624,9 @@ export const crawlSchema = (obj, maxDepth = MAX_DEPTH, options = {}) => { ) { // Check the deadline BEFORE the (potentially expensive) analysis: the // once-per-pop check above is not enough when one stack frame holds many - // patterns. Bail to the timeout path on an expired deadline. - // Stryker disable next-line ConditionalExpression,BlockStatement: this guard - // only fires when a REAL isSafePattern() call between the once-per-pop check - // and here is slow; time cannot deterministically pass in a fast test, so the - // block is NoCoverage and the conditional is equivalent (timing-only). The - // original deadline check carried the same disable before deadlinePassed(). + // patterns. Bail to the timeout path on an expired deadline. Exercised + // deterministically by an injected clock that is under-deadline at the + // once-per-pop check and over it here (see cli.crawl.test.js). if (deadlinePassed()) { timeoutBail(); return result; @@ -760,11 +767,9 @@ export const crawlSchema = (obj, maxDepth = MAX_DEPTH, options = {}) => { const keyPath = `${path}/patternProperties/${escapeJsonPointer(patternKey)}`; // Check the deadline before each key: a single object can carry many // patternProperties keys, all analyzed in ONE stack frame, so the - // once-per-pop check above never fires between them. - // Stryker disable next-line ConditionalExpression,BlockStatement: fires only - // when a REAL per-key isSafePattern() call is slow; time cannot pass between - // the once-per-pop check and here in a fast test, so the block is NoCoverage - // and the conditional is equivalent (timing-only), matching the convention. + // once-per-pop check above never fires between them. Exercised with an + // injected clock that crosses the deadline before a later key (see + // cli.crawl.test.js). if (deadlinePassed()) { timeoutBail(); return result; @@ -1073,18 +1078,23 @@ export const resolveSSRFRefs = async (refs, options = {}) => { options.dnsTotalTimeoutMs != null ? Number(options.dnsTotalTimeoutMs) : DNS_TOTAL_TIMEOUT_MS; - const overallDeadline = totalMs <= 0 ? 0 : Date.now() + totalMs; + // Injectable monotonic clock (defaults to the real wall clock), mirroring + // crawlSchema's options.now so the total-budget deadline can be crossed + // deterministically in tests, including at a batch index > 0. + const now = typeof options.now === "function" ? options.now : Date.now; + const overallDeadline = totalMs <= 0 ? 0 : now() + totalMs; const results = []; const batches = [...hostnameMap.entries()]; // Stryker disable next-line EqualityOperator: < vs <= only adds one empty // trailing batch (slice past the end), so it is an equivalent mutant. for (let i = 0; i < batches.length; i += concurrency) { - // Stryker disable next-line EqualityOperator: Date.now() is never exactly the - // deadline, so > vs >= cannot differ here. - if (Date.now() > overallDeadline) { - // Stryker disable next-line MethodExpression: the budget is only ever hit - // at i=0 in tests, where slice(i) === the whole array. + // The budget boundary is exclusive; pinned by an injected-clock test that + // puts now() exactly at overallDeadline (no bail). + if (now() > overallDeadline) { + // slice(i) skips the batches already resolved; an injected clock that + // expires the budget at a batch index > 0 makes this a proper subset, + // pinned by a test in cli.analyze.test.js. for (const [hostname, entries] of batches.slice(i)) { for (const { ref, path } of entries) { results.push([ @@ -1252,7 +1262,11 @@ export const analyze = async (schema, options = {}) => { deadline = ms <= 0 ? 0 : Date.now() + ms; } - const crawl = crawlSchema(schema, maxDepth, { lang: options.lang, deadline }); + const crawl = crawlSchema(schema, maxDepth, { + lang: options.lang, + deadline, + now: options.now, + }); // Depth and timeout signal INCOMPLETE analysis: the crawl bailed early and // AJV validation plus SSRF checks were skipped. They are deliberately NOT @@ -1293,6 +1307,7 @@ export const analyze = async (schema, options = {}) => { safeHostnames: options.safeHostnames, maxHostnames: options.maxHostnames, dnsTotalTimeoutMs: options.dnsTotalTimeoutMs, + now: options.now, }); errors.push(...ssrfErrors); } diff --git a/tests/cli.analyze.test.js b/tests/cli.analyze.test.js index 364df4c..420c250 100644 --- a/tests/cli.analyze.test.js +++ b/tests/cli.analyze.test.js @@ -1384,6 +1384,139 @@ describe("resolveSSRFRefs error shapes and boundaries", () => { }); }); +// --- injectable monotonic clock: total-budget deadline at a batch index > 0 --- +// resolveSSRFRefs reads the clock through options.now (defaulting to Date.now), +// mirroring crawlSchema. With dnsConcurrency:1 each distinct host is its own batch, +// so an injected clock can let the FIRST batch resolve and then expire the budget +// before a LATER batch. That makes batches.slice(i) a proper subset (i > 0) and +// lets us pin the deadline boundary exactly. The hosts are RFC 6761 `.invalid` +// names that never resolve, so the few lookups that do run fail fast and offline. +describe("resolveSSRFRefs injected clock (budget at batch index > 0)", () => { + const ref = (hostname, path = "/$ref") => ({ + hostname, + ref: `https://${hostname}/schema.json`, + path, + }); + const stepClock = (...values) => { + let i = 0; + return () => values[Math.min(i++, values.length - 1)]; + }; + + // D-(i): the budget expires at batch index 1, not 0. now() reads: #1 sets the + // deadline (0 + 100 = 100); #2 (i=0) = 0 -> under, so host0 is resolved via DNS; + // #3 (i=1) = 200 -> over, so the loop bails with batches.slice(1). Only host1 + // (the second batch) is reported budget-exceeded; host0 got a real DNS finding. + // Kills the MethodExpression: slice() / slice(0) would mark BOTH hosts as + // budget-exceeded (and never DNS-resolve host0). + test("budget expiring at batch index 1 only skips the LATER batch (slice(i) subset)", async () => { + const errors = await resolveSSRFRefs( + [ + ref("first-host-aaa.invalid", "/a/$ref"), + ref("second-host-bbb.invalid", "/b/$ref"), + ], + { + dnsConcurrency: 1, + dnsTimeoutMs: 100, + dnsTotalTimeoutMs: 100, + now: stepClock(0, 0, 200), + }, + ); + strictEqual(errors.length, 2, "one finding per host"); + const first = errors.find( + (e) => e.params.hostname === "first-host-aaa.invalid", + ); + const second = errors.find( + (e) => e.params.hostname === "second-host-bbb.invalid", + ); + ok(first, "the first host must have a finding"); + ok(second, "the second host must have a finding"); + // host0 was resolved (DNS ran): a `.invalid` host does not resolve, so it is a + // "does not resolve" finding WITHOUT the incomplete marker. + ok( + first.message.includes("does not resolve"), + "the first batch must have been DNS-resolved, not skipped", + ); + strictEqual( + first.params.incomplete, + undefined, + "resolved host is not incomplete", + ); + // host1 was skipped because the budget expired: budget-exceeded + incomplete. + ok( + second.message.includes("SSRF DNS budget exceeded"), + "the later batch must be skipped as budget-exceeded", + ); + strictEqual( + second.params.incomplete, + true, + "skipped host is marked incomplete", + ); + }); + + // D-(ii): the budget boundary is EXCLUSIVE. now() reads: #1 sets the deadline + // (0 + 100 = 100); every later read returns EXACTLY 100, so `100 > 100` is false + // and the loop NEVER bails. Both `.invalid` hosts are therefore DNS-resolved (no + // budget-exceeded findings). Kills the EqualityOperator `>`->`>=` (which would + // bail at the boundary and mark both hosts budget-exceeded). + test("a clock exactly at the overall deadline does not bail (> is exclusive)", async () => { + const errors = await resolveSSRFRefs( + [ref("boundary-aaa.invalid"), ref("boundary-bbb.invalid")], + { + dnsConcurrency: 1, + dnsTimeoutMs: 100, + dnsTotalTimeoutMs: 100, + now: stepClock(0, 100, 100, 100), + }, + ); + strictEqual(errors.length, 2, "both hosts produce a finding"); + ok( + errors.every((e) => e.message.includes("does not resolve")), + "at the exclusive boundary both hosts must be DNS-resolved, none skipped", + ); + ok( + !errors.some((e) => e.params.incomplete === true), + "no host may be marked budget-exceeded at the boundary", + ); + }); + + // analyze() threads options.now through to BOTH crawlSchema and resolveSSRFRefs. + // A monotonic counter clock is injected: crawlSchema reads it during its crawl + // (its deadline is the real Date.now()+60s, so these tiny readings never trip the + // crawl timeout), and resolveSSRFRefs then reads the SAME injected clock for the + // SSRF budget. With dnsTotalTimeoutMs:0 the overall deadline is 0, and by the time + // the SSRF loop runs the counter has already advanced past 0, so `now() > 0` is + // true and both remote hosts are skipped as budget-exceeded. A forced-constant + // (equivalent) clock could not produce this, proving options.now flows end to end. + test("analyze threads options.now into the SSRF budget end to end", async () => { + let tick = 0; + const schema = { + $schema: "https://json-schema.org/draft/2020-12/schema", + $id: "test", + $defs: { + a: { $ref: "https://e2e-first-aaa.invalid/s.json" }, + b: { $ref: "https://e2e-second-bbb.invalid/s.json" }, + }, + }; + const errors = await analyze(schema, { + dnsConcurrency: 1, + dnsTimeoutMs: 100, + dnsTotalTimeoutMs: 0, + now: () => ++tick, + }); + ok(tick > 0, "the injected clock must have been read"); + const remote = errors.filter((e) => e.params?.incomplete === true); + strictEqual( + remote.length, + 2, + "both remote hosts are skipped as budget-exceeded", + ); + ok( + remote.every((e) => e.message.includes("SSRF DNS budget exceeded")), + "both must be budget-exceeded via the injected clock", + ); + }); +}); + // Additional resolveInstancePath guards the existing direct tests miss: an empty // pointer with a non-object root (the root guard must win over the !pointer // shortcut), and a null encountered mid-walk (the guard must return before diff --git a/tests/cli.crawl.test.js b/tests/cli.crawl.test.js index 7142d1e..dfa339f 100644 --- a/tests/cli.crawl.test.js +++ b/tests/cli.crawl.test.js @@ -2102,3 +2102,144 @@ describe("crawlSchema collected-refs cap (A4)", () => { ); }); }); + +// --- injectable monotonic clock: deterministic deadline branches --- +// crawlSchema reads the clock through options.now (defaulting to Date.now), the +// same injection pattern as options.memoryUsage. That makes every deadline branch +// reachable in a fast test: we can return a value UNDER the deadline on one read +// and OVER it on the next, so the per-pattern / per-key bails (which fire only +// between the once-per-pop check and the next check) are exercised deterministically. +describe("crawlSchema injected clock (deadline branches)", () => { + // Returns a clock that yields the given values in order, repeating the last + // value once the sequence is exhausted. + const stepClock = (...values) => { + let i = 0; + return () => values[Math.min(i++, values.length - 1)]; + }; + + // C: deadlinePassed semantics. A clock UNDER the deadline must NOT bail, so a + // clean schema is analyzed normally (no timeout finding). Kills the whole- + // condition ConditionalExpression forced-true (which would bail wrongly). + test("a clock under the deadline does not bail (no timeout finding)", () => { + const r = crawlSchema({ type: "string", minLength: 1, maxLength: 5 }, 32, { + deadline: 5_000, + now: () => 1_000, + }); + strictEqual( + r.timedOut, + false, + "must not time out when clock is under deadline", + ); + ok(!r.errors.some((e) => e.keyword === "timeout"), "no timeout finding"); + }); + + // C: a clock OVER the deadline bails on the first pop. Kills the whole-condition + // ConditionalExpression forced-false (which would never bail). + test("a clock over the deadline bails on the first pop", () => { + const r = crawlSchema({ type: "string", minLength: 5, maxLength: 1 }, 32, { + deadline: 1_000, + now: () => 2_000, + }); + ok( + r.errors.some((e) => e.keyword === "timeout"), + "must emit the timeout finding", + ); + strictEqual(r.timedOut, true); + ok( + !r.errors.some((e) => e.keyword === "minLength"), + "must bail before the structural check", + ); + }); + + // C: the deadline boundary is EXCLUSIVE. A clock reading EXACTLY at the deadline + // must NOT bail. Kills the EqualityOperator `>`->`>=` (which would bail at the + // boundary and lose the structural finding). + test("a clock exactly at the deadline does not bail (> is exclusive)", () => { + const r = crawlSchema({ type: "string", minLength: 5, maxLength: 1 }, 32, { + deadline: 1_000, + now: () => 1_000, + }); + strictEqual(r.timedOut, false, "exactly at the deadline must not bail"); + ok( + !r.errors.some((e) => e.keyword === "timeout"), + "no timeout finding at the boundary", + ); + ok( + r.errors.some((e) => e.keyword === "minLength"), + "the structural check still runs at the boundary", + ); + }); + + // A: per-pattern deadline bail in the top-level `pattern` block. The clock is + // UNDER the deadline at the once-per-pop check (call #1) and OVER it at the + // per-pattern check (call #2), so the per-pattern guard fires. Kills both the + // ConditionalExpression (if(false) would skip the bail and analyze the pattern) + // and the BlockStatement (an empty block would neither push the finding nor + // return, so analysis would continue). + test("per-pattern deadline bail fires between the once-per-pop and per-pattern checks", () => { + // Two reads per pop on the single node: top-of-loop (under) then per-pattern + // (over). A safe pattern is used so, absent the bail, NO pattern finding + // would appear and the crawl would complete cleanly. + const r = crawlSchema({ type: "string", pattern: "^[a-z]+$" }, 32, { + deadline: 1_000, + now: stepClock(100, 2_000), + }); + ok( + r.errors.some((e) => e.keyword === "timeout"), + "the per-pattern check must bail to the timeout path", + ); + strictEqual(r.timedOut, true); + ok( + !r.errors.some((e) => e.schemaPath === "#/redos"), + "the per-pattern bail must occur before any ReDoS analysis", + ); + strictEqual(r.errors.length, 1, "only the timeout finding is emitted"); + }); + + // B: per-key deadline bail in the patternProperties loop. The patternProperties + // values are BOOLEAN subschemas (true), so they are never pushed as further nodes + // to crawl. That is deliberate: the ONLY nodes popped are the root and the + // patternProperties object itself, and the clock stays UNDER the deadline on every + // top-of-loop read. The only read that can go OVER is the per-key check on the + // SECOND key. So the timeout finding can ONLY come from the per-key guard: + // - real code: #1 root top-of-loop (100, under), #2 key "^aaa$" per-key + // (200, under), #3 key "^bbb$" per-key (2000, OVER) -> bail. + // - if(false) mutant: the per-key reads vanish, leaving #1 root (100) and #2 + // patternProperties-object top-of-loop (200), both UNDER -> + // no timeout -> this test fails, killing the mutant. + // - empty-block mutant: the per-key reads still run (#2 200, #3 2000) but never + // bail; the clock then returns UNDER once more (#4 100) for + // the patternProperties-object top-of-loop read, so no + // timeout is produced -> killed. The clock returns OVER only + // ONCE (the bbb per-key read) and then drops back under, + // which is what lets it kill BOTH mutants at once. + // Kills both the ConditionalExpression and the BlockStatement for the per-key guard. + test("per-key deadline bail fires before a later patternProperties key", () => { + const r = crawlSchema( + { patternProperties: { "^aaa$": true, "^bbb$": true } }, + 32, + { deadline: 1_000, now: stepClock(100, 200, 2_000, 100) }, + ); + ok( + r.errors.some((e) => e.keyword === "timeout"), + "the per-key check must bail to the timeout path", + ); + strictEqual(r.timedOut, true); + }); + + // A safe top-level pattern with an under-deadline clock on BOTH reads must be + // analyzed normally (no timeout): proves the per-pattern guard does NOT bail when + // the clock stays under, complementing the bail test above so the conditional is + // pinned on both sides. + test("per-pattern guard does not bail when the clock stays under the deadline", () => { + const r = crawlSchema({ type: "string", pattern: "^(a+)+$" }, 32, { + deadline: 1_000, + now: stepClock(100, 200), + }); + strictEqual(r.timedOut, false, "must not bail when the clock stays under"); + ok( + r.errors.some((e) => e.schemaPath === "#/redos"), + "the pattern is analyzed and flagged when no bail occurs", + ); + }); +}); From 13f92c9fc0ea3421057f4d023815dee8d896196d Mon Sep 17 00:00:00 2001 From: will Farrell Date: Fri, 12 Jun 2026 14:22:08 -0600 Subject: [PATCH 12/13] ci: fix issues Signed-off-by: will Farrell --- .github/dependabot.yml | 2 ++ .github/workflows/release.yml | 4 ++-- package.json | 2 +- tests/bfixes.test.js | 2 +- tests/build.test.js | 8 ++++---- tests/cli.fuzz.js | 2 +- 6 files changed, 11 insertions(+), 9 deletions(-) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index be3a3f2..fe826b7 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -5,6 +5,8 @@ updates: directory: "/" schedule: interval: "weekly" + cooldown: + default-days: 15 groups: everything: patterns: diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 2342964..887f5d4 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -100,7 +100,7 @@ jobs: node-version: ${{ env.NODE_VERSION }} registry-url: https://registry.npmjs.org - name: Download artifact - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # 8.0.1 + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: name: ${{ needs.build.outputs.tag }} - name: Release @@ -136,7 +136,7 @@ jobs: node-version: ${{ env.NODE_VERSION }} registry-url: https://registry.npmjs.org - name: Download artifact - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # 8.0.1 + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: name: ${{ needs.release.outputs.tag }} - name: Verify build provenance attestation diff --git a/package.json b/package.json index 4b3475c..edd010f 100644 --- a/package.json +++ b/package.json @@ -66,7 +66,7 @@ "test:sast:semgrep": "semgrep scan --config auto --error", "test:sast:trivy": "trivy fs --scanners vuln,license --include-dev-deps --ignored-licenses 0BSD,Apache-2.0,BSD-1-Clause,BSD-2-Clause,BSD-3-Clause,CC0-1.0,CC-BY-4.0,ISC,MIT,Python-2.0,LGPL-3.0-or-later,MPL-2.0,BlueOak-1.0.0,Unlicense --exit-code 1 --skip-files '**/bun.lock' --disable-telemetry .", "test:sast:trufflehog": "trufflehog filesystem --only-verified --log-level=-1 ./", - "test:sast:zizmor": "zizmor .github/workflows/" + "test:sast:zizmor": "zizmor .github" }, "keywords": [ "JSON", diff --git a/tests/bfixes.test.js b/tests/bfixes.test.js index 33d4f7d..4a16a9a 100644 --- a/tests/bfixes.test.js +++ b/tests/bfixes.test.js @@ -115,7 +115,7 @@ describe("B1: recursive value size limits (2020-12)", () => { // bounded by AJV's own runtime recursion limit: a 2000-level value is // never accepted, it fails closed (validate returns false or throws a // RangeError during validation). Either way it is not treated as safe. - let accepted = true; + let accepted; try { accepted = validate(wrap2020(keyword, buildDeep(2000))); } catch { diff --git a/tests/build.test.js b/tests/build.test.js index d454ce8..ae531da 100644 --- a/tests/build.test.js +++ b/tests/build.test.js @@ -1,4 +1,4 @@ -import { strictEqual, throws } from "node:assert"; +import { doesNotThrow, strictEqual, throws } from "node:assert"; import { describe, test } from "node:test"; import { verifyRefs } from "../bin/build.js"; @@ -13,7 +13,7 @@ describe("verifyRefs", () => { x: { $ref: "#/$defs/b" }, }, }; - strictEqual(verifyRefs(schema, "ok.json"), undefined); + doesNotThrow(() => verifyRefs(schema, "ok.json")); }); test("throws naming the dangling pointer", () => { @@ -48,7 +48,7 @@ describe("verifyRefs", () => { y: { $ref: "#/properties/x" }, }, }; - strictEqual(verifyRefs(schema, "ignore.json"), undefined); + doesNotThrow(() => verifyRefs(schema, "ignore.json")); }); test("resolves #/definitions/ pointers against definitions", () => { @@ -56,7 +56,7 @@ describe("verifyRefs", () => { definitions: { a: { type: "string" } }, properties: { x: { $ref: "#/definitions/a" } }, }; - strictEqual(verifyRefs(ok, "defs-ok.json"), undefined); + doesNotThrow(() => verifyRefs(ok, "defs-ok.json")); const bad = { definitions: { a: { type: "string" } }, properties: { x: { $ref: "#/definitions/missing" } }, diff --git a/tests/cli.fuzz.js b/tests/cli.fuzz.js index 7d4452f..a1da59a 100644 --- a/tests/cli.fuzz.js +++ b/tests/cli.fuzz.js @@ -146,7 +146,7 @@ test("fuzz: crawlSchema() never throws and honours its result contract", () => { `crawlSchema threw ${err?.constructor?.name}: ${err?.message} for ${JSON.stringify(schema)}`, ); } - if (result === null || typeof result !== "object") { + if (!result || typeof result !== "object") { throw new Error( `crawlSchema did not return an object for ${JSON.stringify(schema)}`, ); From fa2ef0c61cf1ea899dbc7b77c0a8ea92e0acddb0 Mon Sep 17 00:00:00 2001 From: will Farrell Date: Fri, 12 Jun 2026 14:23:26 -0600 Subject: [PATCH 13/13] ci: set to 2w Signed-off-by: will Farrell --- .github/dependabot.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index fe826b7..476febc 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -6,7 +6,7 @@ updates: schedule: interval: "weekly" cooldown: - default-days: 15 + default-days: 14 groups: everything: patterns: @@ -17,7 +17,7 @@ updates: schedule: interval: "weekly" cooldown: - default-days: 15 + default-days: 14 groups: dev-dependencies: dependency-type: "development"