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
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
*
|
|
10
10
|
* This transforms the memory system from reactive to continuously organic.
|
|
11
11
|
*/
|
|
12
|
-
import { DEFAULT_WORKER_CONFIG, MCP_LIGHT_TICK_INTERVAL_MS, } from './types.js';
|
|
12
|
+
import { DEFAULT_WORKER_CONFIG, MCP_LIGHT_TICK_INTERVAL_MS, MCP_RETRY_QUEUE_BUDGET, } from './types.js';
|
|
13
13
|
import { getDatabase } from '../database/init.js';
|
|
14
14
|
import { pruneActivationCache } from '../memory/activation.js';
|
|
15
15
|
import { getMemoryStats } from '../memory/store.js';
|
|
@@ -21,22 +21,51 @@ import { emitWorkerLightTick, emitWorkerMediumTick, emitPredictiveConsolidation,
|
|
|
21
21
|
import { processRetryQueue, purgeOldEntries } from '../cloud/sync-queue.js';
|
|
22
22
|
import { sendHeartbeat } from '../cloud/sync.js';
|
|
23
23
|
import { refreshCloudIronDome, applyCachedCloudPatterns } from '../cloud/iron-dome-sync.js';
|
|
24
|
+
import { purgeOldAuditEntries, purgeAuditUnderSizePressure } from '../defence/audit/retention.js';
|
|
24
25
|
import { isFeatureEnabled } from '../license/gate.js';
|
|
25
|
-
import { existsSync, mkdirSync, writeFileSync } from 'fs';
|
|
26
|
+
import { existsSync, mkdirSync, writeFileSync, readFileSync } from 'fs';
|
|
26
27
|
import { join } from 'path';
|
|
27
28
|
import { homedir } from 'os';
|
|
28
29
|
const WORKER_STATE_DIR = join(homedir(), '.shieldcortex', 'state');
|
|
29
30
|
const WORKER_STATE_FILE = join(WORKER_STATE_DIR, 'worker.json');
|
|
30
|
-
|
|
31
|
+
// Audit retention runs in the light tick (which fires every 5–15 min) but is
|
|
32
|
+
// throttled to ~once per day — a daily purge is plenty to keep defence_audit
|
|
33
|
+
// bounded, and running it every tick would be wasteful.
|
|
34
|
+
const AUDIT_PURGE_INTERVAL_MS = 24 * 60 * 60 * 1000;
|
|
35
|
+
function persistWorkerState(profile, lastLightTick, lastAuditPurge) {
|
|
31
36
|
try {
|
|
32
37
|
if (!existsSync(WORKER_STATE_DIR))
|
|
33
38
|
mkdirSync(WORKER_STATE_DIR, { recursive: true });
|
|
34
|
-
writeFileSync(WORKER_STATE_FILE, JSON.stringify({
|
|
39
|
+
writeFileSync(WORKER_STATE_FILE, JSON.stringify({
|
|
40
|
+
pid: process.pid,
|
|
41
|
+
profile,
|
|
42
|
+
lastLightTick: lastLightTick.toISOString(),
|
|
43
|
+
lastAuditPurge: lastAuditPurge ? lastAuditPurge.toISOString() : null,
|
|
44
|
+
}, null, 2), 'utf-8');
|
|
35
45
|
}
|
|
36
46
|
catch {
|
|
37
47
|
// Best-effort: doctor will warn if the file is stale or missing
|
|
38
48
|
}
|
|
39
49
|
}
|
|
50
|
+
/**
|
|
51
|
+
* Read the persisted lastAuditPurge timestamp so the 24h throttle survives
|
|
52
|
+
* process restarts — MCP servers restart frequently (one per Claude Code
|
|
53
|
+
* window), and without this every restart would trigger a fresh purge.
|
|
54
|
+
*/
|
|
55
|
+
function readPersistedAuditPurge() {
|
|
56
|
+
try {
|
|
57
|
+
const raw = JSON.parse(readFileSync(WORKER_STATE_FILE, 'utf-8'));
|
|
58
|
+
if (raw.lastAuditPurge) {
|
|
59
|
+
const d = new Date(raw.lastAuditPurge);
|
|
60
|
+
if (!Number.isNaN(d.getTime()))
|
|
61
|
+
return d;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
// No prior state — treat as never purged.
|
|
66
|
+
}
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
40
69
|
/**
|
|
41
70
|
* Brain Worker Class
|
|
42
71
|
*
|
|
@@ -59,6 +88,9 @@ export class BrainWorker {
|
|
|
59
88
|
lastLightTick = null;
|
|
60
89
|
lastMediumTick = null;
|
|
61
90
|
lastConsolidation = null;
|
|
91
|
+
// Seeded from persisted state so the 24h audit-purge throttle survives
|
|
92
|
+
// frequent MCP-server restarts.
|
|
93
|
+
lastAuditPurge = readPersistedAuditPurge();
|
|
62
94
|
/**
|
|
63
95
|
* Create a new BrainWorker
|
|
64
96
|
*
|
|
@@ -181,20 +213,51 @@ export class BrainWorker {
|
|
|
181
213
|
result: result.predictiveConsolidation,
|
|
182
214
|
});
|
|
183
215
|
}
|
|
184
|
-
// 3
|
|
185
|
-
//
|
|
186
|
-
//
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
216
|
+
// 3. Audit retention — runs on BOTH profiles (MCP-only installs scan
|
|
217
|
+
// tool responses too, so their defence_audit also grows unbounded; the
|
|
218
|
+
// sync-queue purge being worker-only left an analogous gap we deliberately
|
|
219
|
+
// don't repeat here). The pressure valve is a cheap size check, so it runs
|
|
220
|
+
// every tick; the age-based purge is throttled to ~once per 24h.
|
|
221
|
+
try {
|
|
222
|
+
const pressurePurged = purgeAuditUnderSizePressure();
|
|
223
|
+
if (pressurePurged > 0) {
|
|
224
|
+
console.log(`[BrainWorker] Audit size-pressure valve purged ${pressurePurged} rows`);
|
|
225
|
+
}
|
|
226
|
+
const now = result.timestamp.getTime();
|
|
227
|
+
const dueForPurge = this.lastAuditPurge === null ||
|
|
228
|
+
now - this.lastAuditPurge.getTime() >= AUDIT_PURGE_INTERVAL_MS;
|
|
229
|
+
if (dueForPurge) {
|
|
230
|
+
const purged = purgeOldAuditEntries();
|
|
231
|
+
this.lastAuditPurge = result.timestamp;
|
|
232
|
+
if (purged > 0) {
|
|
233
|
+
console.log(`[BrainWorker] Audit retention purged ${purged} entries older than 90d`);
|
|
193
234
|
}
|
|
194
235
|
}
|
|
195
|
-
|
|
196
|
-
|
|
236
|
+
}
|
|
237
|
+
catch (auditErr) {
|
|
238
|
+
console.error('[BrainWorker] Audit retention failed:', auditErr);
|
|
239
|
+
}
|
|
240
|
+
// 4. Sync retry queue — drains on BOTH profiles. MCP-only installs have
|
|
241
|
+
// no full worker, so if this stayed full-only their queued audits/
|
|
242
|
+
// memories would age out unsent (the same gap we closed for audit
|
|
243
|
+
// retention above). The MCP path is BUDGETED (one process per Claude Code
|
|
244
|
+
// window) so it doesn't do unbounded network work each tick; full keeps
|
|
245
|
+
// its historical unbudgeted cadence. processRetryQueue itself no-ops
|
|
246
|
+
// cheaply when cloud is disabled (no API key), so this is safe to call
|
|
247
|
+
// unconditionally — no extra gate needed.
|
|
248
|
+
try {
|
|
249
|
+
const retryResult = await processRetryQueue(this.config.profile === 'mcp' ? { maxRows: MCP_RETRY_QUEUE_BUDGET } : {});
|
|
250
|
+
if (retryResult.processed > 0) {
|
|
251
|
+
console.log(`[BrainWorker] Sync retry queue: processed ${retryResult.processed} ` +
|
|
252
|
+
`(${retryResult.succeeded} ok, ${retryResult.failed} retry, ${retryResult.permanentlyFailed} failed)`);
|
|
197
253
|
}
|
|
254
|
+
}
|
|
255
|
+
catch (retryError) {
|
|
256
|
+
console.error('[BrainWorker] Sync retry queue failed:', retryError);
|
|
257
|
+
}
|
|
258
|
+
// 5-6: heartbeat + Iron Dome — full profile only. Skipped under MCP so we
|
|
259
|
+
// don't fan out N concurrent network calls from N open Claude Code windows.
|
|
260
|
+
if (this.config.profile === 'full') {
|
|
198
261
|
if (isFeatureEnabled('cloud_sync')) {
|
|
199
262
|
try {
|
|
200
263
|
sendHeartbeat();
|
|
@@ -216,7 +279,7 @@ export class BrainWorker {
|
|
|
216
279
|
this.lastLightTick = result.timestamp;
|
|
217
280
|
this.stats.lightTicks++;
|
|
218
281
|
// Persist worker freshness for `shieldcortex doctor` to detect stalls.
|
|
219
|
-
persistWorkerState(this.config.profile, result.timestamp);
|
|
282
|
+
persistWorkerState(this.config.profile, result.timestamp, this.lastAuditPurge);
|
|
220
283
|
// Emit light tick event
|
|
221
284
|
emitWorkerLightTick(result);
|
|
222
285
|
// Log summary
|
package/dist/worker/types.d.ts
CHANGED
|
@@ -51,6 +51,14 @@ export declare const DEFAULT_WORKER_CONFIG: WorkerConfig;
|
|
|
51
51
|
* background work in check across many windows.
|
|
52
52
|
*/
|
|
53
53
|
export declare const MCP_LIGHT_TICK_INTERVAL_MS: number;
|
|
54
|
+
/**
|
|
55
|
+
* Per-tick budget for the sync retry queue under the MCP profile. MCP-only
|
|
56
|
+
* installs have no full worker, so they MUST drain their own queue or queued
|
|
57
|
+
* audits/memories age out unsent — but one MCP process runs per Claude Code
|
|
58
|
+
* window, so we cap the network work each does per light tick. The full
|
|
59
|
+
* (dashboard) profile keeps its historical unbudgeted cadence.
|
|
60
|
+
*/
|
|
61
|
+
export declare const MCP_RETRY_QUEUE_BUDGET = 25;
|
|
54
62
|
/**
|
|
55
63
|
* Result of a light tick operation
|
|
56
64
|
*/
|
package/dist/worker/types.js
CHANGED
|
@@ -26,3 +26,11 @@ export const DEFAULT_WORKER_CONFIG = {
|
|
|
26
26
|
* background work in check across many windows.
|
|
27
27
|
*/
|
|
28
28
|
export const MCP_LIGHT_TICK_INTERVAL_MS = 15 * 60 * 1000;
|
|
29
|
+
/**
|
|
30
|
+
* Per-tick budget for the sync retry queue under the MCP profile. MCP-only
|
|
31
|
+
* installs have no full worker, so they MUST drain their own queue or queued
|
|
32
|
+
* audits/memories age out unsent — but one MCP process runs per Claude Code
|
|
33
|
+
* window, so we cap the network work each does per light tick. The full
|
|
34
|
+
* (dashboard) profile keeps its historical unbudgeted cadence.
|
|
35
|
+
*/
|
|
36
|
+
export const MCP_RETRY_QUEUE_BUDGET = 25;
|
|
@@ -5,6 +5,24 @@
|
|
|
5
5
|
* and aggregates findings into a single XRayResult.
|
|
6
6
|
*/
|
|
7
7
|
import type { XRayResult } from './types.js';
|
|
8
|
+
/**
|
|
9
|
+
* Hidden directories worth scanning. These hold agent-instruction and CI
|
|
10
|
+
* surfaces (skills, slash commands, settings hooks, workflow YAML, editor
|
|
11
|
+
* tasks) — the primary prompt-injection / persistence vectors in an AI-agent
|
|
12
|
+
* codebase. By default `walkDir` skips ALL dot-directories; this allow-list
|
|
13
|
+
* carves out the ones X-Ray must inspect. `SKIP_DIRS` always wins, so `.git`
|
|
14
|
+
* et al. stay excluded even though they're hidden.
|
|
15
|
+
*
|
|
16
|
+
* Shared with watch mode (`watch.ts` imports this) so a scan and a watch agree
|
|
17
|
+
* on exactly which hidden dirs are in scope — one source of truth.
|
|
18
|
+
*/
|
|
19
|
+
export declare const ALLOW_HIDDEN_DIRS: Set<string>;
|
|
20
|
+
/**
|
|
21
|
+
* Whether to descend into a directory while walking. Recurse when it's not in
|
|
22
|
+
* the skip-list AND it's either not hidden or explicitly allow-listed. Exported
|
|
23
|
+
* so watch mode mirrors the identical rule.
|
|
24
|
+
*/
|
|
25
|
+
export declare function shouldWalkDir(name: string): boolean;
|
|
8
26
|
/**
|
|
9
27
|
* Scan an entire directory for X-Ray findings.
|
|
10
28
|
*
|
package/dist/xray/dir-scanner.js
CHANGED
|
@@ -15,6 +15,28 @@ const SKIP_DIRS = new Set([
|
|
|
15
15
|
'__pycache__', '.tox', '.venv', 'venv', '.cache', 'coverage',
|
|
16
16
|
'Caches', 'CacheStorage', 'IndexedDB', 'GPUCache',
|
|
17
17
|
]);
|
|
18
|
+
/**
|
|
19
|
+
* Hidden directories worth scanning. These hold agent-instruction and CI
|
|
20
|
+
* surfaces (skills, slash commands, settings hooks, workflow YAML, editor
|
|
21
|
+
* tasks) — the primary prompt-injection / persistence vectors in an AI-agent
|
|
22
|
+
* codebase. By default `walkDir` skips ALL dot-directories; this allow-list
|
|
23
|
+
* carves out the ones X-Ray must inspect. `SKIP_DIRS` always wins, so `.git`
|
|
24
|
+
* et al. stay excluded even though they're hidden.
|
|
25
|
+
*
|
|
26
|
+
* Shared with watch mode (`watch.ts` imports this) so a scan and a watch agree
|
|
27
|
+
* on exactly which hidden dirs are in scope — one source of truth.
|
|
28
|
+
*/
|
|
29
|
+
export const ALLOW_HIDDEN_DIRS = new Set([
|
|
30
|
+
'.github', '.claude', '.cursor', '.codex', '.vscode', '.agents', '.openclaw',
|
|
31
|
+
]);
|
|
32
|
+
/**
|
|
33
|
+
* Whether to descend into a directory while walking. Recurse when it's not in
|
|
34
|
+
* the skip-list AND it's either not hidden or explicitly allow-listed. Exported
|
|
35
|
+
* so watch mode mirrors the identical rule.
|
|
36
|
+
*/
|
|
37
|
+
export function shouldWalkDir(name) {
|
|
38
|
+
return !SKIP_DIRS.has(name) && (!name.startsWith('.') || ALLOW_HIDDEN_DIRS.has(name));
|
|
39
|
+
}
|
|
18
40
|
/** Absolute path prefixes to never scan — system/OS files that always produce false positives. */
|
|
19
41
|
const SKIP_PATH_PREFIXES = [
|
|
20
42
|
'/System/',
|
|
@@ -75,7 +97,7 @@ function walkDir(dirPath, files, depth = 0) {
|
|
|
75
97
|
break;
|
|
76
98
|
const fullPath = path.join(dirPath, entry.name);
|
|
77
99
|
if (entry.isDirectory()) {
|
|
78
|
-
if (
|
|
100
|
+
if (shouldWalkDir(entry.name)) {
|
|
79
101
|
walkDir(fullPath, files, depth + 1);
|
|
80
102
|
}
|
|
81
103
|
}
|
|
@@ -246,9 +246,16 @@ export async function scanFile(filePath, deep) {
|
|
|
246
246
|
if (!stat.isFile() || stat.size > MAX_FILE_SIZE) {
|
|
247
247
|
return findings;
|
|
248
248
|
}
|
|
249
|
+
// Read the file body AT MOST ONCE. Each branch records what it read so deep
|
|
250
|
+
// mode (below) can reuse it instead of re-reading from disk. Previously a
|
|
251
|
+
// deep scan read every file a second time (and on every other-text file,
|
|
252
|
+
// walked the same content twice) — pure wasted IO for large trees.
|
|
253
|
+
let fileBuffer = null;
|
|
254
|
+
let textContent = null;
|
|
249
255
|
// Route by extension
|
|
250
256
|
if (IMAGE_EXTENSIONS.has(ext)) {
|
|
251
257
|
const buf = fs.readFileSync(filePath);
|
|
258
|
+
fileBuffer = buf;
|
|
252
259
|
findings.push(...scanImage(filePath, buf));
|
|
253
260
|
// Polyglot check
|
|
254
261
|
const polyglot = checkPolyglot(buf);
|
|
@@ -259,6 +266,7 @@ export async function scanFile(filePath, deep) {
|
|
|
259
266
|
}
|
|
260
267
|
else if (JSON_EXTENSIONS.has(ext)) {
|
|
261
268
|
const content = fs.readFileSync(filePath, 'utf-8');
|
|
269
|
+
textContent = content;
|
|
262
270
|
findings.push(...scanJson(filePath, content));
|
|
263
271
|
// Zero-width unicode check
|
|
264
272
|
const zw = hasZeroWidthUnicode(content);
|
|
@@ -269,6 +277,7 @@ export async function scanFile(filePath, deep) {
|
|
|
269
277
|
}
|
|
270
278
|
else if (CODE_EXTENSIONS.has(ext)) {
|
|
271
279
|
const content = fs.readFileSync(filePath, 'utf-8');
|
|
280
|
+
textContent = content;
|
|
272
281
|
findings.push(...detectPatterns(content, filePath));
|
|
273
282
|
// Zero-width unicode check
|
|
274
283
|
const zw = hasZeroWidthUnicode(content);
|
|
@@ -281,6 +290,7 @@ export async function scanFile(filePath, deep) {
|
|
|
281
290
|
// For any other text-like file, try reading as text
|
|
282
291
|
try {
|
|
283
292
|
const buf = fs.readFileSync(filePath);
|
|
293
|
+
fileBuffer = buf;
|
|
284
294
|
// Check if it's mostly text
|
|
285
295
|
let nonPrintable = 0;
|
|
286
296
|
const sampleSize = Math.min(buf.length, 8192);
|
|
@@ -293,6 +303,7 @@ export async function scanFile(filePath, deep) {
|
|
|
293
303
|
if (nonPrintable / sampleSize < 0.1) {
|
|
294
304
|
// Treat as text
|
|
295
305
|
const content = buf.toString('utf-8');
|
|
306
|
+
textContent = content;
|
|
296
307
|
findings.push(...detectPatterns(content, filePath));
|
|
297
308
|
const zw = hasZeroWidthUnicode(content);
|
|
298
309
|
if (zw) {
|
|
@@ -316,7 +327,11 @@ export async function scanFile(filePath, deep) {
|
|
|
316
327
|
// Deep mode: additional analysis (Pro feature)
|
|
317
328
|
if (deep && stat.size > 0) {
|
|
318
329
|
try {
|
|
319
|
-
|
|
330
|
+
// Reuse the content already read above. Derive text from a buffer the
|
|
331
|
+
// image/binary branch read (no re-read); only hit disk if nothing was
|
|
332
|
+
// captured (a branch that didn't read, e.g. an early-skipped path).
|
|
333
|
+
const content = textContent ??
|
|
334
|
+
(fileBuffer ? fileBuffer.toString('utf-8') : fs.readFileSync(filePath, 'utf-8'));
|
|
320
335
|
// Entropy analysis — detect packed/obfuscated code
|
|
321
336
|
if (CODE_EXTENSIONS.has(ext) || ext === '.js' || ext === '.mjs') {
|
|
322
337
|
const entropy = shannonEntropy(content);
|
|
@@ -43,7 +43,15 @@ export function createFindingsStore(basePath) {
|
|
|
43
43
|
addFindings(sourceId, sourceKind, target, rawFindings) {
|
|
44
44
|
const now = new Date().toISOString();
|
|
45
45
|
const existing = readFindings();
|
|
46
|
-
|
|
46
|
+
// Dedupe against EVERY retained finding regardless of status (Phase 17
|
|
47
|
+
// B5). Previously only `status === 'new'` findings seeded the dedupe set,
|
|
48
|
+
// so a finding the user had already triaged — ignored, resolved,
|
|
49
|
+
// reviewed or quarantined — resurfaced as a brand-new duplicate on every
|
|
50
|
+
// re-scan, undoing their decision. Matching across all statuses respects
|
|
51
|
+
// the prior triage. (Findings aged past the 30-day cleanup window below
|
|
52
|
+
// are no longer retained, so they may legitimately reappear — that's the
|
|
53
|
+
// intended TTL behaviour, not a dedupe miss.)
|
|
54
|
+
const existingKeys = new Set(existing.map((f) => findingDedupeKey(f.target, f)));
|
|
47
55
|
const newFindings = [];
|
|
48
56
|
for (const f of rawFindings) {
|
|
49
57
|
const key = findingDedupeKey(target, f);
|
package/dist/xray/index.d.ts
CHANGED
|
@@ -18,6 +18,8 @@ export { watchDirectory } from './watch.js';
|
|
|
18
18
|
export { handlePreinstallCheck } from './preinstall.js';
|
|
19
19
|
export { xrayMemoryContent } from './memory-guard.js';
|
|
20
20
|
export type { MemoryGuardResult } from './memory-guard.js';
|
|
21
|
+
export { toSarif } from './sarif.js';
|
|
22
|
+
export type { SarifLog } from './sarif.js';
|
|
21
23
|
/**
|
|
22
24
|
* Handle the `shieldcortex xray` CLI command.
|
|
23
25
|
*/
|
package/dist/xray/index.js
CHANGED
|
@@ -16,6 +16,7 @@ import { scanDirectory } from './dir-scanner.js';
|
|
|
16
16
|
import { inspectNpmPackage } from './npm-inspector.js';
|
|
17
17
|
import { calculateTrustScore } from './trust-score.js';
|
|
18
18
|
import { formatXRayReport, formatXRayMarkdown } from './report.js';
|
|
19
|
+
import { toSarif } from './sarif.js';
|
|
19
20
|
import { watchDirectory } from './watch.js';
|
|
20
21
|
import { appendActivity, appendHistory, createHistoryEntry } from './activity.js';
|
|
21
22
|
export { calculateTrustScore } from './trust-score.js';
|
|
@@ -27,6 +28,7 @@ export { formatXRayReport, formatXRayMarkdown } from './report.js';
|
|
|
27
28
|
export { watchDirectory } from './watch.js';
|
|
28
29
|
export { handlePreinstallCheck } from './preinstall.js';
|
|
29
30
|
export { xrayMemoryContent } from './memory-guard.js';
|
|
31
|
+
export { toSarif } from './sarif.js';
|
|
30
32
|
// ── Usage tracking ──────────────────────────────────────────
|
|
31
33
|
const USAGE_FILE = path.join(os.homedir(), '.shieldcortex', 'xray-usage.json');
|
|
32
34
|
const FREE_DAILY_LIMIT = 5;
|
|
@@ -84,6 +86,7 @@ export async function handleXRayCommand(args) {
|
|
|
84
86
|
const deep = flags.has('--deep');
|
|
85
87
|
const jsonOutput = flags.has('--json');
|
|
86
88
|
const markdownOutput = flags.has('--markdown');
|
|
89
|
+
const sarifOutput = flags.has('--sarif') || args.includes('--format=sarif');
|
|
87
90
|
const ciMode = flags.has('--ci');
|
|
88
91
|
const watchMode = flags.has('--watch');
|
|
89
92
|
const ciThreshold = (() => {
|
|
@@ -100,6 +103,7 @@ export async function handleXRayCommand(args) {
|
|
|
100
103
|
console.error(' --deep Deep scan with full analysis (Pro)');
|
|
101
104
|
console.error(' --json Output JSON result');
|
|
102
105
|
console.error(' --markdown Output markdown report');
|
|
106
|
+
console.error(' --sarif Output SARIF 2.1.0 (GitHub Code Scanning)');
|
|
103
107
|
console.error(' --ci CI/CD mode: exit code 1 if risk >= threshold');
|
|
104
108
|
console.error(' --threshold=LEVEL Risk threshold for --ci (CRITICAL|HIGH|MEDIUM|LOW, default: HIGH)');
|
|
105
109
|
console.error(' --watch Watch directory for changes and scan incrementally');
|
|
@@ -201,7 +205,12 @@ export async function handleXRayCommand(args) {
|
|
|
201
205
|
: `${result.findings.length} findings across ${result.filesScanned} files`,
|
|
202
206
|
});
|
|
203
207
|
// Output
|
|
204
|
-
if (
|
|
208
|
+
if (sarifOutput) {
|
|
209
|
+
// Pure SARIF JSON on stdout — nothing else, so the file can be uploaded
|
|
210
|
+
// straight to GitHub Code Scanning.
|
|
211
|
+
console.log(JSON.stringify(toSarif(result.findings), null, 2));
|
|
212
|
+
}
|
|
213
|
+
else if (jsonOutput) {
|
|
205
214
|
console.log(JSON.stringify(result, null, 2));
|
|
206
215
|
}
|
|
207
216
|
else if (markdownOutput) {
|
|
@@ -6,6 +6,37 @@
|
|
|
6
6
|
* Uses only Node.js built-ins (https module) — no new dependencies.
|
|
7
7
|
*/
|
|
8
8
|
import type { XRayResult } from './types.js';
|
|
9
|
+
/**
|
|
10
|
+
* Decide whether another redirect hop may be followed. Returns the remaining
|
|
11
|
+
* budget for the next hop, or `null` when the cap is exhausted. Pure + exported
|
|
12
|
+
* so the redirect bound can be unit-tested without standing up a TLS server.
|
|
13
|
+
*/
|
|
14
|
+
export declare function nextRedirectBudget(redirectsLeft: number): number | null;
|
|
15
|
+
/**
|
|
16
|
+
* Simple HTTPS GET returning the response body as a string.
|
|
17
|
+
*
|
|
18
|
+
* Bounds redirect following (max MAX_REDIRECTS hops) and applies a socket
|
|
19
|
+
* timeout so a hostile / mis-configured registry cannot loop or hang us.
|
|
20
|
+
*/
|
|
21
|
+
export declare function httpsGet(url: string, redirectsLeft?: number): Promise<string>;
|
|
22
|
+
/**
|
|
23
|
+
* A minimal readable-stream shape: emits `data` (Buffer), then `end`, or
|
|
24
|
+
* `error`. Lets the decompressed-size guard be unit-tested against a tiny fake
|
|
25
|
+
* stream without producing a real 100 MB tarball.
|
|
26
|
+
*/
|
|
27
|
+
export interface DecompressStream {
|
|
28
|
+
on(event: 'data', listener: (chunk: Buffer) => void): unknown;
|
|
29
|
+
on(event: 'end', listener: () => void): unknown;
|
|
30
|
+
on(event: 'error', listener: (err: Error) => void): unknown;
|
|
31
|
+
destroy?(err?: Error): unknown;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Accumulate a decompressed stream into a single Buffer, aborting if the total
|
|
35
|
+
* exceeds `maxBytes`. This is the gzip-bomb guard: a hostile .tgz can be tiny
|
|
36
|
+
* on disk yet decompress to gigabytes, so we cap the INFLATED size, not just
|
|
37
|
+
* the download. Pure (no fs/network) so it can be tested with a fake stream.
|
|
38
|
+
*/
|
|
39
|
+
export declare function collectDecompressed(stream: DecompressStream, maxBytes?: number): Promise<Buffer>;
|
|
9
40
|
/**
|
|
10
41
|
* Inspect an npm package for hidden risk.
|
|
11
42
|
*
|
|
@@ -14,6 +14,22 @@ import { detectPatterns } from './patterns.js';
|
|
|
14
14
|
import { calculateTrustScore } from './trust-score.js';
|
|
15
15
|
import { scanFile } from './file-scanner.js';
|
|
16
16
|
// ── Constants ───────────────────────────────────────────────
|
|
17
|
+
/**
|
|
18
|
+
* DoS caps for the deep tarball scan. A hostile package can ship a tiny .tgz
|
|
19
|
+
* that decompresses to gigabytes (a zip/gzip "bomb"), redirect a fetch in an
|
|
20
|
+
* endless loop, or hang a socket open forever. These bounds keep the scanner
|
|
21
|
+
* from OOMing or stalling on adversarial input.
|
|
22
|
+
*/
|
|
23
|
+
/** Max bytes we will download for a tarball (compressed, on the wire). */
|
|
24
|
+
const MAX_DOWNLOAD_BYTES = 50 * 1024 * 1024; // 50 MB
|
|
25
|
+
/** Max bytes a tarball may decompress to before we abort (gzip-bomb guard). */
|
|
26
|
+
const MAX_DECOMPRESSED_BYTES = 100 * 1024 * 1024; // 100 MB
|
|
27
|
+
/** Max number of entries we will extract from a single tarball. */
|
|
28
|
+
const MAX_TARBALL_ENTRIES = 10_000;
|
|
29
|
+
/** Max HTTP redirect hops to follow before giving up. */
|
|
30
|
+
const MAX_REDIRECTS = 5;
|
|
31
|
+
/** Per-request network timeout (no response / stalled socket). */
|
|
32
|
+
const FETCH_TIMEOUT_MS = 30_000;
|
|
17
33
|
/** Popular npm packages for typosquat comparison. */
|
|
18
34
|
const POPULAR_PACKAGES = [
|
|
19
35
|
'react', 'express', 'lodash', 'axios', 'chalk', 'commander', 'debug',
|
|
@@ -26,14 +42,33 @@ const POPULAR_PACKAGES = [
|
|
|
26
42
|
'redis', 'mongodb', 'sequelize', 'prisma', 'graphql', 'apollo',
|
|
27
43
|
];
|
|
28
44
|
// ── Helpers ─────────────────────────────────────────────────
|
|
45
|
+
/**
|
|
46
|
+
* Decide whether another redirect hop may be followed. Returns the remaining
|
|
47
|
+
* budget for the next hop, or `null` when the cap is exhausted. Pure + exported
|
|
48
|
+
* so the redirect bound can be unit-tested without standing up a TLS server.
|
|
49
|
+
*/
|
|
50
|
+
export function nextRedirectBudget(redirectsLeft) {
|
|
51
|
+
if (redirectsLeft <= 0)
|
|
52
|
+
return null;
|
|
53
|
+
return redirectsLeft - 1;
|
|
54
|
+
}
|
|
29
55
|
/**
|
|
30
56
|
* Simple HTTPS GET returning the response body as a string.
|
|
57
|
+
*
|
|
58
|
+
* Bounds redirect following (max MAX_REDIRECTS hops) and applies a socket
|
|
59
|
+
* timeout so a hostile / mis-configured registry cannot loop or hang us.
|
|
31
60
|
*/
|
|
32
|
-
function httpsGet(url) {
|
|
61
|
+
export function httpsGet(url, redirectsLeft = MAX_REDIRECTS) {
|
|
33
62
|
return new Promise((resolve, reject) => {
|
|
34
|
-
https.get(url, { headers: { 'Accept': 'application/json' } }, (res) => {
|
|
63
|
+
const req = https.get(url, { headers: { 'Accept': 'application/json' } }, (res) => {
|
|
35
64
|
if (res.statusCode && res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
|
|
36
|
-
|
|
65
|
+
res.resume(); // drain the redirect response so the socket can be reused/freed
|
|
66
|
+
const budget = nextRedirectBudget(redirectsLeft);
|
|
67
|
+
if (budget === null) {
|
|
68
|
+
reject(new Error(`Too many redirects (>${MAX_REDIRECTS}) for ${url}`));
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
httpsGet(res.headers.location, budget).then(resolve, reject);
|
|
37
72
|
return;
|
|
38
73
|
}
|
|
39
74
|
if (res.statusCode !== 200) {
|
|
@@ -45,75 +80,146 @@ function httpsGet(url) {
|
|
|
45
80
|
res.on('data', (chunk) => chunks.push(chunk));
|
|
46
81
|
res.on('end', () => resolve(Buffer.concat(chunks).toString('utf-8')));
|
|
47
82
|
res.on('error', reject);
|
|
48
|
-
})
|
|
83
|
+
});
|
|
84
|
+
req.setTimeout(FETCH_TIMEOUT_MS, () => {
|
|
85
|
+
req.destroy(new Error(`Request timed out after ${FETCH_TIMEOUT_MS}ms for ${url}`));
|
|
86
|
+
});
|
|
87
|
+
req.on('error', reject);
|
|
49
88
|
});
|
|
50
89
|
}
|
|
51
90
|
/**
|
|
52
91
|
* Download a tarball to a temp file and return the path.
|
|
92
|
+
*
|
|
93
|
+
* Bounds redirect following, applies a socket timeout, and caps the number of
|
|
94
|
+
* downloaded bytes (MAX_DOWNLOAD_BYTES) so an attacker cannot fill the disk or
|
|
95
|
+
* stream forever. The compressed cap is a first line of defence; the
|
|
96
|
+
* decompressed cap in extractTarball is the gzip-bomb guard.
|
|
53
97
|
*/
|
|
54
98
|
function downloadTarball(url) {
|
|
55
99
|
return new Promise((resolve, reject) => {
|
|
56
100
|
const tmpFile = path.join(os.tmpdir(), `shieldcortex-xray-${Date.now()}.tgz`);
|
|
57
101
|
const file = fs.createWriteStream(tmpFile);
|
|
58
|
-
|
|
59
|
-
|
|
102
|
+
let settled = false;
|
|
103
|
+
const fail = (err) => {
|
|
104
|
+
if (settled)
|
|
105
|
+
return;
|
|
106
|
+
settled = true;
|
|
107
|
+
file.destroy();
|
|
108
|
+
fs.rm(tmpFile, { force: true }, () => reject(err));
|
|
109
|
+
};
|
|
110
|
+
const doGet = (targetUrl, redirectsLeft) => {
|
|
111
|
+
const req = https.get(targetUrl, (res) => {
|
|
60
112
|
if (res.statusCode && res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
|
|
61
|
-
|
|
113
|
+
res.resume();
|
|
114
|
+
if (redirectsLeft <= 0) {
|
|
115
|
+
fail(new Error(`Too many redirects (>${MAX_REDIRECTS}) downloading tarball`));
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
doGet(res.headers.location, redirectsLeft - 1);
|
|
62
119
|
return;
|
|
63
120
|
}
|
|
64
121
|
if (res.statusCode !== 200) {
|
|
65
|
-
reject(new Error(`HTTP ${res.statusCode} downloading tarball`));
|
|
66
122
|
res.resume();
|
|
123
|
+
fail(new Error(`HTTP ${res.statusCode} downloading tarball`));
|
|
67
124
|
return;
|
|
68
125
|
}
|
|
126
|
+
let downloaded = 0;
|
|
127
|
+
res.on('data', (chunk) => {
|
|
128
|
+
downloaded += chunk.length;
|
|
129
|
+
if (downloaded > MAX_DOWNLOAD_BYTES) {
|
|
130
|
+
req.destroy();
|
|
131
|
+
fail(new Error(`Tarball exceeds ${MAX_DOWNLOAD_BYTES} byte download cap`));
|
|
132
|
+
}
|
|
133
|
+
});
|
|
69
134
|
res.pipe(file);
|
|
70
135
|
file.on('finish', () => {
|
|
136
|
+
if (settled)
|
|
137
|
+
return;
|
|
138
|
+
settled = true;
|
|
71
139
|
file.close();
|
|
72
140
|
resolve(tmpFile);
|
|
73
141
|
});
|
|
74
|
-
|
|
142
|
+
res.on('error', fail);
|
|
143
|
+
});
|
|
144
|
+
req.setTimeout(FETCH_TIMEOUT_MS, () => {
|
|
145
|
+
req.destroy(new Error(`Tarball download timed out after ${FETCH_TIMEOUT_MS}ms`));
|
|
146
|
+
});
|
|
147
|
+
req.on('error', fail);
|
|
75
148
|
};
|
|
76
|
-
|
|
149
|
+
file.on('error', fail);
|
|
150
|
+
doGet(url, MAX_REDIRECTS);
|
|
77
151
|
});
|
|
78
152
|
}
|
|
79
153
|
/**
|
|
80
|
-
*
|
|
81
|
-
*
|
|
154
|
+
* Accumulate a decompressed stream into a single Buffer, aborting if the total
|
|
155
|
+
* exceeds `maxBytes`. This is the gzip-bomb guard: a hostile .tgz can be tiny
|
|
156
|
+
* on disk yet decompress to gigabytes, so we cap the INFLATED size, not just
|
|
157
|
+
* the download. Pure (no fs/network) so it can be tested with a fake stream.
|
|
82
158
|
*/
|
|
83
|
-
|
|
84
|
-
const extractDir = path.join(os.tmpdir(), `shieldcortex-xray-extract-${Date.now()}`);
|
|
85
|
-
fs.mkdirSync(extractDir, { recursive: true });
|
|
159
|
+
export function collectDecompressed(stream, maxBytes = MAX_DECOMPRESSED_BYTES) {
|
|
86
160
|
return new Promise((resolve, reject) => {
|
|
87
|
-
const gunzip = createGunzip();
|
|
88
|
-
const input = fs.createReadStream(tgzPath);
|
|
89
161
|
const chunks = [];
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
reject(
|
|
162
|
+
let total = 0;
|
|
163
|
+
let aborted = false;
|
|
164
|
+
stream.on('data', (chunk) => {
|
|
165
|
+
if (aborted)
|
|
166
|
+
return;
|
|
167
|
+
total += chunk.length;
|
|
168
|
+
if (total > maxBytes) {
|
|
169
|
+
aborted = true;
|
|
170
|
+
stream.destroy?.(new Error('aborted: decompressed size cap exceeded'));
|
|
171
|
+
reject(new Error(`Decompressed tarball exceeds ${maxBytes} byte cap (possible gzip bomb)`));
|
|
172
|
+
return;
|
|
100
173
|
}
|
|
174
|
+
chunks.push(chunk);
|
|
175
|
+
});
|
|
176
|
+
stream.on('end', () => {
|
|
177
|
+
if (aborted)
|
|
178
|
+
return;
|
|
179
|
+
resolve(Buffer.concat(chunks));
|
|
180
|
+
});
|
|
181
|
+
stream.on('error', (err) => {
|
|
182
|
+
if (aborted)
|
|
183
|
+
return;
|
|
184
|
+
reject(err);
|
|
101
185
|
});
|
|
102
|
-
gunzip.on('error', reject);
|
|
103
|
-
input.on('error', reject);
|
|
104
186
|
});
|
|
105
187
|
}
|
|
188
|
+
/**
|
|
189
|
+
* Extract a .tgz to a temp directory using Node built-ins (zlib + tar parsing).
|
|
190
|
+
* Returns path to the extracted directory.
|
|
191
|
+
*
|
|
192
|
+
* Enforces the decompressed-size cap (gzip-bomb guard) while inflating and the
|
|
193
|
+
* entry-count cap while parsing the tar.
|
|
194
|
+
*/
|
|
195
|
+
async function extractTarball(tgzPath) {
|
|
196
|
+
const extractDir = path.join(os.tmpdir(), `shieldcortex-xray-extract-${Date.now()}`);
|
|
197
|
+
fs.mkdirSync(extractDir, { recursive: true });
|
|
198
|
+
const gunzip = createGunzip();
|
|
199
|
+
const input = fs.createReadStream(tgzPath);
|
|
200
|
+
input.on('error', (err) => gunzip.destroy(err));
|
|
201
|
+
input.pipe(gunzip);
|
|
202
|
+
const tarData = await collectDecompressed(gunzip);
|
|
203
|
+
extractTarBuffer(tarData, extractDir);
|
|
204
|
+
return extractDir;
|
|
205
|
+
}
|
|
106
206
|
/**
|
|
107
207
|
* Minimal tar extraction from a buffer. Handles ustar format.
|
|
108
208
|
*/
|
|
109
209
|
function extractTarBuffer(buf, outDir) {
|
|
110
210
|
let offset = 0;
|
|
211
|
+
let entries = 0;
|
|
111
212
|
while (offset < buf.length - 512) {
|
|
112
213
|
// Read header (512 bytes)
|
|
113
214
|
const header = buf.subarray(offset, offset + 512);
|
|
114
215
|
// Check for empty block (end of archive)
|
|
115
216
|
if (header.every(b => b === 0))
|
|
116
217
|
break;
|
|
218
|
+
// Entry-count cap: a hostile tarball can pack millions of tiny entries to
|
|
219
|
+
// exhaust inodes / fd churn even within the decompressed-size cap.
|
|
220
|
+
if (++entries > MAX_TARBALL_ENTRIES) {
|
|
221
|
+
throw new Error(`Tarball exceeds ${MAX_TARBALL_ENTRIES} entry cap`);
|
|
222
|
+
}
|
|
117
223
|
// Extract filename (0-100 bytes, null-terminated)
|
|
118
224
|
const nameEnd = header.indexOf(0, 0);
|
|
119
225
|
const name = header.subarray(0, Math.min(nameEnd, 100)).toString('utf-8');
|
package/dist/xray/patterns.d.ts
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Comprehensive pattern groups for detecting hidden risk in packages, files,
|
|
5
5
|
* and metadata. Follows the same conventions as skill-scanner/patterns.ts:
|
|
6
6
|
* - safeRegexTest wrapper for every test
|
|
7
|
-
* -
|
|
7
|
+
* - Overlapping windowed scanning to bound per-regex work (ReDOS guard)
|
|
8
8
|
* - PatternGroup style with weighted confidence
|
|
9
9
|
* - One match per group is enough (break after first)
|
|
10
10
|
*/
|