@arimakouyou/spec-workflow-mcp 2.2.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/.mcp.json +8 -0
- package/.claude-plugin/agents/code-simplifier.md +80 -0
- package/.claude-plugin/agents/integ-test-auditor.md +91 -0
- package/.claude-plugin/agents/integ-test-worker.md +73 -0
- package/.claude-plugin/agents/parallel-worker.md +136 -0
- package/.claude-plugin/agents/review-worker.md +279 -0
- package/.claude-plugin/agents/unit-test-engineer.md +148 -0
- package/.claude-plugin/agents/wave-harness-worker.md +158 -0
- package/.claude-plugin/hooks/hooks.json +16 -0
- package/.claude-plugin/hooks/tasks-read-guard.sh +17 -0
- package/.claude-plugin/marketplace.json +33 -0
- package/.claude-plugin/plugin.json +11 -0
- package/.claude-plugin/rules/axum.md +154 -0
- package/.claude-plugin/rules/cargo-toml.md +63 -0
- package/.claude-plugin/rules/context7.md +17 -0
- package/.claude-plugin/rules/design-conformance.md +82 -0
- package/.claude-plugin/rules/design-principles.md +53 -0
- package/.claude-plugin/rules/diesel.md +176 -0
- package/.claude-plugin/rules/feedback-loop.md +33 -0
- package/.claude-plugin/rules/leptos.md +319 -0
- package/.claude-plugin/rules/project-architecture.md +134 -0
- package/.claude-plugin/rules/quality-checks.md +265 -0
- package/.claude-plugin/rules/rust-style.md +242 -0
- package/.claude-plugin/rules/security.md +67 -0
- package/.claude-plugin/rules/spec-workflow-enforcement.md +47 -0
- package/.claude-plugin/rules/valkey.md +167 -0
- package/.claude-plugin/skills/integration-test/SKILL.md +230 -0
- package/.claude-plugin/skills/integration-test/references/auditor-prompt.md +78 -0
- package/.claude-plugin/skills/integration-test/references/external-api-mock.md +98 -0
- package/.claude-plugin/skills/integration-test/references/fixture-catalog.md +155 -0
- package/.claude-plugin/skills/integration-test/references/parallel-execution.md +124 -0
- package/.claude-plugin/skills/integration-test/references/quality-gate.md +80 -0
- package/.claude-plugin/skills/integration-test/references/test-case-design.md +88 -0
- package/.claude-plugin/skills/integration-test/references/test-patterns.md +215 -0
- package/.claude-plugin/skills/integration-test/references/whiteboard-template.md +81 -0
- package/.claude-plugin/skills/integration-test/references/worker-prompt.md +70 -0
- package/.claude-plugin/skills/knowhow-capture/SKILL.md +143 -0
- package/.claude-plugin/skills/phase-review-team/SKILL.md +380 -0
- package/.claude-plugin/skills/spec-design/SKILL.md +282 -0
- package/.claude-plugin/skills/spec-e2e-implement/SKILL.md +259 -0
- package/.claude-plugin/skills/spec-impl-code/SKILL.md +101 -0
- package/.claude-plugin/skills/spec-impl-review/SKILL.md +115 -0
- package/.claude-plugin/skills/spec-impl-test-run/SKILL.md +98 -0
- package/.claude-plugin/skills/spec-impl-test-write/SKILL.md +121 -0
- package/.claude-plugin/skills/spec-implement/SKILL.md +822 -0
- package/.claude-plugin/skills/spec-requirements/SKILL.md +130 -0
- package/.claude-plugin/skills/spec-review/SKILL.md +274 -0
- package/.claude-plugin/skills/spec-tasks/SKILL.md +372 -0
- package/.claude-plugin/skills/spec-test-design/SKILL.md +233 -0
- package/.claude-plugin/skills/tdd-skills/SKILL.md +95 -0
- package/.claude-plugin/skills/tdd-skills/references/advanced-techniques.md +49 -0
- package/.claude-plugin/skills/tdd-skills/references/green-strategies.md +70 -0
- package/.claude-plugin/skills/tdd-skills/references/tdd-and-design.md +48 -0
- package/.claude-plugin/skills/tdd-skills/references/test-design.md +43 -0
- package/.claude-plugin/skills/tdd-skills/references/test-doubles.md +53 -0
- package/.claude-plugin/skills/tdd-skills/references/test-patterns.md +40 -0
- package/.claude-plugin/skills/tdd-skills-rust/SKILL.md +128 -0
- package/.claude-plugin/skills/tdd-skills-rust/references/advanced-techniques.md +205 -0
- package/.claude-plugin/skills/tdd-skills-rust/references/green-strategies.md +166 -0
- package/.claude-plugin/skills/tdd-skills-rust/references/tdd-and-design.md +215 -0
- package/.claude-plugin/skills/tdd-skills-rust/references/test-design.md +128 -0
- package/.claude-plugin/skills/tdd-skills-rust/references/test-doubles.md +208 -0
- package/.claude-plugin/skills/tdd-skills-rust/references/test-patterns.md +223 -0
- package/.claude-plugin/with-dashboard/.mcp.json +8 -0
- package/.claude-plugin/with-dashboard/plugin.json +10 -0
- package/CHANGELOG.md +1007 -0
- package/LICENSE +674 -0
- package/README.ja.md +380 -0
- package/README.md +437 -0
- package/dist/__tests__/config.test.d.ts +2 -0
- package/dist/__tests__/config.test.d.ts.map +1 -0
- package/dist/__tests__/config.test.js +264 -0
- package/dist/__tests__/config.test.js.map +1 -0
- package/dist/__tests__/index-args.test.d.ts +2 -0
- package/dist/__tests__/index-args.test.d.ts.map +1 -0
- package/dist/__tests__/index-args.test.js +43 -0
- package/dist/__tests__/index-args.test.js.map +1 -0
- package/dist/__tests__/index-entrypoint.test.d.ts +2 -0
- package/dist/__tests__/index-entrypoint.test.d.ts.map +1 -0
- package/dist/__tests__/index-entrypoint.test.js +23 -0
- package/dist/__tests__/index-entrypoint.test.js.map +1 -0
- package/dist/config.d.ts +26 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +188 -0
- package/dist/config.js.map +1 -0
- package/dist/core/__tests__/git-utils.test.d.ts +2 -0
- package/dist/core/__tests__/git-utils.test.d.ts.map +1 -0
- package/dist/core/__tests__/git-utils.test.js +179 -0
- package/dist/core/__tests__/git-utils.test.js.map +1 -0
- package/dist/core/__tests__/mdx-validator.test.d.ts +2 -0
- package/dist/core/__tests__/mdx-validator.test.d.ts.map +1 -0
- package/dist/core/__tests__/mdx-validator.test.js +42 -0
- package/dist/core/__tests__/mdx-validator.test.js.map +1 -0
- package/dist/core/__tests__/path-utils.test.d.ts +2 -0
- package/dist/core/__tests__/path-utils.test.d.ts.map +1 -0
- package/dist/core/__tests__/path-utils.test.js +342 -0
- package/dist/core/__tests__/path-utils.test.js.map +1 -0
- package/dist/core/__tests__/project-registry.test.d.ts +2 -0
- package/dist/core/__tests__/project-registry.test.d.ts.map +1 -0
- package/dist/core/__tests__/project-registry.test.js +62 -0
- package/dist/core/__tests__/project-registry.test.js.map +1 -0
- package/dist/core/__tests__/security-utils.test.d.ts +2 -0
- package/dist/core/__tests__/security-utils.test.d.ts.map +1 -0
- package/dist/core/__tests__/security-utils.test.js +657 -0
- package/dist/core/__tests__/security-utils.test.js.map +1 -0
- package/dist/core/__tests__/task-parser.test.d.ts +2 -0
- package/dist/core/__tests__/task-parser.test.d.ts.map +1 -0
- package/dist/core/__tests__/task-parser.test.js +222 -0
- package/dist/core/__tests__/task-parser.test.js.map +1 -0
- package/dist/core/__tests__/task-validator.test.d.ts +2 -0
- package/dist/core/__tests__/task-validator.test.d.ts.map +1 -0
- package/dist/core/__tests__/task-validator.test.js +308 -0
- package/dist/core/__tests__/task-validator.test.js.map +1 -0
- package/dist/core/archive-service.d.ts +10 -0
- package/dist/core/archive-service.d.ts.map +1 -0
- package/dist/core/archive-service.js +99 -0
- package/dist/core/archive-service.js.map +1 -0
- package/dist/core/dashboard-session.d.ts +49 -0
- package/dist/core/dashboard-session.d.ts.map +1 -0
- package/dist/core/dashboard-session.js +132 -0
- package/dist/core/dashboard-session.js.map +1 -0
- package/dist/core/git-utils.d.ts +25 -0
- package/dist/core/git-utils.d.ts.map +1 -0
- package/dist/core/git-utils.js +87 -0
- package/dist/core/git-utils.js.map +1 -0
- package/dist/core/global-dir.d.ts +44 -0
- package/dist/core/global-dir.d.ts.map +1 -0
- package/dist/core/global-dir.js +74 -0
- package/dist/core/global-dir.js.map +1 -0
- package/dist/core/implementation-log-migrator.d.ts +41 -0
- package/dist/core/implementation-log-migrator.d.ts.map +1 -0
- package/dist/core/implementation-log-migrator.js +258 -0
- package/dist/core/implementation-log-migrator.js.map +1 -0
- package/dist/core/mdx-validator.d.ts +14 -0
- package/dist/core/mdx-validator.d.ts.map +1 -0
- package/dist/core/mdx-validator.js +34 -0
- package/dist/core/mdx-validator.js.map +1 -0
- package/dist/core/parser.d.ts +11 -0
- package/dist/core/parser.d.ts.map +1 -0
- package/dist/core/parser.js +128 -0
- package/dist/core/parser.js.map +1 -0
- package/dist/core/path-utils.d.ts +68 -0
- package/dist/core/path-utils.d.ts.map +1 -0
- package/dist/core/path-utils.js +302 -0
- package/dist/core/path-utils.js.map +1 -0
- package/dist/core/project-registry.d.ts +94 -0
- package/dist/core/project-registry.d.ts.map +1 -0
- package/dist/core/project-registry.js +297 -0
- package/dist/core/project-registry.js.map +1 -0
- package/dist/core/security-utils.d.ts +99 -0
- package/dist/core/security-utils.d.ts.map +1 -0
- package/dist/core/security-utils.js +275 -0
- package/dist/core/security-utils.js.map +1 -0
- package/dist/core/task-parser.d.ts +90 -0
- package/dist/core/task-parser.d.ts.map +1 -0
- package/dist/core/task-parser.js +477 -0
- package/dist/core/task-parser.js.map +1 -0
- package/dist/core/task-validator.d.ts +37 -0
- package/dist/core/task-validator.d.ts.map +1 -0
- package/dist/core/task-validator.js +499 -0
- package/dist/core/task-validator.js.map +1 -0
- package/dist/core/workspace-initializer.d.ts +16 -0
- package/dist/core/workspace-initializer.d.ts.map +1 -0
- package/dist/core/workspace-initializer.js +168 -0
- package/dist/core/workspace-initializer.js.map +1 -0
- package/dist/dashboard/__tests__/approval-storage-path-resolution.test.d.ts +2 -0
- package/dist/dashboard/__tests__/approval-storage-path-resolution.test.d.ts.map +1 -0
- package/dist/dashboard/__tests__/approval-storage-path-resolution.test.js +78 -0
- package/dist/dashboard/__tests__/approval-storage-path-resolution.test.js.map +1 -0
- package/dist/dashboard/__tests__/multi-server-approvals-content.test.d.ts +2 -0
- package/dist/dashboard/__tests__/multi-server-approvals-content.test.d.ts.map +1 -0
- package/dist/dashboard/__tests__/multi-server-approvals-content.test.js +115 -0
- package/dist/dashboard/__tests__/multi-server-approvals-content.test.js.map +1 -0
- package/dist/dashboard/__tests__/watcher-error-handling.test.d.ts +2 -0
- package/dist/dashboard/__tests__/watcher-error-handling.test.d.ts.map +1 -0
- package/dist/dashboard/__tests__/watcher-error-handling.test.js +118 -0
- package/dist/dashboard/__tests__/watcher-error-handling.test.js.map +1 -0
- package/dist/dashboard/approval-storage.d.ts +139 -0
- package/dist/dashboard/approval-storage.d.ts.map +1 -0
- package/dist/dashboard/approval-storage.js +608 -0
- package/dist/dashboard/approval-storage.js.map +1 -0
- package/dist/dashboard/execution-history-manager.d.ts +52 -0
- package/dist/dashboard/execution-history-manager.d.ts.map +1 -0
- package/dist/dashboard/execution-history-manager.js +161 -0
- package/dist/dashboard/execution-history-manager.js.map +1 -0
- package/dist/dashboard/implementation-log-manager.d.ts +97 -0
- package/dist/dashboard/implementation-log-manager.d.ts.map +1 -0
- package/dist/dashboard/implementation-log-manager.js +617 -0
- package/dist/dashboard/implementation-log-manager.js.map +1 -0
- package/dist/dashboard/job-scheduler.d.ts +91 -0
- package/dist/dashboard/job-scheduler.d.ts.map +1 -0
- package/dist/dashboard/job-scheduler.js +321 -0
- package/dist/dashboard/job-scheduler.js.map +1 -0
- package/dist/dashboard/multi-server.d.ts +42 -0
- package/dist/dashboard/multi-server.d.ts.map +1 -0
- package/dist/dashboard/multi-server.js +1460 -0
- package/dist/dashboard/multi-server.js.map +1 -0
- package/dist/dashboard/parser.d.ts +18 -0
- package/dist/dashboard/parser.d.ts.map +1 -0
- package/dist/dashboard/parser.js +269 -0
- package/dist/dashboard/parser.js.map +1 -0
- package/dist/dashboard/project-manager.d.ts +82 -0
- package/dist/dashboard/project-manager.d.ts.map +1 -0
- package/dist/dashboard/project-manager.js +257 -0
- package/dist/dashboard/project-manager.js.map +1 -0
- package/dist/dashboard/public/assets/Inter-Bold-CD3Pr7BX.woff2 +0 -0
- package/dist/dashboard/public/assets/Inter-Medium-B_8v_WHh.woff2 +0 -0
- package/dist/dashboard/public/assets/Inter-Regular-DRVdRqcI.woff2 +0 -0
- package/dist/dashboard/public/assets/Inter-SemiBold-CtskMddL.woff2 +0 -0
- package/dist/dashboard/public/assets/JetBrainsMono-Bold-D4WEaHbo.woff2 +0 -0
- package/dist/dashboard/public/assets/JetBrainsMono-Medium-3S3k2nMz.woff2 +0 -0
- package/dist/dashboard/public/assets/JetBrainsMono-Regular-BQaDgvhP.woff2 +0 -0
- package/dist/dashboard/public/assets/Tableau10-B-NsZVaP.js +1 -0
- package/dist/dashboard/public/assets/apl-B4CMkyY2.js +1 -0
- package/dist/dashboard/public/assets/arc-a5wW942W.js +1 -0
- package/dist/dashboard/public/assets/array-BKyUJesY.js +1 -0
- package/dist/dashboard/public/assets/asciiarmor-Df11BRmG.js +1 -0
- package/dist/dashboard/public/assets/asn1-EdZsLKOL.js +1 -0
- package/dist/dashboard/public/assets/asterisk-B-8jnY81.js +1 -0
- package/dist/dashboard/public/assets/blockDiagram-c4efeb88-CvjTuK-w.js +118 -0
- package/dist/dashboard/public/assets/brainfuck-C4LP7Hcl.js +1 -0
- package/dist/dashboard/public/assets/c4Diagram-c83219d4-NwVQo5kf.js +10 -0
- package/dist/dashboard/public/assets/channel-Bi16YZhk.js +1 -0
- package/dist/dashboard/public/assets/classDiagram-beda092f-BmSeXDdU.js +2 -0
- package/dist/dashboard/public/assets/classDiagram-v2-2358418a-D7GvvuPr.js +2 -0
- package/dist/dashboard/public/assets/clike-B9uivgTg.js +1 -0
- package/dist/dashboard/public/assets/clojure-BMjYHr_A.js +1 -0
- package/dist/dashboard/public/assets/clone-BpKTiq7P.js +1 -0
- package/dist/dashboard/public/assets/cmake-BQqOBYOt.js +1 -0
- package/dist/dashboard/public/assets/cobol-CWcv1MsR.js +1 -0
- package/dist/dashboard/public/assets/coffeescript-S37ZYGWr.js +1 -0
- package/dist/dashboard/public/assets/commonlisp-DBKNyK5s.js +1 -0
- package/dist/dashboard/public/assets/createText-1719965b-qASbqHUP.js +7 -0
- package/dist/dashboard/public/assets/crystal-SjHAIU92.js +1 -0
- package/dist/dashboard/public/assets/css-BnMrqG3P.js +1 -0
- package/dist/dashboard/public/assets/cypher-C_CwsFkJ.js +1 -0
- package/dist/dashboard/public/assets/d-pRatUO7H.js +1 -0
- package/dist/dashboard/public/assets/diff-DbItnlRl.js +1 -0
- package/dist/dashboard/public/assets/dockerfile-BKs6k2Af.js +1 -0
- package/dist/dashboard/public/assets/dtd-DF_7sFjM.js +1 -0
- package/dist/dashboard/public/assets/dylan-DwRh75JA.js +1 -0
- package/dist/dashboard/public/assets/ebnf-CDyGwa7X.js +1 -0
- package/dist/dashboard/public/assets/ecl-Cabwm37j.js +1 -0
- package/dist/dashboard/public/assets/edges-96097737-BItTSnH7.js +4 -0
- package/dist/dashboard/public/assets/eiffel-CnydiIhH.js +1 -0
- package/dist/dashboard/public/assets/elm-vLlmbW-K.js +1 -0
- package/dist/dashboard/public/assets/erDiagram-0228fc6a-DT224olg.js +51 -0
- package/dist/dashboard/public/assets/erlang-BNw1qcRV.js +1 -0
- package/dist/dashboard/public/assets/factor-kuTfRLto.js +1 -0
- package/dist/dashboard/public/assets/fcl-Kvtd6kyn.js +1 -0
- package/dist/dashboard/public/assets/flowDb-c6c81e3f-D9_ukKtv.js +10 -0
- package/dist/dashboard/public/assets/flowDiagram-50d868cf-CylE8siG.js +4 -0
- package/dist/dashboard/public/assets/flowDiagram-v2-4f6560a1-B2O3JN7Y.js +1 -0
- package/dist/dashboard/public/assets/flowchart-elk-definition-6af322e1-BCaqFKf3.js +139 -0
- package/dist/dashboard/public/assets/forth-Ffai-XNe.js +1 -0
- package/dist/dashboard/public/assets/fortran-DYz_wnZ1.js +1 -0
- package/dist/dashboard/public/assets/ganttDiagram-a2739b55-WQUL1QW_.js +257 -0
- package/dist/dashboard/public/assets/gas-Bneqetm1.js +1 -0
- package/dist/dashboard/public/assets/gherkin-heZmZLOM.js +1 -0
- package/dist/dashboard/public/assets/gitGraphDiagram-82fe8481-CttZrdmr.js +70 -0
- package/dist/dashboard/public/assets/graph-Ch-rVueN.js +1 -0
- package/dist/dashboard/public/assets/groovy-D9Dt4D0W.js +1 -0
- package/dist/dashboard/public/assets/haskell-Cw1EW3IL.js +1 -0
- package/dist/dashboard/public/assets/haxe-H-WmDvRZ.js +1 -0
- package/dist/dashboard/public/assets/http-DBlCnlav.js +1 -0
- package/dist/dashboard/public/assets/idl-BEugSyMb.js +1 -0
- package/dist/dashboard/public/assets/index--kbPpDKv.js +1 -0
- package/dist/dashboard/public/assets/index-3scDwWm6.js +1 -0
- package/dist/dashboard/public/assets/index-5325376f-BL2zVOJU.js +1 -0
- package/dist/dashboard/public/assets/index-BZdjbO25.js +1 -0
- package/dist/dashboard/public/assets/index-BmA_batZ.js +1 -0
- package/dist/dashboard/public/assets/index-Bu0u99kF.js +2 -0
- package/dist/dashboard/public/assets/index-Ch-lr7F4.js +1 -0
- package/dist/dashboard/public/assets/index-ClgWbdoq.js +1 -0
- package/dist/dashboard/public/assets/index-CzLwOMQ_.js +3 -0
- package/dist/dashboard/public/assets/index-DAOEjGO7.js +1 -0
- package/dist/dashboard/public/assets/index-DXqf0B9c.js +1 -0
- package/dist/dashboard/public/assets/index-DegWdR16.js +1 -0
- package/dist/dashboard/public/assets/index-DiHyYGim.js +1 -0
- package/dist/dashboard/public/assets/index-DlZtG7I5.js +1 -0
- package/dist/dashboard/public/assets/index-DmhGE2M8.js +1 -0
- package/dist/dashboard/public/assets/index-QEGvld4x.js +1 -0
- package/dist/dashboard/public/assets/index-RfZPGAJu.js +1 -0
- package/dist/dashboard/public/assets/index-UybBj_7u.js +319 -0
- package/dist/dashboard/public/assets/index-bVekzPnl.js +7 -0
- package/dist/dashboard/public/assets/index-f5bysQzW.css +1 -0
- package/dist/dashboard/public/assets/infoDiagram-8eee0895-DjzkkE3o.js +7 -0
- package/dist/dashboard/public/assets/init-Gi6I4Gst.js +1 -0
- package/dist/dashboard/public/assets/javascript-iXu5QeM3.js +1 -0
- package/dist/dashboard/public/assets/journeyDiagram-c64418c1-CxPZkNdB.js +139 -0
- package/dist/dashboard/public/assets/julia-DuME0IfC.js +1 -0
- package/dist/dashboard/public/assets/katex-XbL3y5x-.js +261 -0
- package/dist/dashboard/public/assets/layout-DX7DNTRm.js +1 -0
- package/dist/dashboard/public/assets/line-DfvpmKOn.js +1 -0
- package/dist/dashboard/public/assets/linear-gQbBPHO5.js +1 -0
- package/dist/dashboard/public/assets/livescript-BwQOo05w.js +1 -0
- package/dist/dashboard/public/assets/lua-BgMRiT3U.js +1 -0
- package/dist/dashboard/public/assets/mathematica-DTrFuWx2.js +1 -0
- package/dist/dashboard/public/assets/mbox-CNhZ1qSd.js +1 -0
- package/dist/dashboard/public/assets/mindmap-definition-8da855dc-CNxmpyG6.js +415 -0
- package/dist/dashboard/public/assets/mirc-CjQqDB4T.js +1 -0
- package/dist/dashboard/public/assets/mllike-CXdrOF99.js +1 -0
- package/dist/dashboard/public/assets/modelica-Dc1JOy9r.js +1 -0
- package/dist/dashboard/public/assets/mscgen-BA5vi2Kp.js +1 -0
- package/dist/dashboard/public/assets/mumps-BT43cFF4.js +1 -0
- package/dist/dashboard/public/assets/nginx-DdIZxoE0.js +1 -0
- package/dist/dashboard/public/assets/nsis-LdVXkNf5.js +1 -0
- package/dist/dashboard/public/assets/ntriples-BfvgReVJ.js +1 -0
- package/dist/dashboard/public/assets/octave-Ck1zUtKM.js +1 -0
- package/dist/dashboard/public/assets/ordinal-Cboi1Yqb.js +1 -0
- package/dist/dashboard/public/assets/oz-BzwKVEFT.js +1 -0
- package/dist/dashboard/public/assets/pascal--L3eBynH.js +1 -0
- package/dist/dashboard/public/assets/path-CbwjOpE9.js +1 -0
- package/dist/dashboard/public/assets/perl-CdXCOZ3F.js +1 -0
- package/dist/dashboard/public/assets/pieDiagram-a8764435-D-xy_NSA.js +35 -0
- package/dist/dashboard/public/assets/pig-CevX1Tat.js +1 -0
- package/dist/dashboard/public/assets/powershell-CFHJl5sT.js +1 -0
- package/dist/dashboard/public/assets/properties-C78fOPTZ.js +1 -0
- package/dist/dashboard/public/assets/protobuf-ChK-085T.js +1 -0
- package/dist/dashboard/public/assets/pug-DeIclll2.js +1 -0
- package/dist/dashboard/public/assets/puppet-DMA9R1ak.js +1 -0
- package/dist/dashboard/public/assets/python-BuPzkPfP.js +1 -0
- package/dist/dashboard/public/assets/q-pXgVlZs6.js +1 -0
- package/dist/dashboard/public/assets/quadrantDiagram-1e28029f-BoL2wzz0.js +7 -0
- package/dist/dashboard/public/assets/r-B6wPVr8A.js +1 -0
- package/dist/dashboard/public/assets/requirementDiagram-08caed73-BujFz0q1.js +52 -0
- package/dist/dashboard/public/assets/rpm-CTu-6PCP.js +1 -0
- package/dist/dashboard/public/assets/ruby-B2Rjki9n.js +1 -0
- package/dist/dashboard/public/assets/sankeyDiagram-a04cb91d-D03_NARm.js +8 -0
- package/dist/dashboard/public/assets/sas-B4kiWyti.js +1 -0
- package/dist/dashboard/public/assets/scheme-C41bIUwD.js +1 -0
- package/dist/dashboard/public/assets/sequenceDiagram-c5b8d532-B65eFcaT.js +122 -0
- package/dist/dashboard/public/assets/shell-CjFT_Tl9.js +1 -0
- package/dist/dashboard/public/assets/sieve-C3Gn_uJK.js +1 -0
- package/dist/dashboard/public/assets/simple-mode-GW_nhZxv.js +1 -0
- package/dist/dashboard/public/assets/smalltalk-CnHTOXQT.js +1 -0
- package/dist/dashboard/public/assets/solr-DehyRSwq.js +1 -0
- package/dist/dashboard/public/assets/sparql-DkYu6x3z.js +1 -0
- package/dist/dashboard/public/assets/spreadsheet-BCZA_wO0.js +1 -0
- package/dist/dashboard/public/assets/sql-D0XecflT.js +1 -0
- package/dist/dashboard/public/assets/stateDiagram-1ecb1508-BDbqu0Vl.js +1 -0
- package/dist/dashboard/public/assets/stateDiagram-v2-c2b004d7-CBHvk4b8.js +1 -0
- package/dist/dashboard/public/assets/stex-C3f8Ysf7.js +1 -0
- package/dist/dashboard/public/assets/styles-b4e223ce-CELsPqaO.js +160 -0
- package/dist/dashboard/public/assets/styles-ca3715f6-BRqMqT6F.js +207 -0
- package/dist/dashboard/public/assets/styles-d45a18b0-e8N-oLPy.js +116 -0
- package/dist/dashboard/public/assets/stylus-B533Al4x.js +1 -0
- package/dist/dashboard/public/assets/svgDrawCommon-b86b1483-vNDtmQc-.js +1 -0
- package/dist/dashboard/public/assets/swift-BzpIVaGY.js +1 -0
- package/dist/dashboard/public/assets/tcl-DVfN8rqt.js +1 -0
- package/dist/dashboard/public/assets/textile-CnDTJFAw.js +1 -0
- package/dist/dashboard/public/assets/tiddlywiki-DO-Gjzrf.js +1 -0
- package/dist/dashboard/public/assets/tiki-DGYXhP31.js +1 -0
- package/dist/dashboard/public/assets/timeline-definition-faaaa080-Dh2_A5VU.js +61 -0
- package/dist/dashboard/public/assets/toml-Bm5Em-hy.js +1 -0
- package/dist/dashboard/public/assets/troff-wAsdV37c.js +1 -0
- package/dist/dashboard/public/assets/ttcn-CfJYG6tj.js +1 -0
- package/dist/dashboard/public/assets/ttcn-cfg-B9xdYoR4.js +1 -0
- package/dist/dashboard/public/assets/turtle-B1tBg_DP.js +1 -0
- package/dist/dashboard/public/assets/vb-CmGdzxic.js +1 -0
- package/dist/dashboard/public/assets/vbscript-BuJXcnF6.js +1 -0
- package/dist/dashboard/public/assets/velocity-D8B20fx6.js +1 -0
- package/dist/dashboard/public/assets/verilog-C6RDOZhf.js +1 -0
- package/dist/dashboard/public/assets/vhdl-lSbBsy5d.js +1 -0
- package/dist/dashboard/public/assets/webidl-ZXfAyPTL.js +1 -0
- package/dist/dashboard/public/assets/xquery-DzFWVndE.js +1 -0
- package/dist/dashboard/public/assets/xychartDiagram-f5964ef8-B76v1AVF.js +7 -0
- package/dist/dashboard/public/assets/yacas-BJ4BC0dw.js +1 -0
- package/dist/dashboard/public/assets/z80-Hz9HOZM7.js +1 -0
- package/dist/dashboard/public/claude-icon-dark.svg +1 -0
- package/dist/dashboard/public/claude-icon.svg +1 -0
- package/dist/dashboard/public/index.html +16 -0
- package/dist/dashboard/settings-manager.d.ts +47 -0
- package/dist/dashboard/settings-manager.d.ts.map +1 -0
- package/dist/dashboard/settings-manager.js +180 -0
- package/dist/dashboard/settings-manager.js.map +1 -0
- package/dist/dashboard/utils.d.ts +31 -0
- package/dist/dashboard/utils.d.ts.map +1 -0
- package/dist/dashboard/utils.js +102 -0
- package/dist/dashboard/utils.js.map +1 -0
- package/dist/dashboard/watcher.d.ts +32 -0
- package/dist/dashboard/watcher.d.ts.map +1 -0
- package/dist/dashboard/watcher.js +173 -0
- package/dist/dashboard/watcher.js.map +1 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +380 -0
- package/dist/index.js.map +1 -0
- package/dist/markdown/templates/design-template.md +126 -0
- package/dist/markdown/templates/product-template.md +51 -0
- package/dist/markdown/templates/requirements-template.md +50 -0
- package/dist/markdown/templates/structure-template.md +145 -0
- package/dist/markdown/templates/tasks-template.md +100 -0
- package/dist/markdown/templates/tech-template.md +99 -0
- package/dist/markdown/templates/test-design-template.md +221 -0
- package/dist/prompts/create-spec.d.ts +3 -0
- package/dist/prompts/create-spec.d.ts.map +1 -0
- package/dist/prompts/create-spec.js +97 -0
- package/dist/prompts/create-spec.js.map +1 -0
- package/dist/prompts/create-steering-doc.d.ts +3 -0
- package/dist/prompts/create-steering-doc.d.ts.map +1 -0
- package/dist/prompts/create-steering-doc.js +75 -0
- package/dist/prompts/create-steering-doc.js.map +1 -0
- package/dist/prompts/implement-task.d.ts +3 -0
- package/dist/prompts/implement-task.d.ts.map +1 -0
- package/dist/prompts/implement-task.js +174 -0
- package/dist/prompts/implement-task.js.map +1 -0
- package/dist/prompts/index.d.ts +20 -0
- package/dist/prompts/index.d.ts.map +1 -0
- package/dist/prompts/index.js +103 -0
- package/dist/prompts/index.js.map +1 -0
- package/dist/prompts/inject-spec-workflow-guide.d.ts +3 -0
- package/dist/prompts/inject-spec-workflow-guide.d.ts.map +1 -0
- package/dist/prompts/inject-spec-workflow-guide.js +60 -0
- package/dist/prompts/inject-spec-workflow-guide.js.map +1 -0
- package/dist/prompts/inject-steering-guide.d.ts +3 -0
- package/dist/prompts/inject-steering-guide.d.ts.map +1 -0
- package/dist/prompts/inject-steering-guide.js +64 -0
- package/dist/prompts/inject-steering-guide.js.map +1 -0
- package/dist/prompts/refresh-tasks.d.ts +3 -0
- package/dist/prompts/refresh-tasks.d.ts.map +1 -0
- package/dist/prompts/refresh-tasks.js +237 -0
- package/dist/prompts/refresh-tasks.js.map +1 -0
- package/dist/prompts/spec-status.d.ts +3 -0
- package/dist/prompts/spec-status.d.ts.map +1 -0
- package/dist/prompts/spec-status.js +77 -0
- package/dist/prompts/spec-status.js.map +1 -0
- package/dist/prompts/types.d.ts +13 -0
- package/dist/prompts/types.d.ts.map +1 -0
- package/dist/prompts/types.js +2 -0
- package/dist/prompts/types.js.map +1 -0
- package/dist/server.d.ts +17 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +175 -0
- package/dist/server.js.map +1 -0
- package/dist/tools/__tests__/log-implementation-review-process.test.d.ts +2 -0
- package/dist/tools/__tests__/log-implementation-review-process.test.d.ts.map +1 -0
- package/dist/tools/__tests__/log-implementation-review-process.test.js +190 -0
- package/dist/tools/__tests__/log-implementation-review-process.test.js.map +1 -0
- package/dist/tools/__tests__/projectPath.test.d.ts +2 -0
- package/dist/tools/__tests__/projectPath.test.d.ts.map +1 -0
- package/dist/tools/__tests__/projectPath.test.js +187 -0
- package/dist/tools/__tests__/projectPath.test.js.map +1 -0
- package/dist/tools/approvals.d.ts +14 -0
- package/dist/tools/approvals.d.ts.map +1 -0
- package/dist/tools/approvals.js +505 -0
- package/dist/tools/approvals.js.map +1 -0
- package/dist/tools/index.d.ts +5 -0
- package/dist/tools/index.d.ts.map +1 -0
- package/dist/tools/index.js +52 -0
- package/dist/tools/index.js.map +1 -0
- package/dist/tools/log-implementation.d.ts +5 -0
- package/dist/tools/log-implementation.d.ts.map +1 -0
- package/dist/tools/log-implementation.js +498 -0
- package/dist/tools/log-implementation.js.map +1 -0
- package/dist/tools/spec-status.d.ts +5 -0
- package/dist/tools/spec-status.d.ts.map +1 -0
- package/dist/tools/spec-status.js +192 -0
- package/dist/tools/spec-status.js.map +1 -0
- package/dist/tools/spec-workflow-guide.d.ts +5 -0
- package/dist/tools/spec-workflow-guide.d.ts.map +1 -0
- package/dist/tools/spec-workflow-guide.js +116 -0
- package/dist/tools/spec-workflow-guide.js.map +1 -0
- package/dist/tools/steering-guide.d.ts +5 -0
- package/dist/tools/steering-guide.d.ts.map +1 -0
- package/dist/tools/steering-guide.js +192 -0
- package/dist/tools/steering-guide.js.map +1 -0
- package/dist/types.d.ts +183 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +13 -0
- package/dist/types.js.map +1 -0
- package/package.json +106 -0
|
@@ -0,0 +1,657 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
2
|
+
import { promises as fs } from 'fs';
|
|
3
|
+
import { join } from 'path';
|
|
4
|
+
import { tmpdir } from 'os';
|
|
5
|
+
import { isLocalhostAddress, getSecurityConfig, generateAllowedOrigins, DEFAULT_SECURITY_CONFIG, VITE_DEV_PORT, RateLimiter, AuditLogger, getCorsConfig, createSecurityHeadersMiddleware } from '../security-utils.js';
|
|
6
|
+
describe('security-utils', () => {
|
|
7
|
+
describe('isLocalhostAddress', () => {
|
|
8
|
+
it('should return true for "localhost"', () => {
|
|
9
|
+
expect(isLocalhostAddress('localhost')).toBe(true);
|
|
10
|
+
});
|
|
11
|
+
it('should return true for "127.0.0.1"', () => {
|
|
12
|
+
expect(isLocalhostAddress('127.0.0.1')).toBe(true);
|
|
13
|
+
});
|
|
14
|
+
it('should return true for IPv6 localhost "::1"', () => {
|
|
15
|
+
expect(isLocalhostAddress('::1')).toBe(true);
|
|
16
|
+
});
|
|
17
|
+
it('should return true for any 127.x.x.x address', () => {
|
|
18
|
+
expect(isLocalhostAddress('127.0.0.2')).toBe(true);
|
|
19
|
+
expect(isLocalhostAddress('127.1.2.3')).toBe(true);
|
|
20
|
+
expect(isLocalhostAddress('127.255.255.255')).toBe(true);
|
|
21
|
+
});
|
|
22
|
+
it('should return false for "0.0.0.0"', () => {
|
|
23
|
+
expect(isLocalhostAddress('0.0.0.0')).toBe(false);
|
|
24
|
+
});
|
|
25
|
+
it('should return false for external IP addresses', () => {
|
|
26
|
+
expect(isLocalhostAddress('192.168.1.1')).toBe(false);
|
|
27
|
+
expect(isLocalhostAddress('10.0.0.1')).toBe(false);
|
|
28
|
+
expect(isLocalhostAddress('8.8.8.8')).toBe(false);
|
|
29
|
+
});
|
|
30
|
+
it('should return false for hostnames', () => {
|
|
31
|
+
expect(isLocalhostAddress('example.com')).toBe(false);
|
|
32
|
+
expect(isLocalhostAddress('myserver')).toBe(false);
|
|
33
|
+
});
|
|
34
|
+
it('should return false for empty string', () => {
|
|
35
|
+
expect(isLocalhostAddress('')).toBe(false);
|
|
36
|
+
});
|
|
37
|
+
});
|
|
38
|
+
describe('DEFAULT_SECURITY_CONFIG', () => {
|
|
39
|
+
it('should have secure defaults', () => {
|
|
40
|
+
expect(DEFAULT_SECURITY_CONFIG.rateLimitEnabled).toBe(true);
|
|
41
|
+
expect(DEFAULT_SECURITY_CONFIG.rateLimitPerMinute).toBe(120);
|
|
42
|
+
expect(DEFAULT_SECURITY_CONFIG.auditLogEnabled).toBe(true);
|
|
43
|
+
expect(DEFAULT_SECURITY_CONFIG.auditLogRetentionDays).toBe(30);
|
|
44
|
+
expect(DEFAULT_SECURITY_CONFIG.corsEnabled).toBe(true);
|
|
45
|
+
expect(DEFAULT_SECURITY_CONFIG.allowedOrigins).toContain('http://localhost:5000');
|
|
46
|
+
expect(DEFAULT_SECURITY_CONFIG.allowedOrigins).toContain('http://127.0.0.1:5000');
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
describe('generateAllowedOrigins', () => {
|
|
50
|
+
const originalNodeEnv = process.env.NODE_ENV;
|
|
51
|
+
afterEach(() => {
|
|
52
|
+
process.env.NODE_ENV = originalNodeEnv;
|
|
53
|
+
});
|
|
54
|
+
it('should include dashboard port origins', () => {
|
|
55
|
+
const origins = generateAllowedOrigins(5000);
|
|
56
|
+
expect(origins).toContain('http://localhost:5000');
|
|
57
|
+
expect(origins).toContain('http://127.0.0.1:5000');
|
|
58
|
+
});
|
|
59
|
+
it('should include Vite dev port in non-production environments', () => {
|
|
60
|
+
process.env.NODE_ENV = 'development';
|
|
61
|
+
const origins = generateAllowedOrigins(5000);
|
|
62
|
+
expect(origins).toContain(`http://localhost:${VITE_DEV_PORT}`);
|
|
63
|
+
expect(origins).toContain(`http://127.0.0.1:${VITE_DEV_PORT}`);
|
|
64
|
+
});
|
|
65
|
+
it('should include Vite dev port when NODE_ENV is undefined', () => {
|
|
66
|
+
// This is the key test - when NODE_ENV is not set, we should still include Vite dev port
|
|
67
|
+
// because we check !== 'production' rather than === 'development'
|
|
68
|
+
delete process.env.NODE_ENV;
|
|
69
|
+
const origins = generateAllowedOrigins(5000);
|
|
70
|
+
expect(origins).toContain(`http://localhost:${VITE_DEV_PORT}`);
|
|
71
|
+
expect(origins).toContain(`http://127.0.0.1:${VITE_DEV_PORT}`);
|
|
72
|
+
});
|
|
73
|
+
it('should NOT include Vite dev port in production', () => {
|
|
74
|
+
process.env.NODE_ENV = 'production';
|
|
75
|
+
const origins = generateAllowedOrigins(5000);
|
|
76
|
+
expect(origins).not.toContain(`http://localhost:${VITE_DEV_PORT}`);
|
|
77
|
+
expect(origins).not.toContain(`http://127.0.0.1:${VITE_DEV_PORT}`);
|
|
78
|
+
});
|
|
79
|
+
it('should use custom port for dashboard origins', () => {
|
|
80
|
+
const origins = generateAllowedOrigins(3000);
|
|
81
|
+
expect(origins).toContain('http://localhost:3000');
|
|
82
|
+
expect(origins).toContain('http://127.0.0.1:3000');
|
|
83
|
+
});
|
|
84
|
+
it('should add specific non-localhost bindAddress to allowed origins', () => {
|
|
85
|
+
const origins = generateAllowedOrigins(5000, '192.168.1.100');
|
|
86
|
+
expect(origins).toContain('http://192.168.1.100:5000');
|
|
87
|
+
expect(origins).toContain('http://localhost:5000');
|
|
88
|
+
});
|
|
89
|
+
it('should NOT add 0.0.0.0 as an origin (handled via wildcard elsewhere)', () => {
|
|
90
|
+
const origins = generateAllowedOrigins(5000, '0.0.0.0');
|
|
91
|
+
expect(origins).not.toContain('http://0.0.0.0:5000');
|
|
92
|
+
});
|
|
93
|
+
it('should NOT add localhost bindAddress (already included)', () => {
|
|
94
|
+
const origins = generateAllowedOrigins(5000, '127.0.0.1');
|
|
95
|
+
const count = origins.filter(o => o === 'http://127.0.0.1:5000').length;
|
|
96
|
+
expect(count).toBe(1);
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
|
+
describe('getSecurityConfig', () => {
|
|
100
|
+
it('should return defaults when no config provided', () => {
|
|
101
|
+
const config = getSecurityConfig();
|
|
102
|
+
// In non-production, dynamic origins include Vite dev server ports
|
|
103
|
+
expect(config.rateLimitEnabled).toBe(DEFAULT_SECURITY_CONFIG.rateLimitEnabled);
|
|
104
|
+
expect(config.rateLimitPerMinute).toBe(DEFAULT_SECURITY_CONFIG.rateLimitPerMinute);
|
|
105
|
+
expect(config.auditLogEnabled).toBe(DEFAULT_SECURITY_CONFIG.auditLogEnabled);
|
|
106
|
+
expect(config.auditLogRetentionDays).toBe(DEFAULT_SECURITY_CONFIG.auditLogRetentionDays);
|
|
107
|
+
expect(config.corsEnabled).toBe(DEFAULT_SECURITY_CONFIG.corsEnabled);
|
|
108
|
+
// allowedOrigins includes default port + Vite dev port (5173) in non-production
|
|
109
|
+
expect(config.allowedOrigins).toContain('http://localhost:5000');
|
|
110
|
+
expect(config.allowedOrigins).toContain('http://127.0.0.1:5000');
|
|
111
|
+
});
|
|
112
|
+
it('should merge user config with defaults', () => {
|
|
113
|
+
const userConfig = {
|
|
114
|
+
rateLimitPerMinute: 100,
|
|
115
|
+
auditLogEnabled: false
|
|
116
|
+
};
|
|
117
|
+
const config = getSecurityConfig(userConfig);
|
|
118
|
+
expect(config.rateLimitPerMinute).toBe(100);
|
|
119
|
+
expect(config.auditLogEnabled).toBe(false);
|
|
120
|
+
// Other defaults preserved
|
|
121
|
+
expect(config.rateLimitEnabled).toBe(true);
|
|
122
|
+
expect(config.corsEnabled).toBe(true);
|
|
123
|
+
});
|
|
124
|
+
it('should allow complete override', () => {
|
|
125
|
+
const fullConfig = {
|
|
126
|
+
rateLimitEnabled: false,
|
|
127
|
+
rateLimitPerMinute: 30,
|
|
128
|
+
auditLogEnabled: false,
|
|
129
|
+
auditLogRetentionDays: 7,
|
|
130
|
+
corsEnabled: false,
|
|
131
|
+
allowedOrigins: ['http://custom:3000']
|
|
132
|
+
};
|
|
133
|
+
const config = getSecurityConfig(fullConfig);
|
|
134
|
+
expect(config).toEqual(fullConfig);
|
|
135
|
+
});
|
|
136
|
+
});
|
|
137
|
+
describe('RateLimiter', () => {
|
|
138
|
+
let rateLimiter;
|
|
139
|
+
beforeEach(() => {
|
|
140
|
+
vi.useFakeTimers();
|
|
141
|
+
rateLimiter = new RateLimiter({
|
|
142
|
+
...DEFAULT_SECURITY_CONFIG,
|
|
143
|
+
rateLimitPerMinute: 3 // Low limit for testing
|
|
144
|
+
});
|
|
145
|
+
});
|
|
146
|
+
afterEach(() => {
|
|
147
|
+
vi.useRealTimers();
|
|
148
|
+
});
|
|
149
|
+
it('should allow requests under the limit', () => {
|
|
150
|
+
const result1 = rateLimiter.checkLimit('client1');
|
|
151
|
+
const result2 = rateLimiter.checkLimit('client1');
|
|
152
|
+
const result3 = rateLimiter.checkLimit('client1');
|
|
153
|
+
expect(result1.allowed).toBe(true);
|
|
154
|
+
expect(result2.allowed).toBe(true);
|
|
155
|
+
expect(result3.allowed).toBe(true);
|
|
156
|
+
});
|
|
157
|
+
it('should block requests over the limit', () => {
|
|
158
|
+
// Use up all 3 allowed requests
|
|
159
|
+
rateLimiter.checkLimit('client1');
|
|
160
|
+
rateLimiter.checkLimit('client1');
|
|
161
|
+
rateLimiter.checkLimit('client1');
|
|
162
|
+
// 4th request should be blocked
|
|
163
|
+
const result = rateLimiter.checkLimit('client1');
|
|
164
|
+
expect(result.allowed).toBe(false);
|
|
165
|
+
expect(result.retryAfter).toBeDefined();
|
|
166
|
+
expect(result.retryAfter).toBeGreaterThan(0);
|
|
167
|
+
});
|
|
168
|
+
it('should track clients separately', () => {
|
|
169
|
+
// Use up client1's limit
|
|
170
|
+
rateLimiter.checkLimit('client1');
|
|
171
|
+
rateLimiter.checkLimit('client1');
|
|
172
|
+
rateLimiter.checkLimit('client1');
|
|
173
|
+
// client2 should still be allowed
|
|
174
|
+
const result = rateLimiter.checkLimit('client2');
|
|
175
|
+
expect(result.allowed).toBe(true);
|
|
176
|
+
});
|
|
177
|
+
it('should reset after time window', () => {
|
|
178
|
+
// Use up all requests
|
|
179
|
+
rateLimiter.checkLimit('client1');
|
|
180
|
+
rateLimiter.checkLimit('client1');
|
|
181
|
+
rateLimiter.checkLimit('client1');
|
|
182
|
+
// Should be blocked
|
|
183
|
+
expect(rateLimiter.checkLimit('client1').allowed).toBe(false);
|
|
184
|
+
// Advance time by 1 minute
|
|
185
|
+
vi.advanceTimersByTime(60001);
|
|
186
|
+
// Should be allowed again
|
|
187
|
+
expect(rateLimiter.checkLimit('client1').allowed).toBe(true);
|
|
188
|
+
});
|
|
189
|
+
it('should return true when rate limiting is disabled', () => {
|
|
190
|
+
const disabledLimiter = new RateLimiter({
|
|
191
|
+
...DEFAULT_SECURITY_CONFIG,
|
|
192
|
+
rateLimitEnabled: false,
|
|
193
|
+
rateLimitPerMinute: 1
|
|
194
|
+
});
|
|
195
|
+
// Make many requests
|
|
196
|
+
for (let i = 0; i < 100; i++) {
|
|
197
|
+
expect(disabledLimiter.checkLimit('client1').allowed).toBe(true);
|
|
198
|
+
}
|
|
199
|
+
});
|
|
200
|
+
});
|
|
201
|
+
describe('getCorsConfig', () => {
|
|
202
|
+
it('should return false when CORS is disabled', () => {
|
|
203
|
+
const config = {
|
|
204
|
+
...DEFAULT_SECURITY_CONFIG,
|
|
205
|
+
corsEnabled: false
|
|
206
|
+
};
|
|
207
|
+
expect(getCorsConfig(config)).toBe(false);
|
|
208
|
+
});
|
|
209
|
+
it('should return config object when CORS is enabled', () => {
|
|
210
|
+
const config = {
|
|
211
|
+
...DEFAULT_SECURITY_CONFIG,
|
|
212
|
+
corsEnabled: true
|
|
213
|
+
};
|
|
214
|
+
const corsConfig = getCorsConfig(config);
|
|
215
|
+
expect(corsConfig).not.toBe(false);
|
|
216
|
+
expect(typeof corsConfig).toBe('object');
|
|
217
|
+
expect(corsConfig).toHaveProperty('origin');
|
|
218
|
+
expect(corsConfig).toHaveProperty('credentials', true);
|
|
219
|
+
expect(corsConfig).toHaveProperty('methods');
|
|
220
|
+
});
|
|
221
|
+
it('should allow requests with no origin', () => {
|
|
222
|
+
const config = {
|
|
223
|
+
...DEFAULT_SECURITY_CONFIG,
|
|
224
|
+
corsEnabled: true
|
|
225
|
+
};
|
|
226
|
+
const corsConfig = getCorsConfig(config);
|
|
227
|
+
const callback = vi.fn();
|
|
228
|
+
corsConfig.origin('', callback);
|
|
229
|
+
expect(callback).toHaveBeenCalledWith(null, true);
|
|
230
|
+
});
|
|
231
|
+
it('should allow requests from allowed origins', () => {
|
|
232
|
+
const config = {
|
|
233
|
+
...DEFAULT_SECURITY_CONFIG,
|
|
234
|
+
corsEnabled: true,
|
|
235
|
+
allowedOrigins: ['http://localhost:5000', 'http://custom:3000']
|
|
236
|
+
};
|
|
237
|
+
const corsConfig = getCorsConfig(config);
|
|
238
|
+
const callback = vi.fn();
|
|
239
|
+
corsConfig.origin('http://localhost:5000', callback);
|
|
240
|
+
expect(callback).toHaveBeenCalledWith(null, true);
|
|
241
|
+
});
|
|
242
|
+
it('should reject requests from non-allowed origins', () => {
|
|
243
|
+
const config = {
|
|
244
|
+
...DEFAULT_SECURITY_CONFIG,
|
|
245
|
+
corsEnabled: true,
|
|
246
|
+
allowedOrigins: ['http://localhost:5000']
|
|
247
|
+
};
|
|
248
|
+
const corsConfig = getCorsConfig(config);
|
|
249
|
+
const callback = vi.fn();
|
|
250
|
+
corsConfig.origin('http://malicious:8080', callback);
|
|
251
|
+
expect(callback).toHaveBeenCalledWith(expect.any(Error));
|
|
252
|
+
});
|
|
253
|
+
});
|
|
254
|
+
describe('createSecurityHeadersMiddleware', () => {
|
|
255
|
+
it('should return a middleware function', () => {
|
|
256
|
+
const middleware = createSecurityHeadersMiddleware();
|
|
257
|
+
expect(typeof middleware).toBe('function');
|
|
258
|
+
});
|
|
259
|
+
it('should set security headers on reply', async () => {
|
|
260
|
+
const middleware = createSecurityHeadersMiddleware();
|
|
261
|
+
const headers = {};
|
|
262
|
+
const mockReply = {
|
|
263
|
+
header: vi.fn((name, value) => {
|
|
264
|
+
headers[name] = value;
|
|
265
|
+
return mockReply;
|
|
266
|
+
})
|
|
267
|
+
};
|
|
268
|
+
const mockRequest = {};
|
|
269
|
+
await middleware(mockRequest, mockReply);
|
|
270
|
+
expect(mockReply.header).toHaveBeenCalledWith('X-Content-Type-Options', 'nosniff');
|
|
271
|
+
expect(mockReply.header).toHaveBeenCalledWith('X-Frame-Options', 'DENY');
|
|
272
|
+
expect(mockReply.header).toHaveBeenCalledWith('X-XSS-Protection', '1; mode=block');
|
|
273
|
+
expect(mockReply.header).toHaveBeenCalledWith('Referrer-Policy', 'strict-origin-when-cross-origin');
|
|
274
|
+
expect(mockReply.header).toHaveBeenCalledWith('Content-Security-Policy', expect.any(String));
|
|
275
|
+
});
|
|
276
|
+
});
|
|
277
|
+
describe('AuditLogger', () => {
|
|
278
|
+
let testDir;
|
|
279
|
+
beforeEach(async () => {
|
|
280
|
+
testDir = join(tmpdir(), `audit-log-test-${Date.now()}`);
|
|
281
|
+
await fs.mkdir(testDir, { recursive: true });
|
|
282
|
+
});
|
|
283
|
+
afterEach(async () => {
|
|
284
|
+
try {
|
|
285
|
+
await fs.rm(testDir, { recursive: true, force: true });
|
|
286
|
+
}
|
|
287
|
+
catch {
|
|
288
|
+
// Ignore cleanup errors
|
|
289
|
+
}
|
|
290
|
+
});
|
|
291
|
+
it('should not log when audit logging is disabled', async () => {
|
|
292
|
+
const logPath = join(testDir, 'audit.log');
|
|
293
|
+
const logger = new AuditLogger({
|
|
294
|
+
...DEFAULT_SECURITY_CONFIG,
|
|
295
|
+
auditLogEnabled: false,
|
|
296
|
+
auditLogPath: logPath
|
|
297
|
+
});
|
|
298
|
+
await logger.log({
|
|
299
|
+
timestamp: new Date().toISOString(),
|
|
300
|
+
actor: '127.0.0.1',
|
|
301
|
+
action: 'GET /api/test',
|
|
302
|
+
resource: '/api/test',
|
|
303
|
+
result: 'success'
|
|
304
|
+
});
|
|
305
|
+
// File should not exist since logging is disabled
|
|
306
|
+
await expect(fs.access(logPath)).rejects.toThrow();
|
|
307
|
+
});
|
|
308
|
+
it('should initialize without throwing when disabled', async () => {
|
|
309
|
+
const logger = new AuditLogger({
|
|
310
|
+
...DEFAULT_SECURITY_CONFIG,
|
|
311
|
+
auditLogEnabled: false
|
|
312
|
+
});
|
|
313
|
+
await expect(logger.initialize()).resolves.not.toThrow();
|
|
314
|
+
});
|
|
315
|
+
it('should create log directory on initialize when enabled', async () => {
|
|
316
|
+
const logDir = join(testDir, 'logs');
|
|
317
|
+
const logPath = join(logDir, 'audit.log');
|
|
318
|
+
const logger = new AuditLogger({
|
|
319
|
+
...DEFAULT_SECURITY_CONFIG,
|
|
320
|
+
auditLogEnabled: true,
|
|
321
|
+
auditLogPath: logPath
|
|
322
|
+
});
|
|
323
|
+
await logger.initialize();
|
|
324
|
+
// Directory should exist
|
|
325
|
+
const stats = await fs.stat(logDir);
|
|
326
|
+
expect(stats.isDirectory()).toBe(true);
|
|
327
|
+
});
|
|
328
|
+
it('should write log entries as JSON lines', async () => {
|
|
329
|
+
const logPath = join(testDir, 'audit.log');
|
|
330
|
+
const logger = new AuditLogger({
|
|
331
|
+
...DEFAULT_SECURITY_CONFIG,
|
|
332
|
+
auditLogEnabled: true,
|
|
333
|
+
auditLogPath: logPath
|
|
334
|
+
});
|
|
335
|
+
await logger.initialize();
|
|
336
|
+
const entry = {
|
|
337
|
+
timestamp: '2024-01-15T10:30:00.000Z',
|
|
338
|
+
actor: '192.168.1.100',
|
|
339
|
+
action: 'POST /api/specs',
|
|
340
|
+
resource: '/api/specs',
|
|
341
|
+
result: 'success',
|
|
342
|
+
details: {
|
|
343
|
+
statusCode: 201,
|
|
344
|
+
duration: 45
|
|
345
|
+
}
|
|
346
|
+
};
|
|
347
|
+
await logger.log(entry);
|
|
348
|
+
// Read and verify log content
|
|
349
|
+
const logContent = await fs.readFile(logPath, 'utf-8');
|
|
350
|
+
const loggedEntry = JSON.parse(logContent.trim());
|
|
351
|
+
expect(loggedEntry.timestamp).toBe('2024-01-15T10:30:00.000Z');
|
|
352
|
+
expect(loggedEntry.actor).toBe('192.168.1.100');
|
|
353
|
+
expect(loggedEntry.action).toBe('POST /api/specs');
|
|
354
|
+
expect(loggedEntry.resource).toBe('/api/specs');
|
|
355
|
+
expect(loggedEntry.result).toBe('success');
|
|
356
|
+
expect(loggedEntry.details.statusCode).toBe(201);
|
|
357
|
+
expect(loggedEntry.details.duration).toBe(45);
|
|
358
|
+
});
|
|
359
|
+
it('should append multiple log entries', async () => {
|
|
360
|
+
const logPath = join(testDir, 'audit.log');
|
|
361
|
+
const logger = new AuditLogger({
|
|
362
|
+
...DEFAULT_SECURITY_CONFIG,
|
|
363
|
+
auditLogEnabled: true,
|
|
364
|
+
auditLogPath: logPath
|
|
365
|
+
});
|
|
366
|
+
await logger.initialize();
|
|
367
|
+
// Log three entries
|
|
368
|
+
await logger.log({
|
|
369
|
+
timestamp: '2024-01-15T10:00:00.000Z',
|
|
370
|
+
actor: '127.0.0.1',
|
|
371
|
+
action: 'GET /api/projects',
|
|
372
|
+
resource: '/api/projects',
|
|
373
|
+
result: 'success'
|
|
374
|
+
});
|
|
375
|
+
await logger.log({
|
|
376
|
+
timestamp: '2024-01-15T10:01:00.000Z',
|
|
377
|
+
actor: '127.0.0.1',
|
|
378
|
+
action: 'POST /api/specs',
|
|
379
|
+
resource: '/api/specs',
|
|
380
|
+
result: 'success'
|
|
381
|
+
});
|
|
382
|
+
await logger.log({
|
|
383
|
+
timestamp: '2024-01-15T10:02:00.000Z',
|
|
384
|
+
actor: '10.0.0.5',
|
|
385
|
+
action: 'DELETE /api/specs/test',
|
|
386
|
+
resource: '/api/specs/test',
|
|
387
|
+
result: 'denied'
|
|
388
|
+
});
|
|
389
|
+
// Read and verify all entries
|
|
390
|
+
const logContent = await fs.readFile(logPath, 'utf-8');
|
|
391
|
+
const lines = logContent.trim().split('\n');
|
|
392
|
+
expect(lines).toHaveLength(3);
|
|
393
|
+
const entry1 = JSON.parse(lines[0]);
|
|
394
|
+
const entry2 = JSON.parse(lines[1]);
|
|
395
|
+
const entry3 = JSON.parse(lines[2]);
|
|
396
|
+
expect(entry1.action).toBe('GET /api/projects');
|
|
397
|
+
expect(entry2.action).toBe('POST /api/specs');
|
|
398
|
+
expect(entry3.action).toBe('DELETE /api/specs/test');
|
|
399
|
+
expect(entry3.result).toBe('denied');
|
|
400
|
+
});
|
|
401
|
+
it('should log different result types correctly', async () => {
|
|
402
|
+
const logPath = join(testDir, 'audit.log');
|
|
403
|
+
const logger = new AuditLogger({
|
|
404
|
+
...DEFAULT_SECURITY_CONFIG,
|
|
405
|
+
auditLogEnabled: true,
|
|
406
|
+
auditLogPath: logPath
|
|
407
|
+
});
|
|
408
|
+
await logger.initialize();
|
|
409
|
+
// Test all result types
|
|
410
|
+
await logger.log({
|
|
411
|
+
timestamp: new Date().toISOString(),
|
|
412
|
+
actor: '127.0.0.1',
|
|
413
|
+
action: 'GET /api/test',
|
|
414
|
+
resource: '/api/test',
|
|
415
|
+
result: 'success'
|
|
416
|
+
});
|
|
417
|
+
await logger.log({
|
|
418
|
+
timestamp: new Date().toISOString(),
|
|
419
|
+
actor: '127.0.0.1',
|
|
420
|
+
action: 'GET /api/error',
|
|
421
|
+
resource: '/api/error',
|
|
422
|
+
result: 'failure'
|
|
423
|
+
});
|
|
424
|
+
await logger.log({
|
|
425
|
+
timestamp: new Date().toISOString(),
|
|
426
|
+
actor: '127.0.0.1',
|
|
427
|
+
action: 'GET /api/protected',
|
|
428
|
+
resource: '/api/protected',
|
|
429
|
+
result: 'denied'
|
|
430
|
+
});
|
|
431
|
+
const logContent = await fs.readFile(logPath, 'utf-8');
|
|
432
|
+
const lines = logContent.trim().split('\n');
|
|
433
|
+
expect(JSON.parse(lines[0]).result).toBe('success');
|
|
434
|
+
expect(JSON.parse(lines[1]).result).toBe('failure');
|
|
435
|
+
expect(JSON.parse(lines[2]).result).toBe('denied');
|
|
436
|
+
});
|
|
437
|
+
it('should use workspace root path when auditLogPath not specified', async () => {
|
|
438
|
+
const workspaceRoot = testDir;
|
|
439
|
+
const logger = new AuditLogger({
|
|
440
|
+
...DEFAULT_SECURITY_CONFIG,
|
|
441
|
+
auditLogEnabled: true
|
|
442
|
+
// No auditLogPath specified
|
|
443
|
+
}, workspaceRoot);
|
|
444
|
+
await logger.initialize();
|
|
445
|
+
await logger.log({
|
|
446
|
+
timestamp: new Date().toISOString(),
|
|
447
|
+
actor: '127.0.0.1',
|
|
448
|
+
action: 'GET /test',
|
|
449
|
+
resource: '/test',
|
|
450
|
+
result: 'success'
|
|
451
|
+
});
|
|
452
|
+
// Should have created log at workspaceRoot/.spec-workflow/audit.log
|
|
453
|
+
const expectedLogPath = join(workspaceRoot, '.spec-workflow', 'audit.log');
|
|
454
|
+
const logContent = await fs.readFile(expectedLogPath, 'utf-8');
|
|
455
|
+
const entry = JSON.parse(logContent.trim());
|
|
456
|
+
expect(entry.action).toBe('GET /test');
|
|
457
|
+
});
|
|
458
|
+
it('should include optional details in log entries', async () => {
|
|
459
|
+
const logPath = join(testDir, 'audit.log');
|
|
460
|
+
const logger = new AuditLogger({
|
|
461
|
+
...DEFAULT_SECURITY_CONFIG,
|
|
462
|
+
auditLogEnabled: true,
|
|
463
|
+
auditLogPath: logPath
|
|
464
|
+
});
|
|
465
|
+
await logger.initialize();
|
|
466
|
+
await logger.log({
|
|
467
|
+
timestamp: new Date().toISOString(),
|
|
468
|
+
actor: '192.168.1.50',
|
|
469
|
+
action: 'PUT /api/specs/feature/tasks',
|
|
470
|
+
resource: '/api/specs/feature/tasks',
|
|
471
|
+
result: 'success',
|
|
472
|
+
details: {
|
|
473
|
+
statusCode: 200,
|
|
474
|
+
duration: 123,
|
|
475
|
+
userAgent: 'Mozilla/5.0 Test Browser',
|
|
476
|
+
customField: 'custom-value'
|
|
477
|
+
}
|
|
478
|
+
});
|
|
479
|
+
const logContent = await fs.readFile(logPath, 'utf-8');
|
|
480
|
+
const entry = JSON.parse(logContent.trim());
|
|
481
|
+
expect(entry.details).toBeDefined();
|
|
482
|
+
expect(entry.details.statusCode).toBe(200);
|
|
483
|
+
expect(entry.details.duration).toBe(123);
|
|
484
|
+
expect(entry.details.userAgent).toBe('Mozilla/5.0 Test Browser');
|
|
485
|
+
expect(entry.details.customField).toBe('custom-value');
|
|
486
|
+
});
|
|
487
|
+
describe('middleware', () => {
|
|
488
|
+
it('should return a middleware function', () => {
|
|
489
|
+
const logger = new AuditLogger({
|
|
490
|
+
...DEFAULT_SECURITY_CONFIG,
|
|
491
|
+
auditLogEnabled: true
|
|
492
|
+
});
|
|
493
|
+
const middleware = logger.middleware();
|
|
494
|
+
expect(typeof middleware).toBe('function');
|
|
495
|
+
});
|
|
496
|
+
it('should register then callback on reply for post-response logging', async () => {
|
|
497
|
+
const logPath = join(testDir, 'audit.log');
|
|
498
|
+
const logger = new AuditLogger({
|
|
499
|
+
...DEFAULT_SECURITY_CONFIG,
|
|
500
|
+
auditLogEnabled: true,
|
|
501
|
+
auditLogPath: logPath
|
|
502
|
+
});
|
|
503
|
+
await logger.initialize();
|
|
504
|
+
const middleware = logger.middleware();
|
|
505
|
+
let thenCallback = null;
|
|
506
|
+
const mockReply = {
|
|
507
|
+
statusCode: 200,
|
|
508
|
+
then: vi.fn((onFulfilled, _onRejected) => {
|
|
509
|
+
thenCallback = onFulfilled;
|
|
510
|
+
})
|
|
511
|
+
};
|
|
512
|
+
const mockRequest = {
|
|
513
|
+
ip: '10.0.0.1',
|
|
514
|
+
method: 'GET',
|
|
515
|
+
url: '/api/projects/list',
|
|
516
|
+
headers: {
|
|
517
|
+
'user-agent': 'Test Agent/1.0'
|
|
518
|
+
}
|
|
519
|
+
};
|
|
520
|
+
await middleware(mockRequest, mockReply);
|
|
521
|
+
// Verify reply.then was called
|
|
522
|
+
expect(mockReply.then).toHaveBeenCalled();
|
|
523
|
+
expect(thenCallback).not.toBeNull();
|
|
524
|
+
// Trigger the then callback to actually log
|
|
525
|
+
await thenCallback();
|
|
526
|
+
// Wait for async log write to complete
|
|
527
|
+
await new Promise(resolve => setTimeout(resolve, 50));
|
|
528
|
+
// Verify log was written
|
|
529
|
+
const logContent = await fs.readFile(logPath, 'utf-8');
|
|
530
|
+
const entry = JSON.parse(logContent.trim());
|
|
531
|
+
expect(entry.actor).toBe('10.0.0.1');
|
|
532
|
+
expect(entry.action).toBe('GET /api/projects/list');
|
|
533
|
+
expect(entry.resource).toBe('/api/projects/list');
|
|
534
|
+
expect(entry.result).toBe('success');
|
|
535
|
+
expect(entry.details.statusCode).toBe(200);
|
|
536
|
+
expect(entry.details.userAgent).toBe('Test Agent/1.0');
|
|
537
|
+
});
|
|
538
|
+
it('should log "denied" result for 401 status code', async () => {
|
|
539
|
+
const logPath = join(testDir, 'audit.log');
|
|
540
|
+
const logger = new AuditLogger({
|
|
541
|
+
...DEFAULT_SECURITY_CONFIG,
|
|
542
|
+
auditLogEnabled: true,
|
|
543
|
+
auditLogPath: logPath
|
|
544
|
+
});
|
|
545
|
+
await logger.initialize();
|
|
546
|
+
const middleware = logger.middleware();
|
|
547
|
+
let thenCallback = null;
|
|
548
|
+
const mockReply = {
|
|
549
|
+
statusCode: 401,
|
|
550
|
+
then: vi.fn((onFulfilled, _onRejected) => {
|
|
551
|
+
thenCallback = onFulfilled;
|
|
552
|
+
})
|
|
553
|
+
};
|
|
554
|
+
const mockRequest = {
|
|
555
|
+
ip: '192.168.1.1',
|
|
556
|
+
method: 'GET',
|
|
557
|
+
url: '/api/admin',
|
|
558
|
+
headers: {}
|
|
559
|
+
};
|
|
560
|
+
await middleware(mockRequest, mockReply);
|
|
561
|
+
await thenCallback();
|
|
562
|
+
await new Promise(resolve => setTimeout(resolve, 50));
|
|
563
|
+
const logContent = await fs.readFile(logPath, 'utf-8');
|
|
564
|
+
const entry = JSON.parse(logContent.trim());
|
|
565
|
+
expect(entry.result).toBe('denied');
|
|
566
|
+
});
|
|
567
|
+
it('should log "denied" result for 403 status code', async () => {
|
|
568
|
+
const logPath = join(testDir, 'audit.log');
|
|
569
|
+
const logger = new AuditLogger({
|
|
570
|
+
...DEFAULT_SECURITY_CONFIG,
|
|
571
|
+
auditLogEnabled: true,
|
|
572
|
+
auditLogPath: logPath
|
|
573
|
+
});
|
|
574
|
+
await logger.initialize();
|
|
575
|
+
const middleware = logger.middleware();
|
|
576
|
+
let thenCallback = null;
|
|
577
|
+
const mockReply = {
|
|
578
|
+
statusCode: 403,
|
|
579
|
+
then: vi.fn((onFulfilled, _onRejected) => {
|
|
580
|
+
thenCallback = onFulfilled;
|
|
581
|
+
})
|
|
582
|
+
};
|
|
583
|
+
const mockRequest = {
|
|
584
|
+
ip: '192.168.1.1',
|
|
585
|
+
method: 'DELETE',
|
|
586
|
+
url: '/api/protected-resource',
|
|
587
|
+
headers: {}
|
|
588
|
+
};
|
|
589
|
+
await middleware(mockRequest, mockReply);
|
|
590
|
+
await thenCallback();
|
|
591
|
+
await new Promise(resolve => setTimeout(resolve, 50));
|
|
592
|
+
const logContent = await fs.readFile(logPath, 'utf-8');
|
|
593
|
+
const entry = JSON.parse(logContent.trim());
|
|
594
|
+
expect(entry.result).toBe('denied');
|
|
595
|
+
});
|
|
596
|
+
it('should log "failure" result for 500 status code', async () => {
|
|
597
|
+
const logPath = join(testDir, 'audit.log');
|
|
598
|
+
const logger = new AuditLogger({
|
|
599
|
+
...DEFAULT_SECURITY_CONFIG,
|
|
600
|
+
auditLogEnabled: true,
|
|
601
|
+
auditLogPath: logPath
|
|
602
|
+
});
|
|
603
|
+
await logger.initialize();
|
|
604
|
+
const middleware = logger.middleware();
|
|
605
|
+
let thenCallback = null;
|
|
606
|
+
const mockReply = {
|
|
607
|
+
statusCode: 500,
|
|
608
|
+
then: vi.fn((onFulfilled, _onRejected) => {
|
|
609
|
+
thenCallback = onFulfilled;
|
|
610
|
+
})
|
|
611
|
+
};
|
|
612
|
+
const mockRequest = {
|
|
613
|
+
ip: '127.0.0.1',
|
|
614
|
+
method: 'POST',
|
|
615
|
+
url: '/api/specs',
|
|
616
|
+
headers: {}
|
|
617
|
+
};
|
|
618
|
+
await middleware(mockRequest, mockReply);
|
|
619
|
+
await thenCallback();
|
|
620
|
+
await new Promise(resolve => setTimeout(resolve, 50));
|
|
621
|
+
const logContent = await fs.readFile(logPath, 'utf-8');
|
|
622
|
+
const entry = JSON.parse(logContent.trim());
|
|
623
|
+
expect(entry.result).toBe('failure');
|
|
624
|
+
});
|
|
625
|
+
it('should use "unknown" for missing IP address', async () => {
|
|
626
|
+
const logPath = join(testDir, 'audit.log');
|
|
627
|
+
const logger = new AuditLogger({
|
|
628
|
+
...DEFAULT_SECURITY_CONFIG,
|
|
629
|
+
auditLogEnabled: true,
|
|
630
|
+
auditLogPath: logPath
|
|
631
|
+
});
|
|
632
|
+
await logger.initialize();
|
|
633
|
+
const middleware = logger.middleware();
|
|
634
|
+
let thenCallback = null;
|
|
635
|
+
const mockReply = {
|
|
636
|
+
statusCode: 200,
|
|
637
|
+
then: vi.fn((onFulfilled, _onRejected) => {
|
|
638
|
+
thenCallback = onFulfilled;
|
|
639
|
+
})
|
|
640
|
+
};
|
|
641
|
+
const mockRequest = {
|
|
642
|
+
// No ip property
|
|
643
|
+
method: 'GET',
|
|
644
|
+
url: '/api/test',
|
|
645
|
+
headers: {}
|
|
646
|
+
};
|
|
647
|
+
await middleware(mockRequest, mockReply);
|
|
648
|
+
await thenCallback();
|
|
649
|
+
await new Promise(resolve => setTimeout(resolve, 50));
|
|
650
|
+
const logContent = await fs.readFile(logPath, 'utf-8');
|
|
651
|
+
const entry = JSON.parse(logContent.trim());
|
|
652
|
+
expect(entry.actor).toBe('unknown');
|
|
653
|
+
});
|
|
654
|
+
});
|
|
655
|
+
});
|
|
656
|
+
});
|
|
657
|
+
//# sourceMappingURL=security-utils.test.js.map
|