sandstream-kit 1.0.0
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/LICENSE +21 -0
- package/README.md +617 -0
- package/dist/adapters/api-key-adapter.d.ts +35 -0
- package/dist/adapters/api-key-adapter.js +46 -0
- package/dist/adapters/api-key-adapter.js.map +1 -0
- package/dist/adapters/clerk-auth.d.ts +6 -0
- package/dist/adapters/clerk-auth.js +20 -0
- package/dist/adapters/clerk-auth.js.map +1 -0
- package/dist/adapters/cloudflare-r2.d.ts +6 -0
- package/dist/adapters/cloudflare-r2.js +136 -0
- package/dist/adapters/cloudflare-r2.js.map +1 -0
- package/dist/adapters/expo-eas.d.ts +6 -0
- package/dist/adapters/expo-eas.js +129 -0
- package/dist/adapters/expo-eas.js.map +1 -0
- package/dist/adapters/flagsmith-flags.d.ts +5 -0
- package/dist/adapters/flagsmith-flags.js +20 -0
- package/dist/adapters/flagsmith-flags.js.map +1 -0
- package/dist/adapters/flyio-hosting.d.ts +2 -0
- package/dist/adapters/flyio-hosting.js +143 -0
- package/dist/adapters/flyio-hosting.js.map +1 -0
- package/dist/adapters/index.d.ts +6 -0
- package/dist/adapters/index.js +48 -0
- package/dist/adapters/index.js.map +1 -0
- package/dist/adapters/inngest-background.d.ts +5 -0
- package/dist/adapters/inngest-background.js +19 -0
- package/dist/adapters/inngest-background.js.map +1 -0
- package/dist/adapters/liveblocks-realtime.d.ts +11 -0
- package/dist/adapters/liveblocks-realtime.js +62 -0
- package/dist/adapters/liveblocks-realtime.js.map +1 -0
- package/dist/adapters/loops-email.d.ts +6 -0
- package/dist/adapters/loops-email.js +18 -0
- package/dist/adapters/loops-email.js.map +1 -0
- package/dist/adapters/neon-db.d.ts +10 -0
- package/dist/adapters/neon-db.js +94 -0
- package/dist/adapters/neon-db.js.map +1 -0
- package/dist/adapters/planetscale-db.d.ts +11 -0
- package/dist/adapters/planetscale-db.js +134 -0
- package/dist/adapters/planetscale-db.js.map +1 -0
- package/dist/adapters/posthog-analytics.d.ts +6 -0
- package/dist/adapters/posthog-analytics.js +22 -0
- package/dist/adapters/posthog-analytics.js.map +1 -0
- package/dist/adapters/railway-hosting.d.ts +2 -0
- package/dist/adapters/railway-hosting.js +136 -0
- package/dist/adapters/railway-hosting.js.map +1 -0
- package/dist/adapters/resend-email.d.ts +35 -0
- package/dist/adapters/resend-email.js +109 -0
- package/dist/adapters/resend-email.js.map +1 -0
- package/dist/adapters/searxng-instance.d.ts +6 -0
- package/dist/adapters/searxng-instance.js +240 -0
- package/dist/adapters/searxng-instance.js.map +1 -0
- package/dist/adapters/sentry-monitoring.d.ts +7 -0
- package/dist/adapters/sentry-monitoring.js +27 -0
- package/dist/adapters/sentry-monitoring.js.map +1 -0
- package/dist/adapters/stripe-payments.d.ts +6 -0
- package/dist/adapters/stripe-payments.js +134 -0
- package/dist/adapters/stripe-payments.js.map +1 -0
- package/dist/adapters/supabase-db.d.ts +6 -0
- package/dist/adapters/supabase-db.js +130 -0
- package/dist/adapters/supabase-db.js.map +1 -0
- package/dist/adapters/tinybird-analytics.d.ts +5 -0
- package/dist/adapters/tinybird-analytics.js +20 -0
- package/dist/adapters/tinybird-analytics.js.map +1 -0
- package/dist/adapters/trigger-background.d.ts +6 -0
- package/dist/adapters/trigger-background.js +20 -0
- package/dist/adapters/trigger-background.js.map +1 -0
- package/dist/adapters/types.d.ts +7 -0
- package/dist/adapters/types.js +2 -0
- package/dist/adapters/types.js.map +1 -0
- package/dist/adapters/upstash-redis.d.ts +6 -0
- package/dist/adapters/upstash-redis.js +88 -0
- package/dist/adapters/upstash-redis.js.map +1 -0
- package/dist/adapters/vercel-hosting.d.ts +6 -0
- package/dist/adapters/vercel-hosting.js +112 -0
- package/dist/adapters/vercel-hosting.js.map +1 -0
- package/dist/agent-adapter-model.d.ts +108 -0
- package/dist/agent-adapter-model.js +6 -0
- package/dist/agent-adapter-model.js.map +1 -0
- package/dist/agent-adapter-service.d.ts +67 -0
- package/dist/agent-adapter-service.js +299 -0
- package/dist/agent-adapter-service.js.map +1 -0
- package/dist/agent-config.d.ts +56 -0
- package/dist/agent-config.js +129 -0
- package/dist/agent-config.js.map +1 -0
- package/dist/agent-governance-model.d.ts +128 -0
- package/dist/agent-governance-model.js +6 -0
- package/dist/agent-governance-model.js.map +1 -0
- package/dist/agent-governance-service.d.ts +101 -0
- package/dist/agent-governance-service.js +319 -0
- package/dist/agent-governance-service.js.map +1 -0
- package/dist/alert-rules-engine.d.ts +102 -0
- package/dist/alert-rules-engine.js +210 -0
- package/dist/alert-rules-engine.js.map +1 -0
- package/dist/analytics-service.d.ts +126 -0
- package/dist/analytics-service.js +318 -0
- package/dist/analytics-service.js.map +1 -0
- package/dist/analyze.d.ts +19 -0
- package/dist/analyze.js +311 -0
- package/dist/analyze.js.map +1 -0
- package/dist/apm-instrumentor.d.ts +119 -0
- package/dist/apm-instrumentor.js +225 -0
- package/dist/apm-instrumentor.js.map +1 -0
- package/dist/approval-model.d.ts +82 -0
- package/dist/approval-model.js +6 -0
- package/dist/approval-model.js.map +1 -0
- package/dist/approval-service.d.ts +39 -0
- package/dist/approval-service.js +236 -0
- package/dist/approval-service.js.map +1 -0
- package/dist/approval.d.ts +22 -0
- package/dist/approval.js +148 -0
- package/dist/approval.js.map +1 -0
- package/dist/audit-logging-model.d.ts +157 -0
- package/dist/audit-logging-model.js +6 -0
- package/dist/audit-logging-model.js.map +1 -0
- package/dist/audit-logging-service.d.ts +89 -0
- package/dist/audit-logging-service.js +367 -0
- package/dist/audit-logging-service.js.map +1 -0
- package/dist/audit-secrets.d.ts +42 -0
- package/dist/audit-secrets.js +126 -0
- package/dist/audit-secrets.js.map +1 -0
- package/dist/audit.d.ts +43 -0
- package/dist/audit.js +286 -0
- package/dist/audit.js.map +1 -0
- package/dist/author-dashboard.d.ts +84 -0
- package/dist/author-dashboard.js +204 -0
- package/dist/author-dashboard.js.map +1 -0
- package/dist/author-notifications.d.ts +130 -0
- package/dist/author-notifications.js +261 -0
- package/dist/author-notifications.js.map +1 -0
- package/dist/author-verification.d.ts +79 -0
- package/dist/author-verification.js +257 -0
- package/dist/author-verification.js.map +1 -0
- package/dist/autonomous-setup-model.d.ts +117 -0
- package/dist/autonomous-setup-model.js +6 -0
- package/dist/autonomous-setup-model.js.map +1 -0
- package/dist/autonomous-setup-service.d.ts +74 -0
- package/dist/autonomous-setup-service.js +325 -0
- package/dist/autonomous-setup-service.js.map +1 -0
- package/dist/badge-system.d.ts +70 -0
- package/dist/badge-system.js +210 -0
- package/dist/badge-system.js.map +1 -0
- package/dist/baseline.d.ts +34 -0
- package/dist/baseline.js +78 -0
- package/dist/baseline.js.map +1 -0
- package/dist/beta-program-service.d.ts +112 -0
- package/dist/beta-program-service.js +240 -0
- package/dist/beta-program-service.js.map +1 -0
- package/dist/budget.d.ts +34 -0
- package/dist/budget.js +159 -0
- package/dist/budget.js.map +1 -0
- package/dist/bumblebee.d.ts +143 -0
- package/dist/bumblebee.js +384 -0
- package/dist/bumblebee.js.map +1 -0
- package/dist/cache-manager.d.ts +97 -0
- package/dist/cache-manager.js +244 -0
- package/dist/cache-manager.js.map +1 -0
- package/dist/cdn-adapter.d.ts +64 -0
- package/dist/cdn-adapter.js +263 -0
- package/dist/cdn-adapter.js.map +1 -0
- package/dist/certification-workflow-model.d.ts +95 -0
- package/dist/certification-workflow-model.js +6 -0
- package/dist/certification-workflow-model.js.map +1 -0
- package/dist/certification-workflow-service.d.ts +72 -0
- package/dist/certification-workflow-service.js +305 -0
- package/dist/certification-workflow-service.js.map +1 -0
- package/dist/check-design.d.ts +38 -0
- package/dist/check-design.js +256 -0
- package/dist/check-design.js.map +1 -0
- package/dist/check-gitignore.d.ts +39 -0
- package/dist/check-gitignore.js +156 -0
- package/dist/check-gitignore.js.map +1 -0
- package/dist/check-hooks.d.ts +15 -0
- package/dist/check-hooks.js +72 -0
- package/dist/check-hooks.js.map +1 -0
- package/dist/check-lock.d.ts +16 -0
- package/dist/check-lock.js +94 -0
- package/dist/check-lock.js.map +1 -0
- package/dist/check-secrets.d.ts +11 -0
- package/dist/check-secrets.js +320 -0
- package/dist/check-secrets.js.map +1 -0
- package/dist/check-security.d.ts +13 -0
- package/dist/check-security.js +887 -0
- package/dist/check-security.js.map +1 -0
- package/dist/check-services.d.ts +10 -0
- package/dist/check-services.js +44 -0
- package/dist/check-services.js.map +1 -0
- package/dist/check-skills.d.ts +8 -0
- package/dist/check-skills.js +26 -0
- package/dist/check-skills.js.map +1 -0
- package/dist/check-tests.d.ts +43 -0
- package/dist/check-tests.js +175 -0
- package/dist/check-tests.js.map +1 -0
- package/dist/check-tools.d.ts +8 -0
- package/dist/check-tools.js +42 -0
- package/dist/check-tools.js.map +1 -0
- package/dist/check-web-search.d.ts +12 -0
- package/dist/check-web-search.js +168 -0
- package/dist/check-web-search.js.map +1 -0
- package/dist/ci-cd-publisher.d.ts +162 -0
- package/dist/ci-cd-publisher.js +319 -0
- package/dist/ci-cd-publisher.js.map +1 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +4074 -0
- package/dist/cli.js.map +1 -0
- package/dist/clone.d.ts +25 -0
- package/dist/clone.js +73 -0
- package/dist/clone.js.map +1 -0
- package/dist/completions.d.ts +8 -0
- package/dist/completions.js +250 -0
- package/dist/completions.js.map +1 -0
- package/dist/compression-manager.d.ts +107 -0
- package/dist/compression-manager.js +250 -0
- package/dist/compression-manager.js.map +1 -0
- package/dist/config.d.ts +233 -0
- package/dist/config.js +255 -0
- package/dist/config.js.map +1 -0
- package/dist/context.d.ts +38 -0
- package/dist/context.js +86 -0
- package/dist/context.js.map +1 -0
- package/dist/cost-monitor.d.ts +72 -0
- package/dist/cost-monitor.js +218 -0
- package/dist/cost-monitor.js.map +1 -0
- package/dist/create-plugin.d.ts +22 -0
- package/dist/create-plugin.js +266 -0
- package/dist/create-plugin.js.map +1 -0
- package/dist/database.d.ts +123 -0
- package/dist/database.js +354 -0
- package/dist/database.js.map +1 -0
- package/dist/datadog-adapter.d.ts +60 -0
- package/dist/datadog-adapter.js +245 -0
- package/dist/datadog-adapter.js.map +1 -0
- package/dist/doctor.d.ts +15 -0
- package/dist/doctor.js +131 -0
- package/dist/doctor.js.map +1 -0
- package/dist/documentation-generator.d.ts +226 -0
- package/dist/documentation-generator.js +348 -0
- package/dist/documentation-generator.js.map +1 -0
- package/dist/elevation-scopes.d.ts +40 -0
- package/dist/elevation-scopes.js +110 -0
- package/dist/elevation-scopes.js.map +1 -0
- package/dist/elevation.d.ts +102 -0
- package/dist/elevation.js +449 -0
- package/dist/elevation.js.map +1 -0
- package/dist/env-diff.d.ts +27 -0
- package/dist/env-diff.js +104 -0
- package/dist/env-diff.js.map +1 -0
- package/dist/env-inspect.d.ts +28 -0
- package/dist/env-inspect.js +81 -0
- package/dist/env-inspect.js.map +1 -0
- package/dist/env-switch.d.ts +37 -0
- package/dist/env-switch.js +102 -0
- package/dist/env-switch.js.map +1 -0
- package/dist/environment.d.ts +27 -0
- package/dist/environment.js +148 -0
- package/dist/environment.js.map +1 -0
- package/dist/error-tracker.d.ts +92 -0
- package/dist/error-tracker.js +206 -0
- package/dist/error-tracker.js.map +1 -0
- package/dist/escalate.d.ts +11 -0
- package/dist/escalate.js +73 -0
- package/dist/escalate.js.map +1 -0
- package/dist/event-stream.d.ts +81 -0
- package/dist/event-stream.js +161 -0
- package/dist/event-stream.js.map +1 -0
- package/dist/fix.d.ts +42 -0
- package/dist/fix.js +419 -0
- package/dist/fix.js.map +1 -0
- package/dist/governance-middleware.d.ts +22 -0
- package/dist/governance-middleware.js +173 -0
- package/dist/governance-middleware.js.map +1 -0
- package/dist/governance.d.ts +44 -0
- package/dist/governance.js +236 -0
- package/dist/governance.js.map +1 -0
- package/dist/hooks.d.ts +25 -0
- package/dist/hooks.js +281 -0
- package/dist/hooks.js.map +1 -0
- package/dist/id-generator.d.ts +43 -0
- package/dist/id-generator.js +47 -0
- package/dist/id-generator.js.map +1 -0
- package/dist/image-optimizer.d.ts +92 -0
- package/dist/image-optimizer.js +202 -0
- package/dist/image-optimizer.js.map +1 -0
- package/dist/install.d.ts +15 -0
- package/dist/install.js +59 -0
- package/dist/install.js.map +1 -0
- package/dist/lock.d.ts +82 -0
- package/dist/lock.js +264 -0
- package/dist/lock.js.map +1 -0
- package/dist/login.d.ts +23 -0
- package/dist/login.js +132 -0
- package/dist/login.js.map +1 -0
- package/dist/mcp-kit-tools-model.d.ts +195 -0
- package/dist/mcp-kit-tools-model.js +6 -0
- package/dist/mcp-kit-tools-model.js.map +1 -0
- package/dist/mcp-kit-tools-service.d.ts +127 -0
- package/dist/mcp-kit-tools-service.js +943 -0
- package/dist/mcp-kit-tools-service.js.map +1 -0
- package/dist/mcp-orchestrator.d.ts +70 -0
- package/dist/mcp-orchestrator.js +175 -0
- package/dist/mcp-orchestrator.js.map +1 -0
- package/dist/mcp-server.d.ts +3 -0
- package/dist/mcp-server.js +722 -0
- package/dist/mcp-server.js.map +1 -0
- package/dist/middleware/rate-limiter.d.ts +74 -0
- package/dist/middleware/rate-limiter.js +342 -0
- package/dist/middleware/rate-limiter.js.map +1 -0
- package/dist/migration-runner.d.ts +66 -0
- package/dist/migration-runner.js +192 -0
- package/dist/migration-runner.js.map +1 -0
- package/dist/migrations.d.ts +25 -0
- package/dist/migrations.js +530 -0
- package/dist/migrations.js.map +1 -0
- package/dist/moderation-system.d.ts +153 -0
- package/dist/moderation-system.js +338 -0
- package/dist/moderation-system.js.map +1 -0
- package/dist/multi-agent-workflow-model.d.ts +125 -0
- package/dist/multi-agent-workflow-model.js +6 -0
- package/dist/multi-agent-workflow-model.js.map +1 -0
- package/dist/multi-agent-workflow-service.d.ts +102 -0
- package/dist/multi-agent-workflow-service.js +452 -0
- package/dist/multi-agent-workflow-service.js.map +1 -0
- package/dist/onepassword.d.ts +75 -0
- package/dist/onepassword.js +140 -0
- package/dist/onepassword.js.map +1 -0
- package/dist/open.d.ts +30 -0
- package/dist/open.js +166 -0
- package/dist/open.js.map +1 -0
- package/dist/output.d.ts +32 -0
- package/dist/output.js +295 -0
- package/dist/output.js.map +1 -0
- package/dist/partner-service.d.ts +101 -0
- package/dist/partner-service.js +191 -0
- package/dist/partner-service.js.map +1 -0
- package/dist/payout-service.d.ts +136 -0
- package/dist/payout-service.js +293 -0
- package/dist/payout-service.js.map +1 -0
- package/dist/pkg.d.ts +30 -0
- package/dist/pkg.js +162 -0
- package/dist/pkg.js.map +1 -0
- package/dist/plugin-loader.d.ts +16 -0
- package/dist/plugin-loader.js +124 -0
- package/dist/plugin-loader.js.map +1 -0
- package/dist/plugin-registry-model.d.ts +133 -0
- package/dist/plugin-registry-model.js +6 -0
- package/dist/plugin-registry-model.js.map +1 -0
- package/dist/plugin-registry-service.d.ts +109 -0
- package/dist/plugin-registry-service.js +361 -0
- package/dist/plugin-registry-service.js.map +1 -0
- package/dist/plugin-registry.d.ts +58 -0
- package/dist/plugin-registry.js +108 -0
- package/dist/plugin-registry.js.map +1 -0
- package/dist/plugin-updates.d.ts +135 -0
- package/dist/plugin-updates.js +326 -0
- package/dist/plugin-updates.js.map +1 -0
- package/dist/plugins-cli.d.ts +7 -0
- package/dist/plugins-cli.js +157 -0
- package/dist/plugins-cli.js.map +1 -0
- package/dist/plugins.d.ts +88 -0
- package/dist/plugins.js +251 -0
- package/dist/plugins.js.map +1 -0
- package/dist/policy.d.ts +66 -0
- package/dist/policy.js +160 -0
- package/dist/policy.js.map +1 -0
- package/dist/post-pull-audit.d.ts +39 -0
- package/dist/post-pull-audit.js +151 -0
- package/dist/post-pull-audit.js.map +1 -0
- package/dist/provision.d.ts +17 -0
- package/dist/provision.js +147 -0
- package/dist/provision.js.map +1 -0
- package/dist/query-optimizer.d.ts +102 -0
- package/dist/query-optimizer.js +199 -0
- package/dist/query-optimizer.js.map +1 -0
- package/dist/read-only-mode.d.ts +46 -0
- package/dist/read-only-mode.js +71 -0
- package/dist/read-only-mode.js.map +1 -0
- package/dist/redis-adapter.d.ts +71 -0
- package/dist/redis-adapter.js +278 -0
- package/dist/redis-adapter.js.map +1 -0
- package/dist/resilience-tests.d.ts +120 -0
- package/dist/resilience-tests.js +293 -0
- package/dist/resilience-tests.js.map +1 -0
- package/dist/revocation.d.ts +22 -0
- package/dist/revocation.js +100 -0
- package/dist/revocation.js.map +1 -0
- package/dist/run.d.ts +21 -0
- package/dist/run.js +80 -0
- package/dist/run.js.map +1 -0
- package/dist/scan-build.d.ts +18 -0
- package/dist/scan-build.js +100 -0
- package/dist/scan-build.js.map +1 -0
- package/dist/scan-plaintext.d.ts +24 -0
- package/dist/scan-plaintext.js +147 -0
- package/dist/scan-plaintext.js.map +1 -0
- package/dist/scan-staged.d.ts +15 -0
- package/dist/scan-staged.js +70 -0
- package/dist/scan-staged.js.map +1 -0
- package/dist/scan-transcripts.d.ts +23 -0
- package/dist/scan-transcripts.js +93 -0
- package/dist/scan-transcripts.js.map +1 -0
- package/dist/secret-backends.d.ts +50 -0
- package/dist/secret-backends.js +510 -0
- package/dist/secret-backends.js.map +1 -0
- package/dist/secret-expiration.d.ts +46 -0
- package/dist/secret-expiration.js +172 -0
- package/dist/secret-expiration.js.map +1 -0
- package/dist/secrets-migrate.d.ts +75 -0
- package/dist/secrets-migrate.js +185 -0
- package/dist/secrets-migrate.js.map +1 -0
- package/dist/secrets-model.d.ts +77 -0
- package/dist/secrets-model.js +6 -0
- package/dist/secrets-model.js.map +1 -0
- package/dist/secrets-onecli.d.ts +65 -0
- package/dist/secrets-onecli.js +113 -0
- package/dist/secrets-onecli.js.map +1 -0
- package/dist/secrets-propagate.d.ts +48 -0
- package/dist/secrets-propagate.js +201 -0
- package/dist/secrets-propagate.js.map +1 -0
- package/dist/secrets-pull.d.ts +34 -0
- package/dist/secrets-pull.js +118 -0
- package/dist/secrets-pull.js.map +1 -0
- package/dist/secrets-purge-history.d.ts +53 -0
- package/dist/secrets-purge-history.js +144 -0
- package/dist/secrets-purge-history.js.map +1 -0
- package/dist/secrets-rotate-cli.d.ts +54 -0
- package/dist/secrets-rotate-cli.js +438 -0
- package/dist/secrets-rotate-cli.js.map +1 -0
- package/dist/secrets-rotate.d.ts +38 -0
- package/dist/secrets-rotate.js +65 -0
- package/dist/secrets-rotate.js.map +1 -0
- package/dist/secrets-service.d.ts +73 -0
- package/dist/secrets-service.js +283 -0
- package/dist/secrets-service.js.map +1 -0
- package/dist/secrets-set.d.ts +25 -0
- package/dist/secrets-set.js +33 -0
- package/dist/secrets-set.js.map +1 -0
- package/dist/secrets-sync.d.ts +21 -0
- package/dist/secrets-sync.js +215 -0
- package/dist/secrets-sync.js.map +1 -0
- package/dist/secrets-validate.d.ts +41 -0
- package/dist/secrets-validate.js +126 -0
- package/dist/secrets-validate.js.map +1 -0
- package/dist/secrets-vault-migrate.d.ts +71 -0
- package/dist/secrets-vault-migrate.js +258 -0
- package/dist/secrets-vault-migrate.js.map +1 -0
- package/dist/secrets.d.ts +16 -0
- package/dist/secrets.js +72 -0
- package/dist/secrets.js.map +1 -0
- package/dist/security-hardening.d.ts +150 -0
- package/dist/security-hardening.js +275 -0
- package/dist/security-hardening.js.map +1 -0
- package/dist/security-policy.d.ts +89 -0
- package/dist/security-policy.js +174 -0
- package/dist/security-policy.js.map +1 -0
- package/dist/security-prescan.d.ts +117 -0
- package/dist/security-prescan.js +566 -0
- package/dist/security-prescan.js.map +1 -0
- package/dist/sentry-adapter.d.ts +49 -0
- package/dist/sentry-adapter.js +227 -0
- package/dist/sentry-adapter.js.map +1 -0
- package/dist/service-adapter.d.ts +94 -0
- package/dist/service-adapter.js +162 -0
- package/dist/service-adapter.js.map +1 -0
- package/dist/skills.d.ts +13 -0
- package/dist/skills.js +17 -0
- package/dist/skills.js.map +1 -0
- package/dist/sla-monitor.d.ts +107 -0
- package/dist/sla-monitor.js +233 -0
- package/dist/sla-monitor.js.map +1 -0
- package/dist/stack-detector.d.ts +12 -0
- package/dist/stack-detector.js +251 -0
- package/dist/stack-detector.js.map +1 -0
- package/dist/team-model.d.ts +58 -0
- package/dist/team-model.js +83 -0
- package/dist/team-model.js.map +1 -0
- package/dist/team-service.d.ts +54 -0
- package/dist/team-service.js +206 -0
- package/dist/team-service.js.map +1 -0
- package/dist/toml-generator.d.ts +8 -0
- package/dist/toml-generator.js +223 -0
- package/dist/toml-generator.js.map +1 -0
- package/dist/triage-sandbox.d.ts +34 -0
- package/dist/triage-sandbox.js +167 -0
- package/dist/triage-sandbox.js.map +1 -0
- package/dist/triage.d.ts +30 -0
- package/dist/triage.js +79 -0
- package/dist/triage.js.map +1 -0
- package/dist/update-check.d.ts +13 -0
- package/dist/update-check.js +91 -0
- package/dist/update-check.js.map +1 -0
- package/dist/utils/colors.d.ts +14 -0
- package/dist/utils/colors.js +15 -0
- package/dist/utils/colors.js.map +1 -0
- package/dist/utils/didYouMean.d.ts +15 -0
- package/dist/utils/didYouMean.js +47 -0
- package/dist/utils/didYouMean.js.map +1 -0
- package/dist/utils/exec.d.ts +21 -0
- package/dist/utils/exec.js +23 -0
- package/dist/utils/exec.js.map +1 -0
- package/dist/utils/execFileNoThrow.d.ts +14 -0
- package/dist/utils/execFileNoThrow.js +29 -0
- package/dist/utils/execFileNoThrow.js.map +1 -0
- package/dist/utils/flags.d.ts +19 -0
- package/dist/utils/flags.js +36 -0
- package/dist/utils/flags.js.map +1 -0
- package/dist/utils/parseCommand.d.ts +16 -0
- package/dist/utils/parseCommand.js +13 -0
- package/dist/utils/parseCommand.js.map +1 -0
- package/dist/utils/prompt.d.ts +13 -0
- package/dist/utils/prompt.js +35 -0
- package/dist/utils/prompt.js.map +1 -0
- package/dist/utils/promptSelect.d.ts +19 -0
- package/dist/utils/promptSelect.js +89 -0
- package/dist/utils/promptSelect.js.map +1 -0
- package/dist/utils/redactSecrets.d.ts +24 -0
- package/dist/utils/redactSecrets.js +134 -0
- package/dist/utils/redactSecrets.js.map +1 -0
- package/dist/validation/dynamic-schema.d.ts +29 -0
- package/dist/validation/dynamic-schema.js +76 -0
- package/dist/validation/dynamic-schema.js.map +1 -0
- package/package.json +52 -0
|
@@ -0,0 +1,887 @@
|
|
|
1
|
+
import { execFile } from "node:child_process";
|
|
2
|
+
import { promisify } from "node:util";
|
|
3
|
+
import { readFile, access } from "node:fs/promises";
|
|
4
|
+
import { resolve } from "node:path";
|
|
5
|
+
import { execFileNoThrow } from "./utils/execFileNoThrow.js";
|
|
6
|
+
import { ensureBumblebee, runScan, maxSeverity, newestCatalogMtime, isCatalogStale, } from "./bumblebee.js";
|
|
7
|
+
const exec = promisify(execFile);
|
|
8
|
+
function envFlagDisabled(value) {
|
|
9
|
+
if (!value)
|
|
10
|
+
return false;
|
|
11
|
+
return ["0", "false", "off", "no"].includes(value.toLowerCase());
|
|
12
|
+
}
|
|
13
|
+
function envFlagEnabled(value) {
|
|
14
|
+
if (!value)
|
|
15
|
+
return false;
|
|
16
|
+
return ["1", "true", "on", "yes"].includes(value.toLowerCase());
|
|
17
|
+
}
|
|
18
|
+
/** Map a bumblebee severity label to the SecurityCheckResult severity scale. */
|
|
19
|
+
function toResultSeverity(label) {
|
|
20
|
+
switch ((label ?? "").toLowerCase()) {
|
|
21
|
+
case "critical":
|
|
22
|
+
return "critical";
|
|
23
|
+
case "high":
|
|
24
|
+
return "high";
|
|
25
|
+
case "medium":
|
|
26
|
+
return "medium";
|
|
27
|
+
case "low":
|
|
28
|
+
return "low";
|
|
29
|
+
default:
|
|
30
|
+
// A known-compromise match with an unrecognized label is still serious.
|
|
31
|
+
return "high";
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Run npm audit and check for high/critical vulnerabilities
|
|
36
|
+
*/
|
|
37
|
+
async function checkNpmAudit() {
|
|
38
|
+
try {
|
|
39
|
+
// Check if package.json exists
|
|
40
|
+
await access(resolve(process.cwd(), "package.json"));
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
return {
|
|
44
|
+
category: "dependency",
|
|
45
|
+
name: "npm audit",
|
|
46
|
+
status: "skip",
|
|
47
|
+
detail: "no package.json found",
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
try {
|
|
51
|
+
await exec("npm", ["audit", "--audit-level=high", "--json"], {
|
|
52
|
+
timeout: 30_000,
|
|
53
|
+
});
|
|
54
|
+
return {
|
|
55
|
+
category: "dependency",
|
|
56
|
+
name: "npm audit",
|
|
57
|
+
status: "pass",
|
|
58
|
+
detail: "no high/critical vulnerabilities",
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
catch (error) {
|
|
62
|
+
if (error && typeof error === "object" && "stdout" in error) {
|
|
63
|
+
try {
|
|
64
|
+
const auditResult = JSON.parse(error.stdout);
|
|
65
|
+
const vulnerabilities = auditResult.metadata?.vulnerabilities || {};
|
|
66
|
+
const high = vulnerabilities.high || 0;
|
|
67
|
+
const critical = vulnerabilities.critical || 0;
|
|
68
|
+
if (high > 0 || critical > 0) {
|
|
69
|
+
return {
|
|
70
|
+
category: "dependency",
|
|
71
|
+
name: "npm audit",
|
|
72
|
+
status: "fail",
|
|
73
|
+
detail: `${critical} critical, ${high} high vulnerabilities`,
|
|
74
|
+
severity: critical > 0 ? "critical" : "high",
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
catch {
|
|
79
|
+
// JSON parse failed, treat as fail
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
return {
|
|
83
|
+
category: "dependency",
|
|
84
|
+
name: "npm audit",
|
|
85
|
+
status: "fail",
|
|
86
|
+
detail: "audit check failed",
|
|
87
|
+
severity: "high",
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Run pip-audit for Python dependencies
|
|
93
|
+
*/
|
|
94
|
+
async function checkPipAudit() {
|
|
95
|
+
try {
|
|
96
|
+
// Check if requirements.txt exists
|
|
97
|
+
await access(resolve(process.cwd(), "requirements.txt"));
|
|
98
|
+
}
|
|
99
|
+
catch {
|
|
100
|
+
return {
|
|
101
|
+
category: "dependency",
|
|
102
|
+
name: "pip-audit",
|
|
103
|
+
status: "skip",
|
|
104
|
+
detail: "no requirements.txt found",
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
try {
|
|
108
|
+
// Check if pip-audit is installed
|
|
109
|
+
await exec("pip-audit", ["--version"], { timeout: 5_000 });
|
|
110
|
+
}
|
|
111
|
+
catch {
|
|
112
|
+
return {
|
|
113
|
+
category: "dependency",
|
|
114
|
+
name: "pip-audit",
|
|
115
|
+
status: "warn",
|
|
116
|
+
detail: "pip-audit not installed (run: pip install pip-audit)",
|
|
117
|
+
severity: "medium",
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
try {
|
|
121
|
+
const { stdout } = await exec("pip-audit", ["--format=json"], {
|
|
122
|
+
timeout: 30_000,
|
|
123
|
+
});
|
|
124
|
+
const result = JSON.parse(stdout);
|
|
125
|
+
const vulns = result.dependencies || [];
|
|
126
|
+
if (vulns.length === 0) {
|
|
127
|
+
return {
|
|
128
|
+
category: "dependency",
|
|
129
|
+
name: "pip-audit",
|
|
130
|
+
status: "pass",
|
|
131
|
+
detail: "no vulnerabilities found",
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
const highSeverity = vulns.filter((v) => v.vulnerabilities?.some(vuln => vuln.severity === "high" || vuln.severity === "critical")).length;
|
|
135
|
+
return {
|
|
136
|
+
category: "dependency",
|
|
137
|
+
name: "pip-audit",
|
|
138
|
+
status: highSeverity > 0 ? "fail" : "warn",
|
|
139
|
+
detail: `${vulns.length} vulnerable dependencies`,
|
|
140
|
+
severity: highSeverity > 0 ? "high" : "medium",
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
catch {
|
|
144
|
+
return {
|
|
145
|
+
category: "dependency",
|
|
146
|
+
name: "pip-audit",
|
|
147
|
+
status: "fail",
|
|
148
|
+
detail: "audit check failed",
|
|
149
|
+
severity: "high",
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Check if .env files are in .gitignore
|
|
155
|
+
*/
|
|
156
|
+
async function checkEnvGitignored() {
|
|
157
|
+
try {
|
|
158
|
+
const gitignoreContent = await readFile(resolve(process.cwd(), ".gitignore"), "utf-8");
|
|
159
|
+
const envPatterns = [".env", ".env.local", ".env.*.local"];
|
|
160
|
+
const missingPatterns = envPatterns.filter(pattern => !gitignoreContent.includes(pattern));
|
|
161
|
+
if (missingPatterns.length === 0) {
|
|
162
|
+
return {
|
|
163
|
+
category: "secrets",
|
|
164
|
+
name: ".env gitignored",
|
|
165
|
+
status: "pass",
|
|
166
|
+
detail: "all .env patterns in .gitignore",
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
return {
|
|
170
|
+
category: "secrets",
|
|
171
|
+
name: ".env gitignored",
|
|
172
|
+
status: "warn",
|
|
173
|
+
detail: `missing patterns: ${missingPatterns.join(", ")}`,
|
|
174
|
+
severity: "high",
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
catch {
|
|
178
|
+
return {
|
|
179
|
+
category: "secrets",
|
|
180
|
+
name: ".env gitignored",
|
|
181
|
+
status: "warn",
|
|
182
|
+
detail: ".gitignore not found",
|
|
183
|
+
severity: "medium",
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* Check if package-lock.json or requirements.txt are committed
|
|
189
|
+
*/
|
|
190
|
+
async function checkLockfilesCommitted() {
|
|
191
|
+
const results = [];
|
|
192
|
+
// Check package-lock.json
|
|
193
|
+
try {
|
|
194
|
+
await access(resolve(process.cwd(), "package.json"));
|
|
195
|
+
try {
|
|
196
|
+
const { stdout } = await exec("git", ["ls-files", "package-lock.json"], {
|
|
197
|
+
timeout: 5_000,
|
|
198
|
+
});
|
|
199
|
+
if (stdout.trim()) {
|
|
200
|
+
results.push({
|
|
201
|
+
category: "supply-chain",
|
|
202
|
+
name: "package-lock.json",
|
|
203
|
+
status: "pass",
|
|
204
|
+
detail: "committed to git",
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
else {
|
|
208
|
+
results.push({
|
|
209
|
+
category: "supply-chain",
|
|
210
|
+
name: "package-lock.json",
|
|
211
|
+
status: "fail",
|
|
212
|
+
detail: "not committed to git",
|
|
213
|
+
severity: "high",
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
catch {
|
|
218
|
+
results.push({
|
|
219
|
+
category: "supply-chain",
|
|
220
|
+
name: "package-lock.json",
|
|
221
|
+
status: "warn",
|
|
222
|
+
detail: "git check failed (not in a git repo?)",
|
|
223
|
+
severity: "low",
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
catch {
|
|
228
|
+
// No package.json, skip
|
|
229
|
+
}
|
|
230
|
+
// Check requirements.txt
|
|
231
|
+
try {
|
|
232
|
+
await access(resolve(process.cwd(), "requirements.txt"));
|
|
233
|
+
try {
|
|
234
|
+
const { stdout } = await exec("git", ["ls-files", "requirements.txt"], {
|
|
235
|
+
timeout: 5_000,
|
|
236
|
+
});
|
|
237
|
+
if (stdout.trim()) {
|
|
238
|
+
results.push({
|
|
239
|
+
category: "supply-chain",
|
|
240
|
+
name: "requirements.txt",
|
|
241
|
+
status: "pass",
|
|
242
|
+
detail: "committed to git",
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
else {
|
|
246
|
+
results.push({
|
|
247
|
+
category: "supply-chain",
|
|
248
|
+
name: "requirements.txt",
|
|
249
|
+
status: "fail",
|
|
250
|
+
detail: "not committed to git",
|
|
251
|
+
severity: "high",
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
catch {
|
|
256
|
+
results.push({
|
|
257
|
+
category: "supply-chain",
|
|
258
|
+
name: "requirements.txt",
|
|
259
|
+
status: "warn",
|
|
260
|
+
detail: "git check failed (not in a git repo?)",
|
|
261
|
+
severity: "low",
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
catch {
|
|
266
|
+
// No requirements.txt, skip
|
|
267
|
+
}
|
|
268
|
+
return results;
|
|
269
|
+
}
|
|
270
|
+
/**
|
|
271
|
+
* Check if local services are exposed to internet
|
|
272
|
+
*/
|
|
273
|
+
async function checkServiceExposure() {
|
|
274
|
+
const results = [];
|
|
275
|
+
// Check Ollama (common port 11434)
|
|
276
|
+
try {
|
|
277
|
+
const { stdout: ollamaCheck } = await exec("sh", ["-c", "command -v ollama && ollama ps 2>/dev/null || echo 'not running'"], { timeout: 5_000 });
|
|
278
|
+
if (ollamaCheck.includes("not running")) {
|
|
279
|
+
results.push({
|
|
280
|
+
category: "exposure",
|
|
281
|
+
name: "Ollama",
|
|
282
|
+
status: "skip",
|
|
283
|
+
detail: "not running",
|
|
284
|
+
});
|
|
285
|
+
}
|
|
286
|
+
else {
|
|
287
|
+
// Check if listening on 0.0.0.0 (exposed) or 127.0.0.1 (localhost only)
|
|
288
|
+
try {
|
|
289
|
+
const { stdout: netstat } = await exec("sh", ["-c", "ss -tlnp 2>/dev/null | grep :11434 || netstat -tlnp 2>/dev/null | grep :11434 || echo 'no listener'"], { timeout: 5_000 });
|
|
290
|
+
if (netstat.includes("0.0.0.0:11434") || netstat.includes(":::11434")) {
|
|
291
|
+
results.push({
|
|
292
|
+
category: "exposure",
|
|
293
|
+
name: "Ollama",
|
|
294
|
+
status: "warn",
|
|
295
|
+
detail: "exposed on all interfaces (0.0.0.0)",
|
|
296
|
+
severity: "medium",
|
|
297
|
+
});
|
|
298
|
+
}
|
|
299
|
+
else if (netstat.includes("127.0.0.1:11434")) {
|
|
300
|
+
results.push({
|
|
301
|
+
category: "exposure",
|
|
302
|
+
name: "Ollama",
|
|
303
|
+
status: "pass",
|
|
304
|
+
detail: "localhost only",
|
|
305
|
+
});
|
|
306
|
+
}
|
|
307
|
+
else {
|
|
308
|
+
results.push({
|
|
309
|
+
category: "exposure",
|
|
310
|
+
name: "Ollama",
|
|
311
|
+
status: "skip",
|
|
312
|
+
detail: "could not determine exposure",
|
|
313
|
+
});
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
catch {
|
|
317
|
+
results.push({
|
|
318
|
+
category: "exposure",
|
|
319
|
+
name: "Ollama",
|
|
320
|
+
status: "skip",
|
|
321
|
+
detail: "could not check network exposure",
|
|
322
|
+
});
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
catch {
|
|
327
|
+
results.push({
|
|
328
|
+
category: "exposure",
|
|
329
|
+
name: "Ollama",
|
|
330
|
+
status: "skip",
|
|
331
|
+
detail: "not installed",
|
|
332
|
+
});
|
|
333
|
+
}
|
|
334
|
+
// Check Remote API (common port 3199)
|
|
335
|
+
try {
|
|
336
|
+
const { stdout: netstat } = await exec("sh", ["-c", "ss -tlnp 2>/dev/null | grep :3199 || netstat -tlnp 2>/dev/null | grep :3199 || echo 'no listener'"], { timeout: 5_000 });
|
|
337
|
+
if (netstat.includes("no listener")) {
|
|
338
|
+
results.push({
|
|
339
|
+
category: "exposure",
|
|
340
|
+
name: "Remote API",
|
|
341
|
+
status: "skip",
|
|
342
|
+
detail: "not running on port 3199",
|
|
343
|
+
});
|
|
344
|
+
}
|
|
345
|
+
else if (netstat.includes("0.0.0.0:3199") || netstat.includes(":::3199")) {
|
|
346
|
+
results.push({
|
|
347
|
+
category: "exposure",
|
|
348
|
+
name: "Remote API",
|
|
349
|
+
status: "warn",
|
|
350
|
+
detail: "exposed on all interfaces (verify firewall)",
|
|
351
|
+
severity: "medium",
|
|
352
|
+
});
|
|
353
|
+
}
|
|
354
|
+
else {
|
|
355
|
+
results.push({
|
|
356
|
+
category: "exposure",
|
|
357
|
+
name: "Remote API",
|
|
358
|
+
status: "pass",
|
|
359
|
+
detail: "localhost only",
|
|
360
|
+
});
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
catch {
|
|
364
|
+
results.push({
|
|
365
|
+
category: "exposure",
|
|
366
|
+
name: "Remote API",
|
|
367
|
+
status: "skip",
|
|
368
|
+
detail: "could not check network exposure",
|
|
369
|
+
});
|
|
370
|
+
}
|
|
371
|
+
return results;
|
|
372
|
+
}
|
|
373
|
+
/**
|
|
374
|
+
* Check if dependencies use pinned versions
|
|
375
|
+
*/
|
|
376
|
+
async function checkPinnedVersions() {
|
|
377
|
+
const unpinned = [];
|
|
378
|
+
// Check package.json
|
|
379
|
+
try {
|
|
380
|
+
const packageJsonContent = await readFile(resolve(process.cwd(), "package.json"), "utf-8");
|
|
381
|
+
const packageJson = JSON.parse(packageJsonContent);
|
|
382
|
+
const checkDeps = (deps) => {
|
|
383
|
+
if (!deps)
|
|
384
|
+
return;
|
|
385
|
+
for (const [name, version] of Object.entries(deps)) {
|
|
386
|
+
// Check for range specifiers: ^, ~, >, <, >=, <=, *, x
|
|
387
|
+
if (/^[~^><=*x]|[*x]$/.test(version)) {
|
|
388
|
+
unpinned.push(`${name}@${version}`);
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
};
|
|
392
|
+
checkDeps(packageJson.dependencies);
|
|
393
|
+
checkDeps(packageJson.devDependencies);
|
|
394
|
+
}
|
|
395
|
+
catch {
|
|
396
|
+
// No package.json or parse error
|
|
397
|
+
}
|
|
398
|
+
// Check requirements.txt
|
|
399
|
+
try {
|
|
400
|
+
const requirementsContent = await readFile(resolve(process.cwd(), "requirements.txt"), "utf-8");
|
|
401
|
+
for (const line of requirementsContent.split("\n")) {
|
|
402
|
+
const trimmed = line.trim();
|
|
403
|
+
if (!trimmed || trimmed.startsWith("#"))
|
|
404
|
+
continue;
|
|
405
|
+
// Check for range specifiers: >=, >, ~=, !=
|
|
406
|
+
if (/[>~!]=?/.test(trimmed)) {
|
|
407
|
+
unpinned.push(trimmed.split(/\s+/)[0]);
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
catch {
|
|
412
|
+
// No requirements.txt or read error
|
|
413
|
+
}
|
|
414
|
+
if (unpinned.length > 0) {
|
|
415
|
+
return {
|
|
416
|
+
category: "supply-chain",
|
|
417
|
+
name: "pinned versions",
|
|
418
|
+
status: "warn",
|
|
419
|
+
detail: `${unpinned.length} unpinned dependencies`,
|
|
420
|
+
severity: "medium",
|
|
421
|
+
};
|
|
422
|
+
}
|
|
423
|
+
return {
|
|
424
|
+
category: "supply-chain",
|
|
425
|
+
name: "pinned versions",
|
|
426
|
+
status: "pass",
|
|
427
|
+
detail: "all dependencies pinned",
|
|
428
|
+
};
|
|
429
|
+
}
|
|
430
|
+
/**
|
|
431
|
+
* Scan for secrets in code using trufflehog or basic pattern matching
|
|
432
|
+
*/
|
|
433
|
+
async function checkSecretsInCode() {
|
|
434
|
+
try {
|
|
435
|
+
// Check if we're in a git repo
|
|
436
|
+
await exec("git", ["rev-parse", "--git-dir"], { timeout: 5_000 });
|
|
437
|
+
}
|
|
438
|
+
catch {
|
|
439
|
+
return {
|
|
440
|
+
category: "secrets",
|
|
441
|
+
name: "secrets scan",
|
|
442
|
+
status: "skip",
|
|
443
|
+
detail: "not a git repository",
|
|
444
|
+
};
|
|
445
|
+
}
|
|
446
|
+
// Try trufflehog first
|
|
447
|
+
try {
|
|
448
|
+
await exec("trufflehog", ["--version"], { timeout: 5_000 });
|
|
449
|
+
try {
|
|
450
|
+
const { stdout } = await exec("trufflehog", ["filesystem", ".", "--json", "--no-update"], { timeout: 60_000 });
|
|
451
|
+
const findings = stdout.trim().split("\n").filter(Boolean);
|
|
452
|
+
if (findings.length > 0) {
|
|
453
|
+
return {
|
|
454
|
+
category: "secrets",
|
|
455
|
+
name: "secrets scan",
|
|
456
|
+
status: "fail",
|
|
457
|
+
detail: `${findings.length} potential secret(s) found`,
|
|
458
|
+
severity: "critical",
|
|
459
|
+
};
|
|
460
|
+
}
|
|
461
|
+
return {
|
|
462
|
+
category: "secrets",
|
|
463
|
+
name: "secrets scan",
|
|
464
|
+
status: "pass",
|
|
465
|
+
detail: "no secrets detected (trufflehog)",
|
|
466
|
+
};
|
|
467
|
+
}
|
|
468
|
+
catch {
|
|
469
|
+
return {
|
|
470
|
+
category: "secrets",
|
|
471
|
+
name: "secrets scan",
|
|
472
|
+
status: "warn",
|
|
473
|
+
detail: "trufflehog scan failed",
|
|
474
|
+
severity: "medium",
|
|
475
|
+
};
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
catch {
|
|
479
|
+
// Trufflehog not installed, use basic pattern matching
|
|
480
|
+
try {
|
|
481
|
+
const { stdout } = await exec("git", [
|
|
482
|
+
"grep",
|
|
483
|
+
"-n",
|
|
484
|
+
"-iE",
|
|
485
|
+
"(api[_-]?key|secret[_-]?key|password|token|credential)[\"']?\\s*[:=]\\s*[\"'][^\"']{20,}",
|
|
486
|
+
], { timeout: 10_000 });
|
|
487
|
+
if (stdout.trim()) {
|
|
488
|
+
const lines = stdout.trim().split("\n");
|
|
489
|
+
const matches = lines.length;
|
|
490
|
+
// Extract unique filenames
|
|
491
|
+
const files = new Set();
|
|
492
|
+
for (const line of lines) {
|
|
493
|
+
const match = line.match(/^([^:]+):/);
|
|
494
|
+
if (match) {
|
|
495
|
+
files.add(match[1]);
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
const fileArray = Array.from(files);
|
|
499
|
+
return {
|
|
500
|
+
category: "secrets",
|
|
501
|
+
name: "secrets scan",
|
|
502
|
+
status: "warn",
|
|
503
|
+
detail: `${matches} potential secret(s) in ${files.size} file(s)`,
|
|
504
|
+
severity: "high",
|
|
505
|
+
files: fileArray,
|
|
506
|
+
suggestion: "Install trufflehog for better detection:\n • macOS/Linux: brew install trufflehog\n • Go: go install github.com/trufflesecurity/trufflehog/v3@latest\n • Or download from: https://github.com/trufflesecurity/trufflehog/releases",
|
|
507
|
+
};
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
catch {
|
|
511
|
+
// No matches or git grep failed
|
|
512
|
+
}
|
|
513
|
+
return {
|
|
514
|
+
category: "secrets",
|
|
515
|
+
name: "secrets scan",
|
|
516
|
+
status: "pass",
|
|
517
|
+
detail: "basic scan passed (install trufflehog for better detection: brew install trufflehog)",
|
|
518
|
+
};
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
/**
|
|
522
|
+
* Check for supply chain attacks using Socket CLI.
|
|
523
|
+
* Detects behavioral anomalies (obfuscated files, unexpected network calls, install scripts)
|
|
524
|
+
* that npm audit misses -catches intentional malware like node-ipc and compromised packages.
|
|
525
|
+
*/
|
|
526
|
+
async function checkSocket() {
|
|
527
|
+
try {
|
|
528
|
+
await access(resolve(process.cwd(), "package.json"));
|
|
529
|
+
}
|
|
530
|
+
catch {
|
|
531
|
+
return { category: "supply-chain", name: "socket scan", status: "skip", detail: "no package.json found" };
|
|
532
|
+
}
|
|
533
|
+
const versionCheck = await execFileNoThrow("socket", ["--version"], { timeout: 5_000 });
|
|
534
|
+
if (!versionCheck.ok) {
|
|
535
|
+
return {
|
|
536
|
+
category: "supply-chain",
|
|
537
|
+
name: "socket scan",
|
|
538
|
+
status: "warn",
|
|
539
|
+
detail: "socket not installed -supply chain malware undetected",
|
|
540
|
+
severity: "medium",
|
|
541
|
+
suggestion: "npm install -g @socketsecurity/cli",
|
|
542
|
+
};
|
|
543
|
+
}
|
|
544
|
+
const result = await execFileNoThrow("socket", ["check", "--json"], { timeout: 60_000 });
|
|
545
|
+
const raw = result.stdout || result.stderr;
|
|
546
|
+
try {
|
|
547
|
+
const parsed = JSON.parse(raw);
|
|
548
|
+
const issues = parsed.issues ?? parsed.alerts ?? [];
|
|
549
|
+
const critical = issues.filter((i) => i.severity === "critical" || i.severity === "high");
|
|
550
|
+
if (critical.length > 0) {
|
|
551
|
+
return {
|
|
552
|
+
category: "supply-chain",
|
|
553
|
+
name: "socket scan",
|
|
554
|
+
status: "fail",
|
|
555
|
+
detail: `${critical.length} critical/high supply chain issue(s) -run: socket check`,
|
|
556
|
+
severity: "critical",
|
|
557
|
+
};
|
|
558
|
+
}
|
|
559
|
+
if (issues.length > 0) {
|
|
560
|
+
return {
|
|
561
|
+
category: "supply-chain",
|
|
562
|
+
name: "socket scan",
|
|
563
|
+
status: "warn",
|
|
564
|
+
detail: `${issues.length} supply chain warning(s) -run: socket check`,
|
|
565
|
+
severity: "medium",
|
|
566
|
+
};
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
catch {
|
|
570
|
+
// Non-JSON output: socket check passed (exit 0, human-readable)
|
|
571
|
+
}
|
|
572
|
+
if (!result.ok) {
|
|
573
|
+
return {
|
|
574
|
+
category: "supply-chain",
|
|
575
|
+
name: "socket scan",
|
|
576
|
+
status: "warn",
|
|
577
|
+
detail: "socket check failed -verify installation or run: socket login",
|
|
578
|
+
severity: "medium",
|
|
579
|
+
suggestion: "socket login",
|
|
580
|
+
};
|
|
581
|
+
}
|
|
582
|
+
return { category: "supply-chain", name: "socket scan", status: "pass", detail: "no supply chain issues detected" };
|
|
583
|
+
}
|
|
584
|
+
/**
|
|
585
|
+
* Scan Dockerfile and filesystem for CVEs using Trivy.
|
|
586
|
+
* Catches OS-level vulnerabilities that npm audit misses.
|
|
587
|
+
*/
|
|
588
|
+
async function checkTrivy() {
|
|
589
|
+
const hasDockerfile = await access(resolve(process.cwd(), "Dockerfile")).then(() => true).catch(() => false);
|
|
590
|
+
if (!hasDockerfile) {
|
|
591
|
+
return { category: "supply-chain", name: "trivy container scan", status: "skip", detail: "no Dockerfile found" };
|
|
592
|
+
}
|
|
593
|
+
const versionCheck = await execFileNoThrow("trivy", ["--version"], { timeout: 5_000 });
|
|
594
|
+
if (!versionCheck.ok) {
|
|
595
|
+
return {
|
|
596
|
+
category: "supply-chain",
|
|
597
|
+
name: "trivy container scan",
|
|
598
|
+
status: "warn",
|
|
599
|
+
detail: "trivy not installed -container CVEs undetected",
|
|
600
|
+
severity: "medium",
|
|
601
|
+
suggestion: "brew install trivy",
|
|
602
|
+
};
|
|
603
|
+
}
|
|
604
|
+
const result = await execFileNoThrow("trivy", ["fs", ".", "--format", "json", "--severity", "HIGH,CRITICAL", "--quiet"], { timeout: 120_000 });
|
|
605
|
+
if (!result.ok && !result.stdout) {
|
|
606
|
+
return { category: "supply-chain", name: "trivy container scan", status: "warn", detail: "trivy scan failed", severity: "medium" };
|
|
607
|
+
}
|
|
608
|
+
try {
|
|
609
|
+
const parsed = JSON.parse(result.stdout);
|
|
610
|
+
const vulns = (parsed.Results ?? []).flatMap((r) => r.Vulnerabilities ?? []);
|
|
611
|
+
if (vulns.length === 0) {
|
|
612
|
+
return { category: "supply-chain", name: "trivy container scan", status: "pass", detail: "no high/critical container vulnerabilities" };
|
|
613
|
+
}
|
|
614
|
+
return {
|
|
615
|
+
category: "supply-chain",
|
|
616
|
+
name: "trivy container scan",
|
|
617
|
+
status: "fail",
|
|
618
|
+
detail: `${vulns.length} high/critical vulnerability(ies) in container`,
|
|
619
|
+
severity: "high",
|
|
620
|
+
};
|
|
621
|
+
}
|
|
622
|
+
catch {
|
|
623
|
+
return { category: "supply-chain", name: "trivy container scan", status: "warn", detail: "trivy scan failed", severity: "medium" };
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
/**
|
|
627
|
+
* Check dependency licenses for GPL/AGPL that create legal obligations.
|
|
628
|
+
*/
|
|
629
|
+
async function checkLicenses() {
|
|
630
|
+
try {
|
|
631
|
+
await access(resolve(process.cwd(), "package.json"));
|
|
632
|
+
}
|
|
633
|
+
catch {
|
|
634
|
+
return { category: "supply-chain", name: "license check", status: "skip", detail: "no package.json found" };
|
|
635
|
+
}
|
|
636
|
+
// Try direct binary first (fast). If absent, fall back to `npx --yes
|
|
637
|
+
// license-checker` so we don't force users to `npm install -g`.
|
|
638
|
+
// npx first-run can fetch the package, so allow generous timeout.
|
|
639
|
+
let runner = null;
|
|
640
|
+
const direct = await execFileNoThrow("license-checker", ["--version"], { timeout: 5_000 });
|
|
641
|
+
if (direct.ok) {
|
|
642
|
+
runner = { cmd: "license-checker", baseArgs: [] };
|
|
643
|
+
}
|
|
644
|
+
else {
|
|
645
|
+
const npxAvailable = await execFileNoThrow("npx", ["--version"], { timeout: 5_000 });
|
|
646
|
+
if (npxAvailable.ok) {
|
|
647
|
+
runner = { cmd: "npx", baseArgs: ["--yes", "license-checker"] };
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
if (!runner) {
|
|
651
|
+
return {
|
|
652
|
+
category: "supply-chain",
|
|
653
|
+
name: "license check",
|
|
654
|
+
status: "warn",
|
|
655
|
+
detail: "license-checker not installed (npx also unavailable)",
|
|
656
|
+
severity: "low",
|
|
657
|
+
suggestion: "npm install -g license-checker",
|
|
658
|
+
};
|
|
659
|
+
}
|
|
660
|
+
const PROBLEMATIC = ["GPL", "AGPL", "LGPL", "CPAL", "OSL", "EUPL"];
|
|
661
|
+
const result = await execFileNoThrow(runner.cmd, [...runner.baseArgs, "--json", "--production"], { timeout: 120_000 });
|
|
662
|
+
if (!result.ok && !result.stdout) {
|
|
663
|
+
return { category: "supply-chain", name: "license check", status: "warn", detail: "license check failed", severity: "low" };
|
|
664
|
+
}
|
|
665
|
+
try {
|
|
666
|
+
const packages = JSON.parse(result.stdout);
|
|
667
|
+
const violations = [];
|
|
668
|
+
for (const [pkg, info] of Object.entries(packages)) {
|
|
669
|
+
const license = info.licenses ?? "";
|
|
670
|
+
if (PROBLEMATIC.some((l) => license.toUpperCase().includes(l))) {
|
|
671
|
+
violations.push(`${pkg} (${license})`);
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
if (violations.length > 0) {
|
|
675
|
+
return {
|
|
676
|
+
category: "supply-chain",
|
|
677
|
+
name: "license check",
|
|
678
|
+
status: "warn",
|
|
679
|
+
detail: `${violations.length} copyleft license(s): ${violations.slice(0, 3).join(", ")}${violations.length > 3 ? ` +${violations.length - 3} more` : ""}`,
|
|
680
|
+
severity: "medium",
|
|
681
|
+
};
|
|
682
|
+
}
|
|
683
|
+
return { category: "supply-chain", name: "license check", status: "pass", detail: "no problematic licenses found" };
|
|
684
|
+
}
|
|
685
|
+
catch {
|
|
686
|
+
return { category: "supply-chain", name: "license check", status: "warn", detail: "license check failed", severity: "low" };
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
/**
|
|
690
|
+
* Run static analysis using Semgrep to catch security anti-patterns in source code.
|
|
691
|
+
*/
|
|
692
|
+
async function checkSemgrep() {
|
|
693
|
+
const versionCheck = await execFileNoThrow("semgrep", ["--version"], { timeout: 5_000 });
|
|
694
|
+
if (!versionCheck.ok) {
|
|
695
|
+
return {
|
|
696
|
+
category: "supply-chain",
|
|
697
|
+
name: "semgrep SAST",
|
|
698
|
+
status: "skip",
|
|
699
|
+
detail: "semgrep not installed (brew install semgrep)",
|
|
700
|
+
};
|
|
701
|
+
}
|
|
702
|
+
const result = await execFileNoThrow("semgrep", ["scan", "--config", "auto", "--json", "--quiet", "--no-rewrite-rule-ids"], { timeout: 120_000 });
|
|
703
|
+
const raw = result.stdout || result.stderr;
|
|
704
|
+
try {
|
|
705
|
+
const parsed = JSON.parse(raw);
|
|
706
|
+
const findings = parsed.results ?? [];
|
|
707
|
+
const high = findings.filter((f) => f.extra?.severity === "ERROR" || f.extra?.severity === "WARNING");
|
|
708
|
+
if (high.length === 0) {
|
|
709
|
+
return { category: "supply-chain", name: "semgrep SAST", status: "pass", detail: "no security issues found" };
|
|
710
|
+
}
|
|
711
|
+
return {
|
|
712
|
+
category: "supply-chain",
|
|
713
|
+
name: "semgrep SAST",
|
|
714
|
+
status: high.some((f) => f.extra?.severity === "ERROR") ? "fail" : "warn",
|
|
715
|
+
detail: `${high.length} security finding(s) -run: semgrep scan --config auto`,
|
|
716
|
+
severity: high.some((f) => f.extra?.severity === "ERROR") ? "high" : "medium",
|
|
717
|
+
};
|
|
718
|
+
}
|
|
719
|
+
catch {
|
|
720
|
+
return { category: "supply-chain", name: "semgrep SAST", status: "warn", detail: "semgrep scan failed", severity: "low" };
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
/**
|
|
724
|
+
* Scan for installed packages matching known supply-chain compromise catalogs
|
|
725
|
+
* using bumblebee. Unlike npm/pip audit (known CVEs), this flags packages that
|
|
726
|
+
* exactly match curated incident catalogs (shai-hulud, typosquats, credential
|
|
727
|
+
* stealers, malicious editor/browser extensions, etc.).
|
|
728
|
+
*
|
|
729
|
+
* Zero-config by default; tunable via environment:
|
|
730
|
+
* KIT_BUMBLEBEE set to 0/false to skip the check entirely
|
|
731
|
+
* KIT_NO_DOWNLOAD set to 1 to never fetch the scanner binary
|
|
732
|
+
* KIT_BUMBLEBEE_PROFILE baseline (default) | project | deep
|
|
733
|
+
* KIT_BUMBLEBEE_ROOTS comma-separated roots (e.g. "." for the repo; required for deep)
|
|
734
|
+
* KIT_BUMBLEBEE_BIN use a pre-installed bumblebee instead of downloading
|
|
735
|
+
* KIT_BUMBLEBEE_CATALOG override the exposure-catalog directory
|
|
736
|
+
*/
|
|
737
|
+
async function checkBumblebee() {
|
|
738
|
+
const name = "bumblebee (supply-chain)";
|
|
739
|
+
const category = "supply-chain";
|
|
740
|
+
if (envFlagDisabled(process.env.KIT_BUMBLEBEE)) {
|
|
741
|
+
return { category, name, status: "skip", detail: "disabled via KIT_BUMBLEBEE" };
|
|
742
|
+
}
|
|
743
|
+
const { install, reason, kind } = await ensureBumblebee({
|
|
744
|
+
allowDownload: !envFlagEnabled(process.env.KIT_NO_DOWNLOAD),
|
|
745
|
+
});
|
|
746
|
+
if (!install) {
|
|
747
|
+
// A failed integrity check (checksum mismatch) is a potential tampering
|
|
748
|
+
// event — escalate to a hard failure rather than failing open to a warn.
|
|
749
|
+
if (kind === "integrity") {
|
|
750
|
+
return {
|
|
751
|
+
category,
|
|
752
|
+
name,
|
|
753
|
+
status: "fail",
|
|
754
|
+
detail: `scanner ${reason}`,
|
|
755
|
+
severity: "high",
|
|
756
|
+
suggestion: "The downloaded scanner did not match its pinned checksum. Do NOT trust it. Investigate for tampering (network MITM, compromised mirror), clear ~/.kit/tools/bumblebee, and retry from a trusted network.",
|
|
757
|
+
};
|
|
758
|
+
}
|
|
759
|
+
return {
|
|
760
|
+
category,
|
|
761
|
+
name,
|
|
762
|
+
status: "warn",
|
|
763
|
+
detail: `scanner unavailable: ${reason}`,
|
|
764
|
+
severity: "low",
|
|
765
|
+
suggestion: "Provide a binary with KIT_BUMBLEBEE_BIN, or allow downloads (unset KIT_NO_DOWNLOAD). Manual install: go install github.com/perplexityai/bumblebee/cmd/bumblebee@latest",
|
|
766
|
+
};
|
|
767
|
+
}
|
|
768
|
+
const profile = process.env.KIT_BUMBLEBEE_PROFILE || "baseline";
|
|
769
|
+
const roots = (process.env.KIT_BUMBLEBEE_ROOTS || "")
|
|
770
|
+
.split(",")
|
|
771
|
+
.map((s) => s.trim())
|
|
772
|
+
.filter(Boolean);
|
|
773
|
+
const { outcome, error } = await runScan({ install, profile, roots });
|
|
774
|
+
if (error || !outcome) {
|
|
775
|
+
return {
|
|
776
|
+
category,
|
|
777
|
+
name,
|
|
778
|
+
status: "warn",
|
|
779
|
+
detail: `scan failed: ${error ?? "no output"}`,
|
|
780
|
+
severity: "medium",
|
|
781
|
+
};
|
|
782
|
+
}
|
|
783
|
+
if (outcome.findings.length > 0) {
|
|
784
|
+
const catalogs = describeFindings(outcome.findings);
|
|
785
|
+
// F9: persist every catalog match to the local audit log so the find
|
|
786
|
+
// survives the next CI run and shows up in `kit audit`.
|
|
787
|
+
await logSupplyChainFindings(outcome.findings, profile).catch(() => { });
|
|
788
|
+
return {
|
|
789
|
+
category,
|
|
790
|
+
name,
|
|
791
|
+
status: "fail",
|
|
792
|
+
detail: `${outcome.findings.length} known supply-chain exposure(s): ${catalogs}`,
|
|
793
|
+
severity: toResultSeverity(maxSeverity(outcome.findings)),
|
|
794
|
+
files: Array.from(new Set(outcome.findings.map((f) => f.sourceFile).filter(Boolean))),
|
|
795
|
+
suggestion: "Remove or downgrade the flagged packages immediately — they match curated known-compromise catalogs. Verify on the source advisory before trusting any replacement.",
|
|
796
|
+
};
|
|
797
|
+
}
|
|
798
|
+
if (!outcome.summarySeen || outcome.status !== "complete" || outcome.timedOut) {
|
|
799
|
+
return {
|
|
800
|
+
category,
|
|
801
|
+
name,
|
|
802
|
+
status: "warn",
|
|
803
|
+
detail: `scan incomplete (status=${outcome.status}${outcome.timedOut ? ", timed out" : ""})`,
|
|
804
|
+
severity: "low",
|
|
805
|
+
};
|
|
806
|
+
}
|
|
807
|
+
// Clean scan — but a frozen catalog set silently loses coverage over time.
|
|
808
|
+
const newest = await newestCatalogMtime(install.catalogDir);
|
|
809
|
+
if (newest !== null) {
|
|
810
|
+
const { stale, ageDays } = isCatalogStale(newest, Date.now());
|
|
811
|
+
if (stale) {
|
|
812
|
+
return {
|
|
813
|
+
category,
|
|
814
|
+
name,
|
|
815
|
+
status: "warn",
|
|
816
|
+
severity: "low",
|
|
817
|
+
detail: `no known exposures (${outcome.packagesScanned} packages), but threat-intel catalogs are ${ageDays} days old`,
|
|
818
|
+
suggestion: "Bump BUMBLEBEE_VERSION (and TARBALL_CHECKSUMS) in src/bumblebee.ts to refresh the exposure catalogs.",
|
|
819
|
+
};
|
|
820
|
+
}
|
|
821
|
+
}
|
|
822
|
+
return {
|
|
823
|
+
category,
|
|
824
|
+
name,
|
|
825
|
+
status: "pass",
|
|
826
|
+
detail: `no known exposures (${outcome.packagesScanned} packages, profile=${profile})`,
|
|
827
|
+
};
|
|
828
|
+
}
|
|
829
|
+
/** Short, human-readable summary of the catalogs matched by findings. */
|
|
830
|
+
function describeFindings(findings) {
|
|
831
|
+
const labels = Array.from(new Set(findings.map((f) => f.catalogName || f.catalogId).filter(Boolean)));
|
|
832
|
+
const shown = labels.slice(0, 3).join("; ");
|
|
833
|
+
return labels.length > 3 ? `${shown}; +${labels.length - 3} more` : shown;
|
|
834
|
+
}
|
|
835
|
+
/**
|
|
836
|
+
* Run all security checks
|
|
837
|
+
*/
|
|
838
|
+
export async function checkSecurity() {
|
|
839
|
+
const results = [];
|
|
840
|
+
const [npmResult, pipResult, envResult, pinnedResult, secretsScan, socketResult, trivyResult, licenseResult, semgrepResult, bumblebeeResult, ...lockfileResults] = await Promise.all([
|
|
841
|
+
checkNpmAudit(),
|
|
842
|
+
checkPipAudit(),
|
|
843
|
+
checkEnvGitignored(),
|
|
844
|
+
checkPinnedVersions(),
|
|
845
|
+
checkSecretsInCode(),
|
|
846
|
+
checkSocket(),
|
|
847
|
+
checkTrivy(),
|
|
848
|
+
checkLicenses(),
|
|
849
|
+
checkSemgrep(),
|
|
850
|
+
checkBumblebee(),
|
|
851
|
+
...await checkLockfilesCommitted(),
|
|
852
|
+
]);
|
|
853
|
+
results.push(npmResult, pipResult, envResult, pinnedResult, secretsScan, socketResult, trivyResult, licenseResult, semgrepResult, bumblebeeResult);
|
|
854
|
+
results.push(...lockfileResults);
|
|
855
|
+
const exposureResults = await checkServiceExposure();
|
|
856
|
+
results.push(...exposureResults);
|
|
857
|
+
return results;
|
|
858
|
+
}
|
|
859
|
+
/**
|
|
860
|
+
* F9 — append bumblebee supply-chain findings to the local audit JSONL.
|
|
861
|
+
* Bypasses the governance config so the trail is captured even when
|
|
862
|
+
* `kit check` is run without `.kit.toml`. One JSON line per finding.
|
|
863
|
+
*/
|
|
864
|
+
async function logSupplyChainFindings(findings, profile) {
|
|
865
|
+
const { appendFile } = await import("node:fs/promises");
|
|
866
|
+
const path = resolve(process.cwd(), ".kit-audit.jsonl");
|
|
867
|
+
const lines = findings
|
|
868
|
+
.map((f) => JSON.stringify({
|
|
869
|
+
timestamp: new Date().toISOString(),
|
|
870
|
+
event_type: "supply_chain_finding",
|
|
871
|
+
source: "bumblebee",
|
|
872
|
+
profile,
|
|
873
|
+
catalog_id: f.catalogId,
|
|
874
|
+
catalog_name: f.catalogName,
|
|
875
|
+
severity: f.severity,
|
|
876
|
+
package: f.packageName || "unknown",
|
|
877
|
+
version: f.version || null,
|
|
878
|
+
ecosystem: f.ecosystem || null,
|
|
879
|
+
source_file: f.sourceFile || null,
|
|
880
|
+
evidence: f.evidence || null,
|
|
881
|
+
}))
|
|
882
|
+
.join("\n");
|
|
883
|
+
if (lines) {
|
|
884
|
+
await appendFile(path, lines + "\n", "utf-8");
|
|
885
|
+
}
|
|
886
|
+
}
|
|
887
|
+
//# sourceMappingURL=check-security.js.map
|