shieldcortex 4.31.2 → 4.32.1
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/README.md +78 -2
- package/dashboard/.next/standalone/dashboard/.next/BUILD_ID +1 -1
- package/dashboard/.next/standalone/dashboard/.next/build-manifest.json +2 -2
- package/dashboard/.next/standalone/dashboard/.next/prerender-manifest.json +3 -3
- package/dashboard/.next/standalone/dashboard/.next/server/app/_global-error.html +2 -2
- package/dashboard/.next/standalone/dashboard/.next/server/app/_global-error.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/_not-found.html +2 -2
- package/dashboard/.next/standalone/dashboard/.next/server/app/_not-found.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/admin.html +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/admin.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/admin.segments/!KGRhc2hib2FyZCk/admin/__PAGE__.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/admin.segments/!KGRhc2hib2FyZCk/admin.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/admin.segments/!KGRhc2hib2FyZCk.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/admin.segments/_full.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/admin.segments/_head.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/admin.segments/_index.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/admin.segments/_tree.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/cloud.html +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/cloud.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/cloud.segments/!KGRhc2hib2FyZCk/cloud/__PAGE__.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/cloud.segments/!KGRhc2hib2FyZCk/cloud.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/cloud.segments/!KGRhc2hib2FyZCk.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/cloud.segments/_full.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/cloud.segments/_head.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/cloud.segments/_index.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/cloud.segments/_tree.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/index.html +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/index.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/index.segments/_full.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/index.segments/_head.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/index.segments/_index.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/index.segments/_tree.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/memory/capture.html +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/memory/capture.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/memory/capture.segments/!KGRhc2hib2FyZCk/memory/capture/__PAGE__.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/memory/capture.segments/!KGRhc2hib2FyZCk/memory/capture.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/memory/capture.segments/!KGRhc2hib2FyZCk/memory.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/memory/capture.segments/!KGRhc2hib2FyZCk.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/memory/capture.segments/_full.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/memory/capture.segments/_head.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/memory/capture.segments/_index.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/memory/capture.segments/_tree.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/memory/graph.html +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/memory/graph.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/memory/graph.segments/!KGRhc2hib2FyZCk/memory/graph/__PAGE__.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/memory/graph.segments/!KGRhc2hib2FyZCk/memory/graph.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/memory/graph.segments/!KGRhc2hib2FyZCk/memory.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/memory/graph.segments/!KGRhc2hib2FyZCk.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/memory/graph.segments/_full.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/memory/graph.segments/_head.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/memory/graph.segments/_index.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/memory/graph.segments/_tree.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/memory/recall.html +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/memory/recall.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/memory/recall.segments/!KGRhc2hib2FyZCk/memory/recall/__PAGE__.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/memory/recall.segments/!KGRhc2hib2FyZCk/memory/recall.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/memory/recall.segments/!KGRhc2hib2FyZCk/memory.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/memory/recall.segments/!KGRhc2hib2FyZCk.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/memory/recall.segments/_full.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/memory/recall.segments/_head.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/memory/recall.segments/_index.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/memory/recall.segments/_tree.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/memory/replay.html +2 -2
- package/dashboard/.next/standalone/dashboard/.next/server/app/memory/replay.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/memory/replay.segments/!KGRhc2hib2FyZCk/memory/replay/__PAGE__.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/memory/replay.segments/!KGRhc2hib2FyZCk/memory/replay.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/memory/replay.segments/!KGRhc2hib2FyZCk/memory.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/memory/replay.segments/!KGRhc2hib2FyZCk.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/memory/replay.segments/_full.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/memory/replay.segments/_head.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/memory/replay.segments/_index.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/memory/replay.segments/_tree.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/memory/review.html +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/memory/review.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/memory/review.segments/!KGRhc2hib2FyZCk/memory/review/__PAGE__.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/memory/review.segments/!KGRhc2hib2FyZCk/memory/review.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/memory/review.segments/!KGRhc2hib2FyZCk/memory.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/memory/review.segments/!KGRhc2hib2FyZCk.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/memory/review.segments/_full.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/memory/review.segments/_head.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/memory/review.segments/_index.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/memory/review.segments/_tree.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/memory/timeline.html +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/memory/timeline.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/memory/timeline.segments/!KGRhc2hib2FyZCk/memory/timeline/__PAGE__.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/memory/timeline.segments/!KGRhc2hib2FyZCk/memory/timeline.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/memory/timeline.segments/!KGRhc2hib2FyZCk/memory.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/memory/timeline.segments/!KGRhc2hib2FyZCk.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/memory/timeline.segments/_full.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/memory/timeline.segments/_head.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/memory/timeline.segments/_index.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/memory/timeline.segments/_tree.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/memory.html +2 -2
- package/dashboard/.next/standalone/dashboard/.next/server/app/memory.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/memory.segments/!KGRhc2hib2FyZCk/memory/__PAGE__.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/memory.segments/!KGRhc2hib2FyZCk/memory.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/memory.segments/!KGRhc2hib2FyZCk.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/memory.segments/_full.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/memory.segments/_head.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/memory.segments/_index.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/memory.segments/_tree.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/overview.html +2 -2
- package/dashboard/.next/standalone/dashboard/.next/server/app/overview.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/overview.segments/!KGRhc2hib2FyZCk/overview/__PAGE__.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/overview.segments/!KGRhc2hib2FyZCk/overview.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/overview.segments/!KGRhc2hib2FyZCk.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/overview.segments/_full.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/overview.segments/_head.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/overview.segments/_index.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/overview.segments/_tree.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/protection/audit.html +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/protection/audit.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/protection/audit.segments/!KGRhc2hib2FyZCk/protection/audit/__PAGE__.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/protection/audit.segments/!KGRhc2hib2FyZCk/protection/audit.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/protection/audit.segments/!KGRhc2hib2FyZCk/protection.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/protection/audit.segments/!KGRhc2hib2FyZCk.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/protection/audit.segments/_full.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/protection/audit.segments/_head.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/protection/audit.segments/_index.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/protection/audit.segments/_tree.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/protection/intercepts.html +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/protection/intercepts.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/protection/intercepts.segments/!KGRhc2hib2FyZCk/protection/intercepts/__PAGE__.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/protection/intercepts.segments/!KGRhc2hib2FyZCk/protection/intercepts.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/protection/intercepts.segments/!KGRhc2hib2FyZCk/protection.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/protection/intercepts.segments/!KGRhc2hib2FyZCk.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/protection/intercepts.segments/_full.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/protection/intercepts.segments/_head.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/protection/intercepts.segments/_index.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/protection/intercepts.segments/_tree.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/protection/iron-dome.html +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/protection/iron-dome.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/protection/iron-dome.segments/!KGRhc2hib2FyZCk/protection/iron-dome/__PAGE__.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/protection/iron-dome.segments/!KGRhc2hib2FyZCk/protection/iron-dome.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/protection/iron-dome.segments/!KGRhc2hib2FyZCk/protection.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/protection/iron-dome.segments/!KGRhc2hib2FyZCk.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/protection/iron-dome.segments/_full.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/protection/iron-dome.segments/_head.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/protection/iron-dome.segments/_index.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/protection/iron-dome.segments/_tree.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/protection/policies.html +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/protection/policies.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/protection/policies.segments/!KGRhc2hib2FyZCk/protection/policies/__PAGE__.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/protection/policies.segments/!KGRhc2hib2FyZCk/protection/policies.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/protection/policies.segments/!KGRhc2hib2FyZCk/protection.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/protection/policies.segments/!KGRhc2hib2FyZCk.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/protection/policies.segments/_full.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/protection/policies.segments/_head.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/protection/policies.segments/_index.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/protection/policies.segments/_tree.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/protection/quarantine.html +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/protection/quarantine.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/protection/quarantine.segments/!KGRhc2hib2FyZCk/protection/quarantine/__PAGE__.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/protection/quarantine.segments/!KGRhc2hib2FyZCk/protection/quarantine.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/protection/quarantine.segments/!KGRhc2hib2FyZCk/protection.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/protection/quarantine.segments/!KGRhc2hib2FyZCk.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/protection/quarantine.segments/_full.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/protection/quarantine.segments/_head.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/protection/quarantine.segments/_index.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/protection/quarantine.segments/_tree.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/protection.html +2 -2
- package/dashboard/.next/standalone/dashboard/.next/server/app/protection.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/protection.segments/!KGRhc2hib2FyZCk/protection/__PAGE__.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/protection.segments/!KGRhc2hib2FyZCk/protection.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/protection.segments/!KGRhc2hib2FyZCk.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/protection.segments/_full.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/protection.segments/_head.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/protection.segments/_index.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/protection.segments/_tree.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/settings.html +2 -2
- package/dashboard/.next/standalone/dashboard/.next/server/app/settings.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/settings.segments/!KGRhc2hib2FyZCk/settings/__PAGE__.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/settings.segments/!KGRhc2hib2FyZCk/settings.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/settings.segments/!KGRhc2hib2FyZCk.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/settings.segments/_full.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/settings.segments/_head.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/settings.segments/_index.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/settings.segments/_tree.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/supply-chain/xray.html +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/supply-chain/xray.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/supply-chain/xray.segments/!KGRhc2hib2FyZCk/supply-chain/xray/__PAGE__.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/supply-chain/xray.segments/!KGRhc2hib2FyZCk/supply-chain/xray.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/supply-chain/xray.segments/!KGRhc2hib2FyZCk/supply-chain.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/supply-chain/xray.segments/!KGRhc2hib2FyZCk.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/supply-chain/xray.segments/_full.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/supply-chain/xray.segments/_head.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/supply-chain/xray.segments/_index.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/supply-chain/xray.segments/_tree.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/supply-chain.html +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/supply-chain.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/supply-chain.segments/!KGRhc2hib2FyZCk/supply-chain/__PAGE__.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/supply-chain.segments/!KGRhc2hib2FyZCk/supply-chain.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/supply-chain.segments/!KGRhc2hib2FyZCk.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/supply-chain.segments/_full.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/supply-chain.segments/_head.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/supply-chain.segments/_index.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/supply-chain.segments/_tree.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/xray.html +2 -2
- package/dashboard/.next/standalone/dashboard/.next/server/app/xray.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/xray.segments/!KGRhc2hib2FyZCk/xray/__PAGE__.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/xray.segments/!KGRhc2hib2FyZCk/xray.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/xray.segments/!KGRhc2hib2FyZCk.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/xray.segments/_full.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/xray.segments/_head.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/xray.segments/_index.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/xray.segments/_tree.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/pages/404.html +2 -2
- package/dashboard/.next/standalone/dashboard/.next/server/pages/500.html +2 -2
- package/dashboard/.next/standalone/dashboard/.next/server/server-reference-manifest.js +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/server-reference-manifest.json +1 -1
- package/dist/api/control.d.ts +2 -0
- package/dist/api/control.js +119 -2
- package/dist/api/routes/memories.js +19 -14
- package/dist/api/routes/system.js +2 -3
- package/dist/api/visualization-server.d.ts +13 -1
- package/dist/api/visualization-server.js +57 -1
- package/dist/audit/env-scanner.js +5 -2
- package/dist/audit/index.d.ts +4 -1
- package/dist/audit/index.js +2 -1
- package/dist/audit/mcp-config-scanner.d.ts +23 -0
- package/dist/audit/mcp-config-scanner.js +110 -0
- package/dist/audit/mcp-tools-scanner.d.ts +112 -0
- package/dist/audit/mcp-tools-scanner.js +299 -0
- package/dist/cli/audit.d.ts +1 -0
- package/dist/cli/audit.js +12 -1
- package/dist/cli/doctor.js +4 -1
- package/dist/cli/mcp.d.ts +13 -0
- package/dist/cli/mcp.js +0 -0
- package/dist/cli/remember.d.ts +75 -0
- package/dist/cli/remember.js +195 -0
- package/dist/cli/repair.d.ts +8 -0
- package/dist/cli/repair.js +34 -0
- package/dist/cli/update.js +34 -0
- package/dist/cloud/config.d.ts +23 -1
- package/dist/cloud/config.js +453 -193
- package/dist/cloud/quarantine-sync.d.ts +12 -2
- package/dist/cloud/quarantine-sync.js +28 -6
- package/dist/cloud/sync-queue.d.ts +21 -2
- package/dist/cloud/sync-queue.js +124 -29
- package/dist/database/better-sqlite3-guard.d.ts +21 -2
- package/dist/database/better-sqlite3-guard.js +29 -5
- package/dist/database/init.js +68 -16
- package/dist/database/inline-schema.js +35 -1
- package/dist/database/migrations.js +104 -8
- package/dist/database/schema.sql +39 -1
- package/dist/defence/audit/queries.d.ts +10 -2
- package/dist/defence/audit/queries.js +30 -4
- package/dist/defence/audit/retention.d.ts +50 -0
- package/dist/defence/audit/retention.js +161 -0
- package/dist/defence/credential-leak/entropy.d.ts +11 -0
- package/dist/defence/credential-leak/entropy.js +27 -0
- package/dist/defence/credential-leak/index.js +27 -1
- package/dist/defence/credential-leak/patterns.d.ts +9 -0
- package/dist/defence/credential-leak/patterns.js +21 -0
- package/dist/defence/custom-patterns/store.js +8 -1
- package/dist/defence/custom-rules/store.d.ts +18 -0
- package/dist/defence/custom-rules/store.js +63 -0
- package/dist/defence/firewall/confusables.d.ts +30 -0
- package/dist/defence/firewall/confusables.js +87 -0
- package/dist/defence/firewall/encoding-detector.js +23 -9
- package/dist/defence/firewall/index.d.ts +11 -1
- package/dist/defence/firewall/index.js +34 -1
- package/dist/defence/firewall/instruction-detector.js +18 -7
- package/dist/defence/firewall/markdown-image-detector.d.ts +34 -0
- package/dist/defence/firewall/markdown-image-detector.js +83 -0
- package/dist/defence/fragmentation/entity-extractor.js +17 -6
- package/dist/defence/index.d.ts +5 -0
- package/dist/defence/index.js +8 -0
- package/dist/defence/iron-dome/index.js +7 -1
- package/dist/defence/pipeline.js +62 -10
- package/dist/defence/scan-windows.d.ts +41 -0
- package/dist/defence/scan-windows.js +61 -0
- package/dist/defence/semantic/attack-corpus.d.ts +22 -0
- package/dist/defence/semantic/attack-corpus.js +75 -0
- package/dist/defence/semantic/index.d.ts +67 -0
- package/dist/defence/semantic/index.js +138 -0
- package/dist/defence/skill-scanner/deep-scan.js +35 -15
- package/dist/defence/skill-scanner/patterns.d.ts +1 -1
- package/dist/defence/skill-scanner/patterns.js +8 -7
- package/dist/defence/tool-response-scanner.d.ts +21 -5
- package/dist/defence/tool-response-scanner.js +111 -22
- package/dist/defence/types.d.ts +11 -1
- package/dist/index.d.ts +29 -0
- package/dist/index.js +112 -21
- package/dist/memory/consolidate.js +1 -1
- package/dist/memory/decay.js +3 -1
- package/dist/memory/embedding.d.ts +18 -2
- package/dist/memory/embedding.js +32 -11
- package/dist/memory/expiry.js +1 -1
- package/dist/memory/search-recall.js +107 -49
- package/dist/memory/search.d.ts +19 -3
- package/dist/memory/search.js +25 -10
- package/dist/memory/store.d.ts +13 -2
- package/dist/memory/store.js +115 -11
- package/dist/scan-only.d.ts +64 -0
- package/dist/scan-only.js +173 -0
- package/dist/server.d.ts +5 -0
- package/dist/server.js +6 -4
- package/dist/setup/claude-md.js +39 -34
- package/dist/setup/codex.js +9 -2
- package/dist/setup/copilot.js +160 -47
- package/dist/setup/json-config.d.ts +99 -0
- package/dist/setup/json-config.js +167 -0
- package/dist/setup/migrate.js +1 -1
- package/dist/setup/native-binding.d.ts +75 -0
- package/dist/setup/native-binding.js +146 -0
- package/dist/setup/settings-hooks.js +8 -13
- package/dist/setup/uninstall.js +1 -21
- package/dist/tools/context.d.ts +8 -8
- package/dist/tools/forget.d.ts +9 -8
- package/dist/tools/forget.js +17 -4
- package/dist/tools/recall.d.ts +13 -13
- package/dist/tools/remember.d.ts +16 -16
- package/dist/tools/remember.js +19 -8
- package/dist/worker/brain-worker.d.ts +1 -0
- package/dist/worker/brain-worker.js +79 -16
- package/dist/worker/types.d.ts +8 -0
- package/dist/worker/types.js +8 -0
- package/dist/xray/dir-scanner.d.ts +18 -0
- package/dist/xray/dir-scanner.js +23 -1
- package/dist/xray/file-scanner.js +16 -1
- package/dist/xray/findings-store.js +9 -1
- package/dist/xray/index.d.ts +2 -0
- package/dist/xray/index.js +10 -1
- package/dist/xray/npm-inspector.d.ts +31 -0
- package/dist/xray/npm-inspector.js +135 -29
- package/dist/xray/patterns.d.ts +1 -1
- package/dist/xray/patterns.js +20 -23
- package/dist/xray/sarif.d.ts +78 -0
- package/dist/xray/sarif.js +166 -0
- package/dist/xray/watch.d.ts +1 -0
- package/dist/xray/watch.js +10 -1
- package/hooks/openclaw/cortex-memory/handler.ts +122 -18
- package/hooks/openclaw/cortex-memory/runtime.mjs +10 -4
- package/package.json +10 -3
- package/scripts/postinstall.mjs +8 -3
- package/dist/memory/embedding-cache.d.ts +0 -20
- package/dist/memory/embedding-cache.js +0 -91
- /package/dashboard/.next/standalone/dashboard/.next/static/{_j4TeMpss-w79QtNNWqZw → tjJ3X8xQ-2_WQTPGF3zCA}/_buildManifest.js +0 -0
- /package/dashboard/.next/standalone/dashboard/.next/static/{_j4TeMpss-w79QtNNWqZw → tjJ3X8xQ-2_WQTPGF3zCA}/_clientMiddlewareManifest.json +0 -0
- /package/dashboard/.next/standalone/dashboard/.next/static/{_j4TeMpss-w79QtNNWqZw → tjJ3X8xQ-2_WQTPGF3zCA}/_ssgManifest.js +0 -0
|
@@ -23,7 +23,6 @@
|
|
|
23
23
|
* databases that have only partially run them.
|
|
24
24
|
*/
|
|
25
25
|
import { randomUUID } from 'crypto';
|
|
26
|
-
import { seedDefaultFirewallRules } from './seed-firewall-rules.js';
|
|
27
26
|
/**
|
|
28
27
|
* Log unexpected errors from idempotent DDL operations (v4.26.0).
|
|
29
28
|
*
|
|
@@ -473,13 +472,10 @@ export function runMigrations(database) {
|
|
|
473
472
|
logIfUnexpectedDdlError(err, 'firewall_rules.built_in column add');
|
|
474
473
|
// (pipeline handles missing column gracefully; this is informational)
|
|
475
474
|
}
|
|
476
|
-
//
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
catch {
|
|
481
|
-
// Seeder runs idempotently; failures here should never block startup.
|
|
482
|
-
}
|
|
475
|
+
// Built-in firewall rule seeding now lives in `initDatabase()` AFTER the
|
|
476
|
+
// schema is applied — `runMigrations()` returns early on fresh databases
|
|
477
|
+
// (no `memories` table), so a seed call here never fired for new installs.
|
|
478
|
+
// The init-level call is idempotent and covers both fresh and existing DBs.
|
|
483
479
|
// Migration: session_events.content_hash + dedupe UNIQUE index (v4.17).
|
|
484
480
|
// DBs created between the foundation commit and the importer commit have
|
|
485
481
|
// session_events but no content_hash column. ALTER + idempotent index
|
|
@@ -654,4 +650,104 @@ export function runMigrations(database) {
|
|
|
654
650
|
// Best-effort marker; the stderr line above carries the signal.
|
|
655
651
|
}
|
|
656
652
|
}
|
|
653
|
+
// Migration: control_state table (cross-process kill-switch / pause).
|
|
654
|
+
//
|
|
655
|
+
// Phase 2 hardening: the kill switch / pause mode used to live only in
|
|
656
|
+
// module-level memory, so a dashboard (API process) activation never reached
|
|
657
|
+
// the separate MCP server process. control_state is the single-row source of
|
|
658
|
+
// truth both processes read/write; api/control.ts persists to and refreshes
|
|
659
|
+
// from it. Existing DBs that predate the table get it here (schema.sql /
|
|
660
|
+
// inline-schema.ts cover fresh installs).
|
|
661
|
+
try {
|
|
662
|
+
database.exec(`
|
|
663
|
+
CREATE TABLE IF NOT EXISTS control_state (
|
|
664
|
+
id INTEGER PRIMARY KEY CHECK (id = 1),
|
|
665
|
+
mode TEXT NOT NULL DEFAULT 'active' CHECK (mode IN ('active','paused','kill_switch')),
|
|
666
|
+
meta_json TEXT,
|
|
667
|
+
updated_at TEXT NOT NULL
|
|
668
|
+
);
|
|
669
|
+
`);
|
|
670
|
+
}
|
|
671
|
+
catch (err) {
|
|
672
|
+
logIfUnexpectedDdlError(err, 'control_state');
|
|
673
|
+
}
|
|
674
|
+
// Migration: audit_aggregates table (Phase 8a — bound defence_audit growth).
|
|
675
|
+
//
|
|
676
|
+
// defence_audit grows on every pipeline run with no DELETE anywhere, so a busy
|
|
677
|
+
// agent eventually trips the 100MB hard block (init.ts MAX_DB_SIZE) and bricks
|
|
678
|
+
// its own memory store. Retention purges (src/defence/audit/retention.ts) now
|
|
679
|
+
// delete old rows — but getLifetimeStats() scans the whole table, so a naive
|
|
680
|
+
// purge would make lifetime totals silently undercount. This single-row
|
|
681
|
+
// cumulative aggregate is the rollup target: purges add the to-be-deleted
|
|
682
|
+
// rows' contributions here BEFORE deleting, and getLifetimeStats() returns
|
|
683
|
+
// aggregate + live so the numbers never go backwards.
|
|
684
|
+
try {
|
|
685
|
+
database.exec(`
|
|
686
|
+
CREATE TABLE IF NOT EXISTS audit_aggregates (
|
|
687
|
+
id INTEGER PRIMARY KEY CHECK (id = 1),
|
|
688
|
+
total_scans INTEGER NOT NULL DEFAULT 0,
|
|
689
|
+
threats_blocked INTEGER NOT NULL DEFAULT 0,
|
|
690
|
+
quarantined INTEGER NOT NULL DEFAULT 0,
|
|
691
|
+
memories_protected INTEGER NOT NULL DEFAULT 0,
|
|
692
|
+
credential_leaks INTEGER NOT NULL DEFAULT 0,
|
|
693
|
+
updated_at TEXT
|
|
694
|
+
);
|
|
695
|
+
`);
|
|
696
|
+
}
|
|
697
|
+
catch (err) {
|
|
698
|
+
logIfUnexpectedDdlError(err, 'audit_aggregates');
|
|
699
|
+
}
|
|
700
|
+
// Migration: Phase 10 — scope the memories_au FTS trigger to UPDATE OF the
|
|
701
|
+
// indexed columns only (title, content, tags).
|
|
702
|
+
//
|
|
703
|
+
// The trigger originally shipped as `AFTER UPDATE ON memories` with no `OF`
|
|
704
|
+
// column list, so it fired a full FTS5 external-content delete+reinsert on
|
|
705
|
+
// ANY column update. The hottest write path — accessMemory() bumping
|
|
706
|
+
// access_count / last_accessed / salience on every recalled memory, plus the
|
|
707
|
+
// 5-minute decay-score persistence — never touches indexed text, so each read
|
|
708
|
+
// was re-tokenising and rewriting the FTS index for nothing (write
|
|
709
|
+
// amplification + WAL churn + more surface for FTS drift).
|
|
710
|
+
//
|
|
711
|
+
// A `CREATE TRIGGER IF NOT EXISTS` alone CANNOT replace the existing unscoped
|
|
712
|
+
// trigger on an upgraded DB, so we DROP it first, then recreate with the
|
|
713
|
+
// scoped form. Fresh installs already get the scoped trigger from
|
|
714
|
+
// schema.sql / inline-schema.ts; this block only matters for existing DBs.
|
|
715
|
+
try {
|
|
716
|
+
database.exec('DROP TRIGGER IF EXISTS memories_au');
|
|
717
|
+
database.exec(`
|
|
718
|
+
CREATE TRIGGER IF NOT EXISTS memories_au AFTER UPDATE OF title, content, tags ON memories BEGIN
|
|
719
|
+
INSERT INTO memories_fts(memories_fts, rowid, title, content, tags)
|
|
720
|
+
VALUES('delete', old.id, old.title, old.content, old.tags);
|
|
721
|
+
INSERT INTO memories_fts(rowid, title, content, tags)
|
|
722
|
+
VALUES (new.id, new.title, new.content, new.tags);
|
|
723
|
+
END;
|
|
724
|
+
`);
|
|
725
|
+
}
|
|
726
|
+
catch (err) {
|
|
727
|
+
logIfUnexpectedDdlError(err, 'memories_au');
|
|
728
|
+
}
|
|
729
|
+
// Migration: Phase 14 — mcp_tool_hashes table (MCP tool-description drift /
|
|
730
|
+
// rug-pull detection). `shieldcortex mcp scan` stores a sha256 of each MCP
|
|
731
|
+
// server's advertised tool definition (name + description + inputSchema) and
|
|
732
|
+
// compares it on the next scan: a CHANGED hash for an already-seen tool is
|
|
733
|
+
// the classic rug-pull signal (a server silently altering an approved tool
|
|
734
|
+
// description). Mirrors the content-hash idempotency pattern used elsewhere
|
|
735
|
+
// (session_events.content_hash). Fresh installs get this from schema.sql /
|
|
736
|
+
// inline-schema.ts; this block only matters for existing DBs.
|
|
737
|
+
try {
|
|
738
|
+
database.exec(`
|
|
739
|
+
CREATE TABLE IF NOT EXISTS mcp_tool_hashes (
|
|
740
|
+
server_name TEXT NOT NULL,
|
|
741
|
+
tool_name TEXT NOT NULL,
|
|
742
|
+
content_hash TEXT NOT NULL,
|
|
743
|
+
first_seen TEXT,
|
|
744
|
+
last_seen TEXT,
|
|
745
|
+
last_changed TEXT,
|
|
746
|
+
PRIMARY KEY (server_name, tool_name)
|
|
747
|
+
);
|
|
748
|
+
`);
|
|
749
|
+
}
|
|
750
|
+
catch (err) {
|
|
751
|
+
logIfUnexpectedDdlError(err, 'mcp_tool_hashes');
|
|
752
|
+
}
|
|
657
753
|
}
|
package/dist/database/schema.sql
CHANGED
|
@@ -65,7 +65,7 @@ CREATE TRIGGER IF NOT EXISTS memories_ad AFTER DELETE ON memories BEGIN
|
|
|
65
65
|
VALUES('delete', old.id, old.title, old.content, old.tags);
|
|
66
66
|
END;
|
|
67
67
|
|
|
68
|
-
CREATE TRIGGER IF NOT EXISTS memories_au AFTER UPDATE ON memories BEGIN
|
|
68
|
+
CREATE TRIGGER IF NOT EXISTS memories_au AFTER UPDATE OF title, content, tags ON memories BEGIN
|
|
69
69
|
INSERT INTO memories_fts(memories_fts, rowid, title, content, tags)
|
|
70
70
|
VALUES('delete', old.id, old.title, old.content, old.tags);
|
|
71
71
|
INSERT INTO memories_fts(rowid, title, content, tags)
|
|
@@ -229,6 +229,21 @@ CREATE INDEX IF NOT EXISTS idx_audit_result ON defence_audit(firewall_result);
|
|
|
229
229
|
CREATE INDEX IF NOT EXISTS idx_audit_source ON defence_audit(source_type);
|
|
230
230
|
CREATE INDEX IF NOT EXISTS idx_audit_project ON defence_audit(project);
|
|
231
231
|
|
|
232
|
+
-- Defence: cumulative audit aggregate (single row, id=1). Retention purges roll
|
|
233
|
+
-- the to-be-deleted rows' lifetime-stat contributions into this row BEFORE
|
|
234
|
+
-- deleting, so getLifetimeStats() = this aggregate + a scan of the (bounded)
|
|
235
|
+
-- live defence_audit table. Without it, retention would make lifetime stats
|
|
236
|
+
-- silently undercount after every purge.
|
|
237
|
+
CREATE TABLE IF NOT EXISTS audit_aggregates (
|
|
238
|
+
id INTEGER PRIMARY KEY CHECK (id = 1),
|
|
239
|
+
total_scans INTEGER NOT NULL DEFAULT 0,
|
|
240
|
+
threats_blocked INTEGER NOT NULL DEFAULT 0,
|
|
241
|
+
quarantined INTEGER NOT NULL DEFAULT 0,
|
|
242
|
+
memories_protected INTEGER NOT NULL DEFAULT 0,
|
|
243
|
+
credential_leaks INTEGER NOT NULL DEFAULT 0,
|
|
244
|
+
updated_at TEXT
|
|
245
|
+
);
|
|
246
|
+
|
|
232
247
|
-- Defence: Quarantine for blocked/suspicious memories pending review
|
|
233
248
|
CREATE TABLE IF NOT EXISTS quarantine (
|
|
234
249
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
@@ -345,6 +360,7 @@ CREATE TABLE IF NOT EXISTS firewall_rules (
|
|
|
345
360
|
|
|
346
361
|
CREATE INDEX IF NOT EXISTS idx_firewall_rules_priority ON firewall_rules(priority);
|
|
347
362
|
CREATE INDEX IF NOT EXISTS idx_firewall_rules_enabled ON firewall_rules(enabled);
|
|
363
|
+
CREATE INDEX IF NOT EXISTS idx_firewall_rules_built_in ON firewall_rules(built_in);
|
|
348
364
|
|
|
349
365
|
-- Rate limits (cross-process, persisted)
|
|
350
366
|
CREATE TABLE IF NOT EXISTS rate_limits (
|
|
@@ -352,3 +368,25 @@ CREATE TABLE IF NOT EXISTS rate_limits (
|
|
|
352
368
|
write_count INTEGER NOT NULL DEFAULT 1,
|
|
353
369
|
window_start_ms INTEGER NOT NULL
|
|
354
370
|
);
|
|
371
|
+
|
|
372
|
+
-- Control state (single row, cross-process kill-switch / pause)
|
|
373
|
+
CREATE TABLE IF NOT EXISTS control_state (
|
|
374
|
+
id INTEGER PRIMARY KEY CHECK (id = 1),
|
|
375
|
+
mode TEXT NOT NULL DEFAULT 'active' CHECK (mode IN ('active','paused','kill_switch')),
|
|
376
|
+
meta_json TEXT,
|
|
377
|
+
updated_at TEXT NOT NULL
|
|
378
|
+
);
|
|
379
|
+
|
|
380
|
+
-- Phase 14: MCP tool-description hashes (drift / rug-pull detection).
|
|
381
|
+
-- `shieldcortex mcp scan` stores a sha256 of each MCP server's advertised tool
|
|
382
|
+
-- definition; a CHANGED hash on a re-scan flags a silently-altered (rug-pulled)
|
|
383
|
+
-- tool description.
|
|
384
|
+
CREATE TABLE IF NOT EXISTS mcp_tool_hashes (
|
|
385
|
+
server_name TEXT NOT NULL,
|
|
386
|
+
tool_name TEXT NOT NULL,
|
|
387
|
+
content_hash TEXT NOT NULL,
|
|
388
|
+
first_seen TEXT,
|
|
389
|
+
last_seen TEXT,
|
|
390
|
+
last_changed TEXT,
|
|
391
|
+
PRIMARY KEY (server_name, tool_name)
|
|
392
|
+
);
|
|
@@ -76,8 +76,16 @@ export interface LifetimeStats {
|
|
|
76
76
|
memoriesProtected: number;
|
|
77
77
|
}
|
|
78
78
|
/**
|
|
79
|
-
* Get all-time aggregate stats
|
|
80
|
-
*
|
|
79
|
+
* Get all-time aggregate stats.
|
|
80
|
+
*
|
|
81
|
+
* defence_audit is now retention-bounded (see src/defence/audit/retention.ts):
|
|
82
|
+
* old rows are periodically purged so the table can't grow until it bricks the
|
|
83
|
+
* DB at the 100MB hard limit. To keep these lifetime totals correct, every purge
|
|
84
|
+
* first rolls the to-be-deleted rows' contributions into the single-row
|
|
85
|
+
* `audit_aggregates` table. So the lifetime total is `aggregate + scan(live)` —
|
|
86
|
+
* the aggregate carries the purged history and the COUNT scan covers the
|
|
87
|
+
* (bounded) rows still on disk. This guarantees totals never go backwards after
|
|
88
|
+
* a purge. The credential LIKE scan is over a bounded table now, so it's fine.
|
|
81
89
|
*/
|
|
82
90
|
export declare function getLifetimeStats(): LifetimeStats;
|
|
83
91
|
/**
|
|
@@ -107,12 +107,20 @@ export function getAuditStats(timeRange, project) {
|
|
|
107
107
|
};
|
|
108
108
|
}
|
|
109
109
|
/**
|
|
110
|
-
* Get all-time aggregate stats
|
|
111
|
-
*
|
|
110
|
+
* Get all-time aggregate stats.
|
|
111
|
+
*
|
|
112
|
+
* defence_audit is now retention-bounded (see src/defence/audit/retention.ts):
|
|
113
|
+
* old rows are periodically purged so the table can't grow until it bricks the
|
|
114
|
+
* DB at the 100MB hard limit. To keep these lifetime totals correct, every purge
|
|
115
|
+
* first rolls the to-be-deleted rows' contributions into the single-row
|
|
116
|
+
* `audit_aggregates` table. So the lifetime total is `aggregate + scan(live)` —
|
|
117
|
+
* the aggregate carries the purged history and the COUNT scan covers the
|
|
118
|
+
* (bounded) rows still on disk. This guarantees totals never go backwards after
|
|
119
|
+
* a purge. The credential LIKE scan is over a bounded table now, so it's fine.
|
|
112
120
|
*/
|
|
113
121
|
export function getLifetimeStats() {
|
|
114
122
|
const db = getDatabase();
|
|
115
|
-
// Counts by firewall result
|
|
123
|
+
// Counts by firewall result (live, retention-bounded rows)
|
|
116
124
|
const counts = db.prepare(`
|
|
117
125
|
SELECT firewall_result, COUNT(*) as cnt
|
|
118
126
|
FROM defence_audit
|
|
@@ -137,7 +145,25 @@ export function getLifetimeStats() {
|
|
|
137
145
|
FROM defence_audit
|
|
138
146
|
WHERE LOWER(threat_indicators) LIKE '%credential%'
|
|
139
147
|
`).get();
|
|
140
|
-
|
|
148
|
+
let credentialLeaks = credRow?.cnt ?? 0;
|
|
149
|
+
// Add the cumulative aggregate of all rows already purged by retention. The
|
|
150
|
+
// table may not exist on very old DBs mid-migration — treat absence as zero.
|
|
151
|
+
try {
|
|
152
|
+
const agg = db.prepare(`
|
|
153
|
+
SELECT total_scans, threats_blocked, quarantined, memories_protected, credential_leaks
|
|
154
|
+
FROM audit_aggregates WHERE id = 1
|
|
155
|
+
`).get();
|
|
156
|
+
if (agg) {
|
|
157
|
+
totalScans += agg.total_scans;
|
|
158
|
+
threatsBlocked += agg.threats_blocked;
|
|
159
|
+
quarantined += agg.quarantined;
|
|
160
|
+
memoriesProtected += agg.memories_protected;
|
|
161
|
+
credentialLeaks += agg.credential_leaks;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
catch {
|
|
165
|
+
// No aggregate row / table yet — lifetime == live counts.
|
|
166
|
+
}
|
|
141
167
|
return {
|
|
142
168
|
totalScans,
|
|
143
169
|
threatsBlocked,
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Audit retention + size-pressure valve (Phase 8a).
|
|
3
|
+
*
|
|
4
|
+
* `logAudit()` inserts a defence_audit row on EVERY pipeline run (including
|
|
5
|
+
* per-tool-response scans) and nothing ever deletes them — the table grows
|
|
6
|
+
* forever. The DB has a 100MB hard limit (init.ts MAX_DB_SIZE) that BLOCKS all
|
|
7
|
+
* writes once exceeded, and consolidation/vacuum can't shrink audit rows that
|
|
8
|
+
* are never deleted, so a busy agent bricks its own memory store with no
|
|
9
|
+
* recovery path.
|
|
10
|
+
*
|
|
11
|
+
* Retention alone would break lifetime stats: getLifetimeStats() scans the
|
|
12
|
+
* whole defence_audit table, so deleting old rows would make every lifetime
|
|
13
|
+
* total silently undercount after a purge. So retention is COUPLED with an
|
|
14
|
+
* aggregate rollup — before deleting, the to-be-deleted rows' stat
|
|
15
|
+
* contributions are folded into the single-row `audit_aggregates` table, and
|
|
16
|
+
* getLifetimeStats() returns `aggregate + scan(remaining)`. Totals never regress.
|
|
17
|
+
*/
|
|
18
|
+
/** Default age-based retention window. */
|
|
19
|
+
export declare const DEFAULT_RETENTION_DAYS = 90;
|
|
20
|
+
/**
|
|
21
|
+
* Hard ceiling on live defence_audit rows under size pressure. When the DB file
|
|
22
|
+
* crosses the warning threshold, the valve trims the oldest rows down to (at
|
|
23
|
+
* most) this many so audit growth can't carry the file to the 100MB block.
|
|
24
|
+
*/
|
|
25
|
+
export declare const AUDIT_PRESSURE_ROW_CAP = 100000;
|
|
26
|
+
/**
|
|
27
|
+
* Age-based retention: roll the contributions of rows older than the cutoff into
|
|
28
|
+
* audit_aggregates, then DELETE them. Aggregate + delete run in one transaction
|
|
29
|
+
* so a crash can't drop rows without first banking their stats (which would make
|
|
30
|
+
* lifetime totals regress). Returns the number of rows deleted.
|
|
31
|
+
*/
|
|
32
|
+
export declare function purgeOldAuditEntries(retentionDays?: number): number;
|
|
33
|
+
/**
|
|
34
|
+
* Size-pressure valve. Cheap to call: it first checks the DB file size and bails
|
|
35
|
+
* unless the file has crossed the warning threshold (reusing the codebase's
|
|
36
|
+
* existing 50MB WARN_DB_SIZE via checkDatabaseSize(), so we don't keep a second
|
|
37
|
+
* copy of the threshold). When over pressure, it trims the OLDEST audit rows
|
|
38
|
+
* down to a row cap — same aggregate-then-delete path as age retention — so
|
|
39
|
+
* audit growth can never carry the DB to the 100MB block.
|
|
40
|
+
*
|
|
41
|
+
* Thresholds are injectable for tests. Simulating a real 50MB file (and, on a
|
|
42
|
+
* `:memory:` DB, ANY measurable file size) is impractical, so passing
|
|
43
|
+
* `warnBytes` BYPASSES the file-size gate entirely and drives the row-cap trim
|
|
44
|
+
* directly; `maxRows` sets the cap. In production neither is passed and the gate
|
|
45
|
+
* uses the live file size vs WARN_DB_SIZE.
|
|
46
|
+
*/
|
|
47
|
+
export declare function purgeAuditUnderSizePressure(options?: {
|
|
48
|
+
warnBytes?: number;
|
|
49
|
+
maxRows?: number;
|
|
50
|
+
}): number;
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Audit retention + size-pressure valve (Phase 8a).
|
|
3
|
+
*
|
|
4
|
+
* `logAudit()` inserts a defence_audit row on EVERY pipeline run (including
|
|
5
|
+
* per-tool-response scans) and nothing ever deletes them — the table grows
|
|
6
|
+
* forever. The DB has a 100MB hard limit (init.ts MAX_DB_SIZE) that BLOCKS all
|
|
7
|
+
* writes once exceeded, and consolidation/vacuum can't shrink audit rows that
|
|
8
|
+
* are never deleted, so a busy agent bricks its own memory store with no
|
|
9
|
+
* recovery path.
|
|
10
|
+
*
|
|
11
|
+
* Retention alone would break lifetime stats: getLifetimeStats() scans the
|
|
12
|
+
* whole defence_audit table, so deleting old rows would make every lifetime
|
|
13
|
+
* total silently undercount after a purge. So retention is COUPLED with an
|
|
14
|
+
* aggregate rollup — before deleting, the to-be-deleted rows' stat
|
|
15
|
+
* contributions are folded into the single-row `audit_aggregates` table, and
|
|
16
|
+
* getLifetimeStats() returns `aggregate + scan(remaining)`. Totals never regress.
|
|
17
|
+
*/
|
|
18
|
+
import { getDatabase, checkDatabaseSize } from '../../database/init.js';
|
|
19
|
+
/** Default age-based retention window. */
|
|
20
|
+
export const DEFAULT_RETENTION_DAYS = 90;
|
|
21
|
+
/**
|
|
22
|
+
* Hard ceiling on live defence_audit rows under size pressure. When the DB file
|
|
23
|
+
* crosses the warning threshold, the valve trims the oldest rows down to (at
|
|
24
|
+
* most) this many so audit growth can't carry the file to the 100MB block.
|
|
25
|
+
*/
|
|
26
|
+
export const AUDIT_PRESSURE_ROW_CAP = 100_000;
|
|
27
|
+
/**
|
|
28
|
+
* Compute the lifetime-stat contributions of a set of defence_audit rows
|
|
29
|
+
* identified by a WHERE clause + params. Mirrors getLifetimeStats's mapping
|
|
30
|
+
* EXACTLY (BLOCK→threats_blocked, QUARANTINE→quarantined, ALLOW→memories_protected,
|
|
31
|
+
* and the credential predicate is the same case-insensitive LIKE) so the
|
|
32
|
+
* aggregate + live split reproduces the pre-purge totals byte-for-byte.
|
|
33
|
+
*/
|
|
34
|
+
function computeDelta(where, params) {
|
|
35
|
+
const db = getDatabase();
|
|
36
|
+
const counts = db.prepare(`
|
|
37
|
+
SELECT firewall_result, COUNT(*) AS cnt
|
|
38
|
+
FROM defence_audit
|
|
39
|
+
WHERE ${where}
|
|
40
|
+
GROUP BY firewall_result
|
|
41
|
+
`).all(...params);
|
|
42
|
+
const delta = {
|
|
43
|
+
total_scans: 0,
|
|
44
|
+
threats_blocked: 0,
|
|
45
|
+
quarantined: 0,
|
|
46
|
+
memories_protected: 0,
|
|
47
|
+
credential_leaks: 0,
|
|
48
|
+
};
|
|
49
|
+
for (const row of counts) {
|
|
50
|
+
delta.total_scans += row.cnt;
|
|
51
|
+
if (row.firewall_result === 'BLOCK')
|
|
52
|
+
delta.threats_blocked += row.cnt;
|
|
53
|
+
else if (row.firewall_result === 'QUARANTINE')
|
|
54
|
+
delta.quarantined += row.cnt;
|
|
55
|
+
else if (row.firewall_result === 'ALLOW')
|
|
56
|
+
delta.memories_protected += row.cnt;
|
|
57
|
+
}
|
|
58
|
+
const credRow = db.prepare(`
|
|
59
|
+
SELECT COUNT(*) AS cnt
|
|
60
|
+
FROM defence_audit
|
|
61
|
+
WHERE (${where}) AND LOWER(threat_indicators) LIKE '%credential%'
|
|
62
|
+
`).get(...params);
|
|
63
|
+
delta.credential_leaks = credRow?.cnt ?? 0;
|
|
64
|
+
return delta;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Fold a delta into the single-row audit_aggregates (id=1) cumulative totals.
|
|
68
|
+
* UPSERT so the row is created on first purge and incremented thereafter.
|
|
69
|
+
*/
|
|
70
|
+
function rollIntoAggregate(delta) {
|
|
71
|
+
const db = getDatabase();
|
|
72
|
+
db.prepare(`
|
|
73
|
+
INSERT INTO audit_aggregates (
|
|
74
|
+
id, total_scans, threats_blocked, quarantined, memories_protected,
|
|
75
|
+
credential_leaks, updated_at
|
|
76
|
+
) VALUES (1, @total_scans, @threats_blocked, @quarantined, @memories_protected, @credential_leaks, @updated_at)
|
|
77
|
+
ON CONFLICT(id) DO UPDATE SET
|
|
78
|
+
total_scans = total_scans + excluded.total_scans,
|
|
79
|
+
threats_blocked = threats_blocked + excluded.threats_blocked,
|
|
80
|
+
quarantined = quarantined + excluded.quarantined,
|
|
81
|
+
memories_protected = memories_protected + excluded.memories_protected,
|
|
82
|
+
credential_leaks = credential_leaks + excluded.credential_leaks,
|
|
83
|
+
updated_at = excluded.updated_at
|
|
84
|
+
`).run({
|
|
85
|
+
total_scans: delta.total_scans,
|
|
86
|
+
threats_blocked: delta.threats_blocked,
|
|
87
|
+
quarantined: delta.quarantined,
|
|
88
|
+
memories_protected: delta.memories_protected,
|
|
89
|
+
credential_leaks: delta.credential_leaks,
|
|
90
|
+
updated_at: new Date().toISOString(),
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Age-based retention: roll the contributions of rows older than the cutoff into
|
|
95
|
+
* audit_aggregates, then DELETE them. Aggregate + delete run in one transaction
|
|
96
|
+
* so a crash can't drop rows without first banking their stats (which would make
|
|
97
|
+
* lifetime totals regress). Returns the number of rows deleted.
|
|
98
|
+
*/
|
|
99
|
+
export function purgeOldAuditEntries(retentionDays = DEFAULT_RETENTION_DAYS) {
|
|
100
|
+
const db = getDatabase();
|
|
101
|
+
const cutoff = new Date(Date.now() - retentionDays * 24 * 60 * 60 * 1000).toISOString();
|
|
102
|
+
return db.transaction(() => {
|
|
103
|
+
const where = 'timestamp < ?';
|
|
104
|
+
const params = [cutoff];
|
|
105
|
+
const delta = computeDelta(where, params);
|
|
106
|
+
if (delta.total_scans === 0)
|
|
107
|
+
return 0; // nothing to do; skip the aggregate write
|
|
108
|
+
rollIntoAggregate(delta);
|
|
109
|
+
const res = db.prepare(`DELETE FROM defence_audit WHERE ${where}`).run(...params);
|
|
110
|
+
return Number(res.changes ?? 0);
|
|
111
|
+
})();
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Size-pressure valve. Cheap to call: it first checks the DB file size and bails
|
|
115
|
+
* unless the file has crossed the warning threshold (reusing the codebase's
|
|
116
|
+
* existing 50MB WARN_DB_SIZE via checkDatabaseSize(), so we don't keep a second
|
|
117
|
+
* copy of the threshold). When over pressure, it trims the OLDEST audit rows
|
|
118
|
+
* down to a row cap — same aggregate-then-delete path as age retention — so
|
|
119
|
+
* audit growth can never carry the DB to the 100MB block.
|
|
120
|
+
*
|
|
121
|
+
* Thresholds are injectable for tests. Simulating a real 50MB file (and, on a
|
|
122
|
+
* `:memory:` DB, ANY measurable file size) is impractical, so passing
|
|
123
|
+
* `warnBytes` BYPASSES the file-size gate entirely and drives the row-cap trim
|
|
124
|
+
* directly; `maxRows` sets the cap. In production neither is passed and the gate
|
|
125
|
+
* uses the live file size vs WARN_DB_SIZE.
|
|
126
|
+
*/
|
|
127
|
+
export function purgeAuditUnderSizePressure(options = {}) {
|
|
128
|
+
const db = getDatabase();
|
|
129
|
+
const maxRows = options.maxRows ?? AUDIT_PRESSURE_ROW_CAP;
|
|
130
|
+
// Size gate. In production: only proceed once the live DB file crosses the
|
|
131
|
+
// codebase's existing WARN_DB_SIZE (50MB), surfaced via checkDatabaseSize() —
|
|
132
|
+
// we reuse that rather than keep a second copy of the threshold. When tests
|
|
133
|
+
// inject `warnBytes`, the gate is bypassed (a `:memory:` DB has no measurable
|
|
134
|
+
// file size) so the row-cap path can be exercised directly.
|
|
135
|
+
if (options.warnBytes === undefined) {
|
|
136
|
+
if (!checkDatabaseSize().warning)
|
|
137
|
+
return 0;
|
|
138
|
+
}
|
|
139
|
+
return db.transaction(() => {
|
|
140
|
+
const total = db.prepare('SELECT COUNT(*) AS c FROM defence_audit').get().c;
|
|
141
|
+
if (total <= maxRows)
|
|
142
|
+
return 0;
|
|
143
|
+
// Trim down to the cap by deleting the OLDEST rows. Find the cutoff id: keep
|
|
144
|
+
// the newest `maxRows` by timestamp, purge everything older.
|
|
145
|
+
const cutoffRow = db.prepare(`
|
|
146
|
+
SELECT timestamp FROM defence_audit
|
|
147
|
+
ORDER BY timestamp DESC
|
|
148
|
+
LIMIT 1 OFFSET ?
|
|
149
|
+
`).get(maxRows);
|
|
150
|
+
if (!cutoffRow)
|
|
151
|
+
return 0;
|
|
152
|
+
const where = 'timestamp <= ?';
|
|
153
|
+
const params = [cutoffRow.timestamp];
|
|
154
|
+
const delta = computeDelta(where, params);
|
|
155
|
+
if (delta.total_scans === 0)
|
|
156
|
+
return 0;
|
|
157
|
+
rollIntoAggregate(delta);
|
|
158
|
+
const res = db.prepare(`DELETE FROM defence_audit WHERE ${where}`).run(...params);
|
|
159
|
+
return Number(res.changes ?? 0);
|
|
160
|
+
})();
|
|
161
|
+
}
|
|
@@ -39,3 +39,14 @@ export declare function extractHighEntropyTokens(content: string): Array<{
|
|
|
39
39
|
entropy: number;
|
|
40
40
|
confidence: number;
|
|
41
41
|
}>;
|
|
42
|
+
/**
|
|
43
|
+
* Allowlist of well-known PUBLIC identifier shapes that are NOT secrets and
|
|
44
|
+
* must never be flagged as credentials — by ANY detector (pattern OR entropy).
|
|
45
|
+
*
|
|
46
|
+
* Conservative on purpose: only the exact canonical shapes below are excluded,
|
|
47
|
+
* so real secrets (sk_live_..., random base64 tokens) still trip detection.
|
|
48
|
+
* The token is matched on its own boundaries (anchored), so a SHA-shaped
|
|
49
|
+
* substring of a longer secret is NOT what gets matched here — callers pass the
|
|
50
|
+
* isolated token.
|
|
51
|
+
*/
|
|
52
|
+
export declare function isWellKnownNonSecret(token: string): boolean;
|
|
@@ -80,10 +80,37 @@ export function extractHighEntropyTokens(content) {
|
|
|
80
80
|
}
|
|
81
81
|
return results;
|
|
82
82
|
}
|
|
83
|
+
/**
|
|
84
|
+
* Allowlist of well-known PUBLIC identifier shapes that are NOT secrets and
|
|
85
|
+
* must never be flagged as credentials — by ANY detector (pattern OR entropy).
|
|
86
|
+
*
|
|
87
|
+
* Conservative on purpose: only the exact canonical shapes below are excluded,
|
|
88
|
+
* so real secrets (sk_live_..., random base64 tokens) still trip detection.
|
|
89
|
+
* The token is matched on its own boundaries (anchored), so a SHA-shaped
|
|
90
|
+
* substring of a longer secret is NOT what gets matched here — callers pass the
|
|
91
|
+
* isolated token.
|
|
92
|
+
*/
|
|
93
|
+
export function isWellKnownNonSecret(token) {
|
|
94
|
+
const t = token.trim();
|
|
95
|
+
// Canonical UUID (any version), e.g. 550e8400-e29b-41d4-a716-446655440000.
|
|
96
|
+
if (/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(t))
|
|
97
|
+
return true;
|
|
98
|
+
// Git commit SHA-1 (40 hex) and SHA-256 (64 hex) — public revision ids.
|
|
99
|
+
if (/^[0-9a-f]{40}$/i.test(t))
|
|
100
|
+
return true;
|
|
101
|
+
if (/^[0-9a-f]{64}$/i.test(t))
|
|
102
|
+
return true;
|
|
103
|
+
// Abbreviated git SHA (7–12 hex, the `git rev-parse --short` range).
|
|
104
|
+
if (/^[0-9a-f]{7,12}$/i.test(t))
|
|
105
|
+
return true;
|
|
106
|
+
return false;
|
|
107
|
+
}
|
|
83
108
|
/**
|
|
84
109
|
* Heuristic filter to reduce false positives from entropy detection.
|
|
85
110
|
*/
|
|
86
111
|
function isLikelyFalsePositive(token) {
|
|
112
|
+
if (isWellKnownNonSecret(token))
|
|
113
|
+
return true;
|
|
87
114
|
// UUIDs — legitimate identifiers, not secrets
|
|
88
115
|
if (/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(token))
|
|
89
116
|
return true;
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* variable patterns, and high-entropy string heuristics.
|
|
8
8
|
*/
|
|
9
9
|
import { ALL_CREDENTIAL_PATTERNS, } from './patterns.js';
|
|
10
|
-
import { extractHighEntropyTokens } from './entropy.js';
|
|
10
|
+
import { extractHighEntropyTokens, isWellKnownNonSecret } from './entropy.js';
|
|
11
11
|
export const DEFAULT_CREDENTIAL_CONFIG = {
|
|
12
12
|
enabled: true,
|
|
13
13
|
blockOnCritical: true,
|
|
@@ -49,6 +49,27 @@ function isAllowlisted(value, allowlist) {
|
|
|
49
49
|
}
|
|
50
50
|
return false;
|
|
51
51
|
}
|
|
52
|
+
/**
|
|
53
|
+
* Expand a match span to the full contiguous identifier token it sits in, then
|
|
54
|
+
* test it against the well-known-non-secret allowlist (git SHA / UUID).
|
|
55
|
+
*
|
|
56
|
+
* Generic hex patterns (e.g. the 32-hex "Azure" rule) match a SUBSTRING of a
|
|
57
|
+
* 40-hex commit SHA, so checking only the captured value misses it — we must
|
|
58
|
+
* look at the surrounding contiguous run. The token boundary is the usual
|
|
59
|
+
* credential alphabet ([A-Za-z0-9-]); we deliberately do NOT cross `/`, `+`,
|
|
60
|
+
* `=` etc. so a real base64 secret that merely contains a hex-looking run is
|
|
61
|
+
* not whitelisted.
|
|
62
|
+
*/
|
|
63
|
+
function matchIsWellKnownNonSecret(content, start, end) {
|
|
64
|
+
const tokenChar = /[A-Za-z0-9-]/;
|
|
65
|
+
let s = start;
|
|
66
|
+
let e = end;
|
|
67
|
+
while (s > 0 && tokenChar.test(content[s - 1]))
|
|
68
|
+
s--;
|
|
69
|
+
while (e < content.length && tokenChar.test(content[e]))
|
|
70
|
+
e++;
|
|
71
|
+
return isWellKnownNonSecret(content.slice(s, e));
|
|
72
|
+
}
|
|
52
73
|
// ── Scanner ──
|
|
53
74
|
/**
|
|
54
75
|
* Scan content for credential leaks.
|
|
@@ -88,6 +109,11 @@ export function scanForCredentials(content, config) {
|
|
|
88
109
|
const end = start + fullMatch.length;
|
|
89
110
|
if (matchedRanges.some(r => start >= r.start && end <= r.end))
|
|
90
111
|
continue;
|
|
112
|
+
// Skip well-known PUBLIC identifiers (git SHA / UUID). Generic hex rules
|
|
113
|
+
// match a substring of these, so expand to the full token before testing
|
|
114
|
+
// (Phase 17 A5).
|
|
115
|
+
if (matchIsWellKnownNonSecret(content, start, end))
|
|
116
|
+
continue;
|
|
91
117
|
const action = actionForSeverity(pattern.severity, cfg);
|
|
92
118
|
const redacted = redactMatch(secretValue, pattern.type);
|
|
93
119
|
findings.push({
|
|
@@ -23,3 +23,12 @@ export declare const PRIVATE_KEY_PATTERNS: CredentialPattern[];
|
|
|
23
23
|
export declare const CONNECTION_STRING_PATTERNS: CredentialPattern[];
|
|
24
24
|
export declare const ENV_SECRET_PATTERNS: CredentialPattern[];
|
|
25
25
|
export declare const ALL_CREDENTIAL_PATTERNS: CredentialPattern[];
|
|
26
|
+
/**
|
|
27
|
+
* Fetch specific credential pattern SOURCES by name from the single source of
|
|
28
|
+
* truth. Lets other detectors (e.g. fragmentation entity extraction) reuse the
|
|
29
|
+
* canonical token regexes instead of maintaining their own divergent copies —
|
|
30
|
+
* WITHOUT pulling in the broad/low-confidence heuristics, so detection scope is
|
|
31
|
+
* unchanged. Throws on an unknown name so a rename can never silently drop a
|
|
32
|
+
* provider.
|
|
33
|
+
*/
|
|
34
|
+
export declare function getCredentialRegexesByName(names: string[]): RegExp[];
|
|
@@ -357,3 +357,24 @@ export const ALL_CREDENTIAL_PATTERNS = [
|
|
|
357
357
|
...GENERIC_SECRET_PATTERNS,
|
|
358
358
|
...ENV_SECRET_PATTERNS,
|
|
359
359
|
];
|
|
360
|
+
// ── Single Source of Truth — pattern lookup by name (Phase 17 C3) ──
|
|
361
|
+
/** Index of every credential pattern by its `name`, for cross-module reuse. */
|
|
362
|
+
const PATTERNS_BY_NAME = new Map(ALL_CREDENTIAL_PATTERNS.map((p) => [p.name, p]));
|
|
363
|
+
/**
|
|
364
|
+
* Fetch specific credential pattern SOURCES by name from the single source of
|
|
365
|
+
* truth. Lets other detectors (e.g. fragmentation entity extraction) reuse the
|
|
366
|
+
* canonical token regexes instead of maintaining their own divergent copies —
|
|
367
|
+
* WITHOUT pulling in the broad/low-confidence heuristics, so detection scope is
|
|
368
|
+
* unchanged. Throws on an unknown name so a rename can never silently drop a
|
|
369
|
+
* provider.
|
|
370
|
+
*/
|
|
371
|
+
export function getCredentialRegexesByName(names) {
|
|
372
|
+
return names.map((name) => {
|
|
373
|
+
const pattern = PATTERNS_BY_NAME.get(name);
|
|
374
|
+
if (!pattern) {
|
|
375
|
+
throw new Error(`Unknown credential pattern name: "${name}"`);
|
|
376
|
+
}
|
|
377
|
+
// Fresh RegExp so callers don't share lastIndex state with the source.
|
|
378
|
+
return new RegExp(pattern.regex.source, pattern.regex.flags);
|
|
379
|
+
});
|
|
380
|
+
}
|
|
@@ -2,6 +2,13 @@
|
|
|
2
2
|
* SQLite CRUD for custom injection patterns (Pro feature).
|
|
3
3
|
*/
|
|
4
4
|
import { getDatabase } from '../../database/init.js';
|
|
5
|
+
import { createRequire } from 'module';
|
|
6
|
+
// safe-regex2 is require()d lazily (it may not be installed) behind a
|
|
7
|
+
// try/catch fallback. Under real Node ESM a bare require() throws
|
|
8
|
+
// ReferenceError, which the catch would swallow — quietly downgrading the
|
|
9
|
+
// ReDoS check to the weaker heuristic. createRequire() gives us a working
|
|
10
|
+
// require here.
|
|
11
|
+
const require = createRequire(import.meta.url);
|
|
5
12
|
const MAX_PATTERNS = 50;
|
|
6
13
|
const MAX_REGEX_LENGTH = 500;
|
|
7
14
|
/**
|
|
@@ -23,7 +30,7 @@ export function validateRegex(pattern) {
|
|
|
23
30
|
}
|
|
24
31
|
// ReDoS check via safe-regex2
|
|
25
32
|
try {
|
|
26
|
-
//
|
|
33
|
+
// Load the CommonJS safe-regex2 via createRequire (it may not be installed)
|
|
27
34
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
28
35
|
const safe = require('safe-regex2');
|
|
29
36
|
if (!safe(pattern)) {
|
|
@@ -36,3 +36,21 @@ export declare function deleteFirewallRule(id: number): boolean;
|
|
|
36
36
|
* Get all enabled rules sorted by priority (used by the defence pipeline).
|
|
37
37
|
*/
|
|
38
38
|
export declare function getEnabledFirewallRules(): FirewallRule[];
|
|
39
|
+
/**
|
|
40
|
+
* Evaluate a single firewall rule against candidate text(s), honouring the
|
|
41
|
+
* rule's `condition_type`. This is the single source of truth for rule
|
|
42
|
+
* matching — the defence pipeline calls it for every enabled rule.
|
|
43
|
+
*
|
|
44
|
+
* - `keyword`: case-insensitive LITERAL substring match. Regex metacharacters
|
|
45
|
+
* in the value (`a.b`, `x+y`) are treated literally, not as a pattern.
|
|
46
|
+
* - `domain`: the value is matched as a host/domain. It matches when the value
|
|
47
|
+
* appears as a hostname (or a suffix of one, so `evil.com` matches
|
|
48
|
+
* `api.evil.com`) anywhere in the text, including inside a URL.
|
|
49
|
+
* - `regex` (and any unknown type): compiled as a case-insensitive RegExp.
|
|
50
|
+
* Invalid patterns never match (returns false) rather than throwing.
|
|
51
|
+
*
|
|
52
|
+
* `condition_value` is assumed to have already been vetted by safe-regex2 at
|
|
53
|
+
* creation time for regex rules (see createFirewallRule callers); we still
|
|
54
|
+
* guard compilation here so a malformed stored value can't crash the pipeline.
|
|
55
|
+
*/
|
|
56
|
+
export declare function ruleMatches(rule: Pick<FirewallRule, 'condition_type' | 'condition_value'>, ...texts: Array<string | undefined | null>): boolean;
|