vibecheck-ai 2.0.1 → 5.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/bin/.generated +25 -0
- package/bin/_deprecations.js +463 -0
- package/bin/_router.js +46 -0
- package/bin/cli-hygiene.js +241 -0
- package/bin/dev/run-v2-torture.js +30 -0
- package/bin/registry.js +656 -0
- package/bin/runners/CLI_REFACTOR_SUMMARY.md +229 -0
- package/bin/runners/ENHANCEMENT_GUIDE.md +121 -0
- package/bin/runners/REPORT_AUDIT.md +64 -0
- package/bin/runners/cli-utils.js +1070 -0
- package/bin/runners/context/ai-task-decomposer.js +337 -0
- package/bin/runners/context/analyzer.js +513 -0
- package/bin/runners/context/api-contracts.js +427 -0
- package/bin/runners/context/context-diff.js +342 -0
- package/bin/runners/context/context-pruner.js +291 -0
- package/bin/runners/context/dependency-graph.js +414 -0
- package/bin/runners/context/generators/claude.js +107 -0
- package/bin/runners/context/generators/codex.js +108 -0
- package/bin/runners/context/generators/copilot.js +119 -0
- package/bin/runners/context/generators/cursor-enhanced.js +2525 -0
- package/bin/runners/context/generators/cursor.js +514 -0
- package/bin/runners/context/generators/mcp.js +169 -0
- package/bin/runners/context/generators/windsurf.js +180 -0
- package/bin/runners/context/git-context.js +304 -0
- package/bin/runners/context/index.js +1110 -0
- package/bin/runners/context/insights.js +173 -0
- package/bin/runners/context/mcp-server/generate-rules.js +337 -0
- package/bin/runners/context/mcp-server/index.js +1176 -0
- package/bin/runners/context/mcp-server/package.json +24 -0
- package/bin/runners/context/memory.js +200 -0
- package/bin/runners/context/monorepo.js +215 -0
- package/bin/runners/context/multi-repo-federation.js +404 -0
- package/bin/runners/context/patterns.js +253 -0
- package/bin/runners/context/proof-context.js +1264 -0
- package/bin/runners/context/security-scanner.js +541 -0
- package/bin/runners/context/semantic-search.js +350 -0
- package/bin/runners/context/shared.js +264 -0
- package/bin/runners/context/team-conventions.js +336 -0
- package/bin/runners/lib/__tests__/entitlements-v2.test.js +295 -0
- package/bin/runners/lib/agent-firewall/ai/false-positive-analyzer.js +474 -0
- package/bin/runners/lib/agent-firewall/change-packet/builder.js +488 -0
- package/bin/runners/lib/agent-firewall/change-packet/schema.json +228 -0
- package/bin/runners/lib/agent-firewall/change-packet/store.js +200 -0
- package/bin/runners/lib/agent-firewall/claims/claim-types.js +21 -0
- package/bin/runners/lib/agent-firewall/claims/extractor.js +303 -0
- package/bin/runners/lib/agent-firewall/claims/patterns.js +24 -0
- package/bin/runners/lib/agent-firewall/critic/index.js +151 -0
- package/bin/runners/lib/agent-firewall/critic/judge.js +432 -0
- package/bin/runners/lib/agent-firewall/critic/prompts.js +305 -0
- package/bin/runners/lib/agent-firewall/enforcement/gateway.js +1059 -0
- package/bin/runners/lib/agent-firewall/enforcement/index.js +98 -0
- package/bin/runners/lib/agent-firewall/enforcement/mode.js +318 -0
- package/bin/runners/lib/agent-firewall/enforcement/orchestrator.js +484 -0
- package/bin/runners/lib/agent-firewall/enforcement/proof-artifact.js +418 -0
- package/bin/runners/lib/agent-firewall/enforcement/schemas/change-event.schema.json +173 -0
- package/bin/runners/lib/agent-firewall/enforcement/schemas/intent.schema.json +181 -0
- package/bin/runners/lib/agent-firewall/enforcement/schemas/verdict.schema.json +222 -0
- package/bin/runners/lib/agent-firewall/enforcement/verdict-v2.js +333 -0
- package/bin/runners/lib/agent-firewall/evidence/auth-evidence.js +88 -0
- package/bin/runners/lib/agent-firewall/evidence/contract-evidence.js +75 -0
- package/bin/runners/lib/agent-firewall/evidence/env-evidence.js +127 -0
- package/bin/runners/lib/agent-firewall/evidence/resolver.js +102 -0
- package/bin/runners/lib/agent-firewall/evidence/route-evidence.js +213 -0
- package/bin/runners/lib/agent-firewall/evidence/side-effect-evidence.js +145 -0
- package/bin/runners/lib/agent-firewall/fs-hook/daemon.js +19 -0
- package/bin/runners/lib/agent-firewall/fs-hook/installer.js +87 -0
- package/bin/runners/lib/agent-firewall/fs-hook/watcher.js +184 -0
- package/bin/runners/lib/agent-firewall/git-hook/pre-commit.js +163 -0
- package/bin/runners/lib/agent-firewall/ide-extension/cursor.js +107 -0
- package/bin/runners/lib/agent-firewall/ide-extension/vscode.js +68 -0
- package/bin/runners/lib/agent-firewall/ide-extension/windsurf.js +66 -0
- package/bin/runners/lib/agent-firewall/index.js +200 -0
- package/bin/runners/lib/agent-firewall/integration/index.js +20 -0
- package/bin/runners/lib/agent-firewall/integration/ship-gate.js +437 -0
- package/bin/runners/lib/agent-firewall/intent/alignment-engine.js +634 -0
- package/bin/runners/lib/agent-firewall/intent/auto-detect.js +426 -0
- package/bin/runners/lib/agent-firewall/intent/index.js +102 -0
- package/bin/runners/lib/agent-firewall/intent/schema.js +352 -0
- package/bin/runners/lib/agent-firewall/intent/store.js +283 -0
- package/bin/runners/lib/agent-firewall/interception/fs-interceptor.js +502 -0
- package/bin/runners/lib/agent-firewall/interception/index.js +23 -0
- package/bin/runners/lib/agent-firewall/interceptor/base.js +308 -0
- package/bin/runners/lib/agent-firewall/interceptor/cursor.js +35 -0
- package/bin/runners/lib/agent-firewall/interceptor/vscode.js +35 -0
- package/bin/runners/lib/agent-firewall/interceptor/windsurf.js +34 -0
- package/bin/runners/lib/agent-firewall/lawbook/distributor.js +465 -0
- package/bin/runners/lib/agent-firewall/lawbook/evaluator.js +604 -0
- package/bin/runners/lib/agent-firewall/lawbook/index.js +304 -0
- package/bin/runners/lib/agent-firewall/lawbook/registry.js +514 -0
- package/bin/runners/lib/agent-firewall/lawbook/schema.js +420 -0
- package/bin/runners/lib/agent-firewall/logger.js +141 -0
- package/bin/runners/lib/agent-firewall/policy/default-policy.json +90 -0
- package/bin/runners/lib/agent-firewall/policy/engine.js +103 -0
- package/bin/runners/lib/agent-firewall/policy/loader.js +451 -0
- package/bin/runners/lib/agent-firewall/policy/rules/auth-drift.js +50 -0
- package/bin/runners/lib/agent-firewall/policy/rules/contract-drift.js +50 -0
- package/bin/runners/lib/agent-firewall/policy/rules/fake-success.js +79 -0
- package/bin/runners/lib/agent-firewall/policy/rules/ghost-env.js +227 -0
- package/bin/runners/lib/agent-firewall/policy/rules/ghost-route.js +191 -0
- package/bin/runners/lib/agent-firewall/policy/rules/scope.js +93 -0
- package/bin/runners/lib/agent-firewall/policy/rules/unsafe-side-effect.js +57 -0
- package/bin/runners/lib/agent-firewall/policy/schema.json +183 -0
- package/bin/runners/lib/agent-firewall/policy/verdict.js +54 -0
- package/bin/runners/lib/agent-firewall/proposal/extractor.js +394 -0
- package/bin/runners/lib/agent-firewall/proposal/index.js +212 -0
- package/bin/runners/lib/agent-firewall/proposal/schema.js +251 -0
- package/bin/runners/lib/agent-firewall/proposal/validator.js +386 -0
- package/bin/runners/lib/agent-firewall/reality/index.js +332 -0
- package/bin/runners/lib/agent-firewall/reality/state.js +625 -0
- package/bin/runners/lib/agent-firewall/reality/watcher.js +322 -0
- package/bin/runners/lib/agent-firewall/risk/index.js +173 -0
- package/bin/runners/lib/agent-firewall/risk/scorer.js +328 -0
- package/bin/runners/lib/agent-firewall/risk/thresholds.js +322 -0
- package/bin/runners/lib/agent-firewall/risk/vectors.js +421 -0
- package/bin/runners/lib/agent-firewall/session/collector.js +451 -0
- package/bin/runners/lib/agent-firewall/session/index.js +26 -0
- package/bin/runners/lib/agent-firewall/simulator/diff-simulator.js +472 -0
- package/bin/runners/lib/agent-firewall/simulator/import-resolver.js +346 -0
- package/bin/runners/lib/agent-firewall/simulator/index.js +181 -0
- package/bin/runners/lib/agent-firewall/simulator/route-validator.js +380 -0
- package/bin/runners/lib/agent-firewall/time-machine/incident-correlator.js +661 -0
- package/bin/runners/lib/agent-firewall/time-machine/index.js +267 -0
- package/bin/runners/lib/agent-firewall/time-machine/replay-engine.js +436 -0
- package/bin/runners/lib/agent-firewall/time-machine/state-reconstructor.js +490 -0
- package/bin/runners/lib/agent-firewall/time-machine/timeline-builder.js +530 -0
- package/bin/runners/lib/agent-firewall/truthpack/index.js +67 -0
- package/bin/runners/lib/agent-firewall/truthpack/loader.js +137 -0
- package/bin/runners/lib/agent-firewall/unblock/planner.js +337 -0
- package/bin/runners/lib/agent-firewall/utils/ignore-checker.js +118 -0
- package/bin/runners/lib/ai-bridge.js +416 -0
- package/bin/runners/lib/analysis-core.js +309 -0
- package/bin/runners/lib/analyzers.js +2500 -0
- package/bin/runners/lib/api-client.js +269 -0
- package/bin/runners/lib/approve-output.js +235 -0
- package/bin/runners/lib/artifact-envelope.js +540 -0
- package/bin/runners/lib/assets/vibecheck-logo.png +0 -0
- package/bin/runners/lib/audit-bridge.js +391 -0
- package/bin/runners/lib/auth-shared.js +977 -0
- package/bin/runners/lib/auth-truth.js +193 -0
- package/bin/runners/lib/auth.js +215 -0
- package/bin/runners/lib/authority-badge.js +425 -0
- package/bin/runners/lib/backup.js +62 -0
- package/bin/runners/lib/billing.js +107 -0
- package/bin/runners/lib/checkpoint.js +941 -0
- package/bin/runners/lib/claims.js +118 -0
- package/bin/runners/lib/classify-output.js +204 -0
- package/bin/runners/lib/cleanup/engine.js +571 -0
- package/bin/runners/lib/cleanup/index.js +53 -0
- package/bin/runners/lib/cleanup/output.js +375 -0
- package/bin/runners/lib/cleanup/rules.js +1060 -0
- package/bin/runners/lib/cli-output.js +400 -0
- package/bin/runners/lib/cli-ui.js +540 -0
- package/bin/runners/lib/compliance-bridge-new.js +0 -0
- package/bin/runners/lib/compliance-bridge.js +165 -0
- package/bin/runners/lib/contracts/auth-contract.js +202 -0
- package/bin/runners/lib/contracts/env-contract.js +181 -0
- package/bin/runners/lib/contracts/external-contract.js +206 -0
- package/bin/runners/lib/contracts/guard.js +168 -0
- package/bin/runners/lib/contracts/index.js +89 -0
- package/bin/runners/lib/contracts/plan-validator.js +311 -0
- package/bin/runners/lib/contracts/route-contract.js +199 -0
- package/bin/runners/lib/contracts.js +804 -0
- package/bin/runners/lib/default-config.js +127 -0
- package/bin/runners/lib/detect.js +89 -0
- package/bin/runners/lib/detectors-v2.js +622 -0
- package/bin/runners/lib/doctor/autofix.js +254 -0
- package/bin/runners/lib/doctor/diagnosis-receipt.js +454 -0
- package/bin/runners/lib/doctor/failure-signatures.js +526 -0
- package/bin/runners/lib/doctor/fix-script.js +336 -0
- package/bin/runners/lib/doctor/index.js +37 -0
- package/bin/runners/lib/doctor/modules/build-tools.js +453 -0
- package/bin/runners/lib/doctor/modules/dependencies.js +325 -0
- package/bin/runners/lib/doctor/modules/index.js +105 -0
- package/bin/runners/lib/doctor/modules/network.js +250 -0
- package/bin/runners/lib/doctor/modules/os-quirks.js +706 -0
- package/bin/runners/lib/doctor/modules/project.js +312 -0
- package/bin/runners/lib/doctor/modules/repo-integrity.js +485 -0
- package/bin/runners/lib/doctor/modules/runtime.js +224 -0
- package/bin/runners/lib/doctor/modules/security.js +350 -0
- package/bin/runners/lib/doctor/modules/system.js +213 -0
- package/bin/runners/lib/doctor/modules/vibecheck.js +394 -0
- package/bin/runners/lib/doctor/reporter.js +262 -0
- package/bin/runners/lib/doctor/safe-repair.js +384 -0
- package/bin/runners/lib/doctor/service.js +262 -0
- package/bin/runners/lib/doctor/types.js +113 -0
- package/bin/runners/lib/doctor/ui.js +263 -0
- package/bin/runners/lib/doctor-enhanced.js +233 -0
- package/bin/runners/lib/doctor-output.js +226 -0
- package/bin/runners/lib/doctor-v2.js +608 -0
- package/bin/runners/lib/drift.js +425 -0
- package/bin/runners/lib/enforcement.js +72 -0
- package/bin/runners/lib/engine/ast-cache.js +210 -0
- package/bin/runners/lib/engine/auth-extractor.js +211 -0
- package/bin/runners/lib/engine/billing-extractor.js +112 -0
- package/bin/runners/lib/engine/enforcement-extractor.js +100 -0
- package/bin/runners/lib/engine/env-extractor.js +207 -0
- package/bin/runners/lib/engine/express-extractor.js +208 -0
- package/bin/runners/lib/engine/extractors.js +849 -0
- package/bin/runners/lib/engine/index.js +207 -0
- package/bin/runners/lib/engine/repo-index.js +514 -0
- package/bin/runners/lib/engine/types.js +124 -0
- package/bin/runners/lib/engines/accessibility-engine.js +190 -0
- package/bin/runners/lib/engines/api-consistency-engine.js +162 -0
- package/bin/runners/lib/engines/ast-cache.js +99 -0
- package/bin/runners/lib/engines/attack-detector.js +1192 -0
- package/bin/runners/lib/engines/code-quality-engine.js +255 -0
- package/bin/runners/lib/engines/console-logs-engine.js +115 -0
- package/bin/runners/lib/engines/cross-file-analysis-engine.js +268 -0
- package/bin/runners/lib/engines/dead-code-engine.js +198 -0
- package/bin/runners/lib/engines/deprecated-api-engine.js +226 -0
- package/bin/runners/lib/engines/empty-catch-engine.js +150 -0
- package/bin/runners/lib/engines/file-filter.js +131 -0
- package/bin/runners/lib/engines/hardcoded-secrets-engine.js +251 -0
- package/bin/runners/lib/engines/mock-data-engine.js +272 -0
- package/bin/runners/lib/engines/parallel-processor.js +71 -0
- package/bin/runners/lib/engines/performance-issues-engine.js +265 -0
- package/bin/runners/lib/engines/security-vulnerabilities-engine.js +243 -0
- package/bin/runners/lib/engines/todo-fixme-engine.js +115 -0
- package/bin/runners/lib/engines/type-aware-engine.js +152 -0
- package/bin/runners/lib/engines/unsafe-regex-engine.js +225 -0
- package/bin/runners/lib/engines/vibecheck-engines/README.md +53 -0
- package/bin/runners/lib/engines/vibecheck-engines/index.js +15 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/ast-cache.js +164 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/code-quality-engine.js +291 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/console-logs-engine.js +83 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/dead-code-engine.js +198 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/deprecated-api-engine.js +275 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/empty-catch-engine.js +167 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/file-filter.js +217 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/hardcoded-secrets-engine.js +139 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/mock-data-engine.js +140 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/parallel-processor.js +164 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/performance-issues-engine.js +234 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/type-aware-engine.js +217 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/unsafe-regex-engine.js +78 -0
- package/bin/runners/lib/engines/vibecheck-engines/package.json +13 -0
- package/bin/runners/lib/enterprise-detect.js +603 -0
- package/bin/runners/lib/enterprise-init.js +942 -0
- package/bin/runners/lib/entitlements-v2.js +265 -0
- package/bin/runners/lib/entitlements.generated.js +0 -0
- package/bin/runners/lib/entitlements.js +340 -0
- package/bin/runners/lib/env-resolver.js +417 -0
- package/bin/runners/lib/env-template.js +66 -0
- package/bin/runners/lib/env.js +189 -0
- package/bin/runners/lib/error-handler.js +368 -0
- package/bin/runners/lib/error-messages.js +289 -0
- package/bin/runners/lib/evidence-pack.js +684 -0
- package/bin/runners/lib/exit-codes.js +275 -0
- package/bin/runners/lib/extractors/client-calls.js +990 -0
- package/bin/runners/lib/extractors/fastify-route-dump.js +573 -0
- package/bin/runners/lib/extractors/fastify-routes.js +426 -0
- package/bin/runners/lib/extractors/index.js +363 -0
- package/bin/runners/lib/extractors/next-routes.js +524 -0
- package/bin/runners/lib/extractors/proof-graph.js +431 -0
- package/bin/runners/lib/extractors/route-matcher.js +451 -0
- package/bin/runners/lib/extractors/truthpack-v2.js +377 -0
- package/bin/runners/lib/extractors/ui-bindings.js +547 -0
- package/bin/runners/lib/finding-id.js +69 -0
- package/bin/runners/lib/finding-sorter.js +89 -0
- package/bin/runners/lib/findings-schema.js +281 -0
- package/bin/runners/lib/fingerprint.js +377 -0
- package/bin/runners/lib/firewall-prompt.js +50 -0
- package/bin/runners/lib/fix-output.js +228 -0
- package/bin/runners/lib/global-flags.js +250 -0
- package/bin/runners/lib/graph/graph-builder.js +265 -0
- package/bin/runners/lib/graph/html-renderer.js +413 -0
- package/bin/runners/lib/graph/index.js +32 -0
- package/bin/runners/lib/graph/runtime-collector.js +215 -0
- package/bin/runners/lib/graph/static-extractor.js +518 -0
- package/bin/runners/lib/help-formatter.js +413 -0
- package/bin/runners/lib/html-proof-report.js +913 -0
- package/bin/runners/lib/html-report.js +650 -0
- package/bin/runners/lib/init-wizard.js +601 -0
- package/bin/runners/lib/interactive-menu.js +1496 -0
- package/bin/runners/lib/json-output.js +76 -0
- package/bin/runners/lib/llm.js +75 -0
- package/bin/runners/lib/logger.js +38 -0
- package/bin/runners/lib/meter.js +61 -0
- package/bin/runners/lib/missions/briefing.js +427 -0
- package/bin/runners/lib/missions/checkpoint.js +753 -0
- package/bin/runners/lib/missions/evidence.js +126 -0
- package/bin/runners/lib/missions/hardening.js +851 -0
- package/bin/runners/lib/missions/plan.js +648 -0
- package/bin/runners/lib/missions/safety-gates.js +645 -0
- package/bin/runners/lib/missions/schema.js +478 -0
- package/bin/runners/lib/missions/templates.js +317 -0
- package/bin/runners/lib/next-action.js +560 -0
- package/bin/runners/lib/packs/bundle.js +675 -0
- package/bin/runners/lib/packs/evidence-pack.js +671 -0
- package/bin/runners/lib/packs/pack-factory.js +837 -0
- package/bin/runners/lib/packs/permissions-pack.js +686 -0
- package/bin/runners/lib/packs/proof-graph-pack.js +779 -0
- package/bin/runners/lib/patch.js +40 -0
- package/bin/runners/lib/permissions/auth-model.js +213 -0
- package/bin/runners/lib/permissions/idor-prover.js +205 -0
- package/bin/runners/lib/permissions/index.js +45 -0
- package/bin/runners/lib/permissions/matrix-builder.js +198 -0
- package/bin/runners/lib/pkgjson.js +28 -0
- package/bin/runners/lib/policy.js +295 -0
- package/bin/runners/lib/polish/accessibility.js +62 -0
- package/bin/runners/lib/polish/analyzer.js +93 -0
- package/bin/runners/lib/polish/backend.js +87 -0
- package/bin/runners/lib/polish/configuration.js +83 -0
- package/bin/runners/lib/polish/documentation.js +83 -0
- package/bin/runners/lib/polish/frontend.js +817 -0
- package/bin/runners/lib/polish/index.js +27 -0
- package/bin/runners/lib/polish/infrastructure.js +80 -0
- package/bin/runners/lib/polish/internationalization.js +85 -0
- package/bin/runners/lib/polish/libraries.js +180 -0
- package/bin/runners/lib/polish/observability.js +75 -0
- package/bin/runners/lib/polish/performance.js +64 -0
- package/bin/runners/lib/polish/privacy.js +110 -0
- package/bin/runners/lib/polish/resilience.js +92 -0
- package/bin/runners/lib/polish/security.js +78 -0
- package/bin/runners/lib/polish/seo.js +71 -0
- package/bin/runners/lib/polish/styles.js +62 -0
- package/bin/runners/lib/polish/utils.js +104 -0
- package/bin/runners/lib/preflight.js +142 -0
- package/bin/runners/lib/prerequisites.js +149 -0
- package/bin/runners/lib/prove-output.js +220 -0
- package/bin/runners/lib/reality/correlation-detectors.js +359 -0
- package/bin/runners/lib/reality/index.js +318 -0
- package/bin/runners/lib/reality/request-hashing.js +416 -0
- package/bin/runners/lib/reality/request-mapper.js +453 -0
- package/bin/runners/lib/reality/safety-rails.js +463 -0
- package/bin/runners/lib/reality/semantic-snapshot.js +408 -0
- package/bin/runners/lib/reality/toast-detector.js +393 -0
- package/bin/runners/lib/reality-findings.js +84 -0
- package/bin/runners/lib/reality-output.js +231 -0
- package/bin/runners/lib/receipts.js +179 -0
- package/bin/runners/lib/redact.js +29 -0
- package/bin/runners/lib/replay/capsule-manager.js +154 -0
- package/bin/runners/lib/replay/index.js +263 -0
- package/bin/runners/lib/replay/player.js +348 -0
- package/bin/runners/lib/replay/recorder.js +331 -0
- package/bin/runners/lib/report-engine.js +626 -0
- package/bin/runners/lib/report-html.js +1233 -0
- package/bin/runners/lib/report-output.js +366 -0
- package/bin/runners/lib/report-templates.js +967 -0
- package/bin/runners/lib/report.js +135 -0
- package/bin/runners/lib/route-detection.js +1209 -0
- package/bin/runners/lib/route-truth.js +1322 -0
- package/bin/runners/lib/safelist/index.js +96 -0
- package/bin/runners/lib/safelist/integration.js +334 -0
- package/bin/runners/lib/safelist/matcher.js +696 -0
- package/bin/runners/lib/safelist/schema.js +948 -0
- package/bin/runners/lib/safelist/store.js +438 -0
- package/bin/runners/lib/sandbox/index.js +59 -0
- package/bin/runners/lib/sandbox/proof-chain.js +399 -0
- package/bin/runners/lib/sandbox/sandbox-runner.js +205 -0
- package/bin/runners/lib/sandbox/worktree.js +174 -0
- package/bin/runners/lib/scan-cache.js +330 -0
- package/bin/runners/lib/scan-output-schema.js +344 -0
- package/bin/runners/lib/scan-output.js +631 -0
- package/bin/runners/lib/scan-runner.js +135 -0
- package/bin/runners/lib/schema-validator.js +350 -0
- package/bin/runners/lib/schemas/ajv-validator.js +464 -0
- package/bin/runners/lib/schemas/contracts.schema.json +160 -0
- package/bin/runners/lib/schemas/error-envelope.schema.json +105 -0
- package/bin/runners/lib/schemas/finding-v3.schema.json +151 -0
- package/bin/runners/lib/schemas/finding.schema.json +100 -0
- package/bin/runners/lib/schemas/mission-pack.schema.json +206 -0
- package/bin/runners/lib/schemas/proof-graph.schema.json +176 -0
- package/bin/runners/lib/schemas/reality-report.schema.json +162 -0
- package/bin/runners/lib/schemas/report-artifact.schema.json +120 -0
- package/bin/runners/lib/schemas/run-request.schema.json +108 -0
- package/bin/runners/lib/schemas/share-pack.schema.json +180 -0
- package/bin/runners/lib/schemas/ship-manifest.schema.json +251 -0
- package/bin/runners/lib/schemas/ship-report.schema.json +117 -0
- package/bin/runners/lib/schemas/truthpack-v2.schema.json +303 -0
- package/bin/runners/lib/schemas/validator.js +465 -0
- package/bin/runners/lib/schemas/verdict.schema.json +140 -0
- package/bin/runners/lib/score-history.js +282 -0
- package/bin/runners/lib/security-bridge.js +249 -0
- package/bin/runners/lib/server-usage.js +513 -0
- package/bin/runners/lib/share-pack.js +239 -0
- package/bin/runners/lib/ship-gate.js +832 -0
- package/bin/runners/lib/ship-manifest.js +1153 -0
- package/bin/runners/lib/ship-output-enterprise.js +239 -0
- package/bin/runners/lib/ship-output.js +1128 -0
- package/bin/runners/lib/snippets.js +67 -0
- package/bin/runners/lib/status-output.js +340 -0
- package/bin/runners/lib/terminal-ui.js +356 -0
- package/bin/runners/lib/truth.js +1691 -0
- package/bin/runners/lib/ui.js +562 -0
- package/bin/runners/lib/unified-cli-output.js +947 -0
- package/bin/runners/lib/unified-output.js +197 -0
- package/bin/runners/lib/upsell.js +410 -0
- package/bin/runners/lib/usage.js +153 -0
- package/bin/runners/lib/validate-patch.js +156 -0
- package/bin/runners/lib/verdict-engine.js +628 -0
- package/bin/runners/lib/verification.js +345 -0
- package/bin/runners/lib/why-tree.js +650 -0
- package/bin/runners/reality/engine.js +917 -0
- package/bin/runners/reality/flows.js +122 -0
- package/bin/runners/reality/report.js +378 -0
- package/bin/runners/reality/session.js +193 -0
- package/bin/runners/runAIAgent.js +229 -0
- package/bin/runners/runAgent.d.ts +5 -0
- package/bin/runners/runAgent.js +161 -0
- package/bin/runners/runAllowlist.js +418 -0
- package/bin/runners/runApprove.js +320 -0
- package/bin/runners/runAudit.js +692 -0
- package/bin/runners/runAuth.js +731 -0
- package/bin/runners/runCI.js +353 -0
- package/bin/runners/runCheckpoint.js +530 -0
- package/bin/runners/runClassify.js +928 -0
- package/bin/runners/runCleanup.js +343 -0
- package/bin/runners/runContext.d.ts +4 -0
- package/bin/runners/runContext.js +175 -0
- package/bin/runners/runDoctor.js +877 -0
- package/bin/runners/runEvidencePack.js +362 -0
- package/bin/runners/runFirewall.d.ts +5 -0
- package/bin/runners/runFirewall.js +134 -0
- package/bin/runners/runFirewallHook.d.ts +5 -0
- package/bin/runners/runFirewallHook.js +56 -0
- package/bin/runners/runFix.js +1355 -0
- package/bin/runners/runForge.js +451 -0
- package/bin/runners/runGuard.js +262 -0
- package/bin/runners/runInit.js +1927 -0
- package/bin/runners/runIntent.js +906 -0
- package/bin/runners/runKickoff.js +878 -0
- package/bin/runners/runLabs.js +424 -0
- package/bin/runners/runLaunch.js +2000 -0
- package/bin/runners/runLink.js +785 -0
- package/bin/runners/runMcp.js +1875 -0
- package/bin/runners/runPacks.js +2089 -0
- package/bin/runners/runPolish.d.ts +4 -0
- package/bin/runners/runPolish.js +390 -0
- package/bin/runners/runPromptFirewall.js +211 -0
- package/bin/runners/runProve.js +1411 -0
- package/bin/runners/runQuickstart.js +531 -0
- package/bin/runners/runReality.js +2260 -0
- package/bin/runners/runReport.js +726 -0
- package/bin/runners/runRuntime.js +110 -0
- package/bin/runners/runSafelist.js +1190 -0
- package/bin/runners/runScan.js +688 -0
- package/bin/runners/runShield.js +1282 -0
- package/bin/runners/runShip.js +1660 -0
- package/bin/runners/runTruth.d.ts +5 -0
- package/bin/runners/runTruth.js +101 -0
- package/bin/runners/runValidate.js +179 -0
- package/bin/runners/runWatch.js +478 -0
- package/bin/runners/utils.js +360 -0
- package/bin/scan.js +617 -0
- package/bin/vibecheck.js +1617 -0
- package/dist/guardrail/index.d.ts +2405 -0
- package/dist/guardrail/index.js +9747 -0
- package/dist/guardrail/index.js.map +1 -0
- package/dist/scanner/index.d.ts +282 -0
- package/dist/scanner/index.js +3395 -0
- package/dist/scanner/index.js.map +1 -0
- package/package.json +123 -104
- package/README.md +0 -491
- package/dist/index.js +0 -99711
- package/dist/index.js.map +0 -1
|
@@ -0,0 +1,948 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* vibecheck safelist - Schema & Validation
|
|
3
|
+
*
|
|
4
|
+
* ═══════════════════════════════════════════════════════════════════════════════
|
|
5
|
+
* A SCALPEL, NOT A TRASH CAN
|
|
6
|
+
* ═══════════════════════════════════════════════════════════════════════════════
|
|
7
|
+
*
|
|
8
|
+
* Responsible suppression with:
|
|
9
|
+
* - Required justification
|
|
10
|
+
* - Owner accountability
|
|
11
|
+
* - Optional expiration
|
|
12
|
+
* - Audit trail
|
|
13
|
+
* - Scope control (repo-wide vs local)
|
|
14
|
+
* - Abuse prevention limits
|
|
15
|
+
* - Conflict detection
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
"use strict";
|
|
19
|
+
|
|
20
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
21
|
+
// SCHEMA VERSION - Increment on breaking changes
|
|
22
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
23
|
+
|
|
24
|
+
const SCHEMA_VERSION = 2;
|
|
25
|
+
|
|
26
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
27
|
+
// LIMITS - Prevent safelist abuse
|
|
28
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
29
|
+
|
|
30
|
+
const LIMITS = {
|
|
31
|
+
MAX_ENTRIES_PER_FILE: 500, // Max entries in a single safelist file
|
|
32
|
+
MAX_REASON_LENGTH: 500, // Max characters in reason
|
|
33
|
+
MAX_PATTERN_LENGTH: 200, // Max regex pattern length
|
|
34
|
+
MAX_FILE_PATH_LENGTH: 300, // Max file path length
|
|
35
|
+
MIN_REASON_LENGTH: 10, // Min characters in reason
|
|
36
|
+
MAX_LINE_RANGE: 1000, // Max lines in a line range
|
|
37
|
+
MAX_OWNER_NAME_LENGTH: 100, // Max owner name length
|
|
38
|
+
MAX_TICKET_URL_LENGTH: 500, // Max ticket URL length
|
|
39
|
+
WARN_ENTRIES_THRESHOLD: 100, // Warn when exceeding this many entries
|
|
40
|
+
STALE_DAYS_THRESHOLD: 90, // Days without match before considered stale
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
44
|
+
// DANGEROUS PATTERNS - Patterns that suppress too much
|
|
45
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
46
|
+
|
|
47
|
+
const DANGEROUS_PATTERNS = [
|
|
48
|
+
/^\.\*$/, // Matches everything
|
|
49
|
+
/^\.\+$/, // Matches everything non-empty
|
|
50
|
+
/^\[^\\s\]\*$/i, // Matches any non-whitespace
|
|
51
|
+
/^\\w\*$/i, // Matches any word
|
|
52
|
+
/^\\S\*$/i, // Matches any non-whitespace
|
|
53
|
+
/^\.\{0,\}$/, // Matches everything (alternate)
|
|
54
|
+
];
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Check if a pattern is dangerously broad
|
|
58
|
+
*/
|
|
59
|
+
function isDangerousPattern(pattern) {
|
|
60
|
+
if (!pattern || pattern.length < 3) return true; // Too short
|
|
61
|
+
|
|
62
|
+
for (const dangerous of DANGEROUS_PATTERNS) {
|
|
63
|
+
if (dangerous.test(pattern)) return true;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Check for patterns that would match almost anything
|
|
67
|
+
try {
|
|
68
|
+
const regex = new RegExp(pattern, "i");
|
|
69
|
+
const testStrings = ["a", "test", "ERROR", "123", "foo_bar"];
|
|
70
|
+
const matchCount = testStrings.filter(s => regex.test(s)).length;
|
|
71
|
+
if (matchCount >= 4) return true; // Matches too many test strings
|
|
72
|
+
} catch {
|
|
73
|
+
// Invalid regex - will be caught by other validation
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return false;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
80
|
+
// ENTRY SCHEMA
|
|
81
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Safelist entry schema
|
|
85
|
+
* @typedef {Object} SafelistEntry
|
|
86
|
+
* @property {string} id - Unique entry ID (e.g., "SL_abc123")
|
|
87
|
+
* @property {string} type - Entry type: "finding" | "pattern" | "rule" | "file"
|
|
88
|
+
* @property {Object} target - What to suppress
|
|
89
|
+
* @property {string} [target.findingId] - Specific finding ID
|
|
90
|
+
* @property {string} [target.pattern] - Regex pattern to match
|
|
91
|
+
* @property {string} [target.ruleId] - Rule ID to suppress
|
|
92
|
+
* @property {string} [target.file] - File path (glob supported)
|
|
93
|
+
* @property {number[]} [target.lines] - Line range [start, end]
|
|
94
|
+
* @property {Object} justification - Required justification
|
|
95
|
+
* @property {string} justification.reason - Why this is suppressed
|
|
96
|
+
* @property {string} justification.category - Category of justification
|
|
97
|
+
* @property {string} [justification.ticket] - Link to issue/ticket
|
|
98
|
+
* @property {Object} owner - Who is responsible
|
|
99
|
+
* @property {string} owner.name - Person/team name
|
|
100
|
+
* @property {string} [owner.email] - Contact email
|
|
101
|
+
* @property {string} [owner.team] - Team name
|
|
102
|
+
* @property {Object} scope - Where this applies
|
|
103
|
+
* @property {string} scope.type - "repo" | "local" | "branch"
|
|
104
|
+
* @property {string[]} [scope.commands] - Commands this applies to (default: all)
|
|
105
|
+
* @property {Object} lifecycle - Temporal properties
|
|
106
|
+
* @property {string} lifecycle.createdAt - ISO timestamp
|
|
107
|
+
* @property {string} lifecycle.createdBy - Who created (CLI user, CI, etc.)
|
|
108
|
+
* @property {string} [lifecycle.expiresAt] - ISO timestamp for expiry
|
|
109
|
+
* @property {string} [lifecycle.reviewAt] - ISO timestamp for review reminder
|
|
110
|
+
* @property {string} [lifecycle.lastMatchedAt] - Last time this matched a finding
|
|
111
|
+
* @property {number} lifecycle.matchCount - How many times this has matched
|
|
112
|
+
* @property {Object} [audit] - Audit trail
|
|
113
|
+
* @property {Array} [audit.history] - Change history
|
|
114
|
+
*/
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Justification categories - must pick one
|
|
118
|
+
*/
|
|
119
|
+
const JUSTIFICATION_CATEGORIES = {
|
|
120
|
+
"false-positive": {
|
|
121
|
+
name: "False Positive",
|
|
122
|
+
description: "The finding is incorrect - code is actually fine",
|
|
123
|
+
requiresTicket: false,
|
|
124
|
+
maxExpiry: null, // Can be permanent
|
|
125
|
+
},
|
|
126
|
+
"accepted-risk": {
|
|
127
|
+
name: "Accepted Risk",
|
|
128
|
+
description: "Risk acknowledged and accepted by team",
|
|
129
|
+
requiresTicket: true, // Should have a ticket
|
|
130
|
+
maxExpiry: 365, // Max 1 year, then review
|
|
131
|
+
},
|
|
132
|
+
"test-fixture": {
|
|
133
|
+
name: "Test Fixture",
|
|
134
|
+
description: "Intentional test data/mock",
|
|
135
|
+
requiresTicket: false,
|
|
136
|
+
maxExpiry: null,
|
|
137
|
+
},
|
|
138
|
+
"legacy-code": {
|
|
139
|
+
name: "Legacy Code",
|
|
140
|
+
description: "Known issue in legacy code pending refactor",
|
|
141
|
+
requiresTicket: true,
|
|
142
|
+
maxExpiry: 180, // Max 6 months
|
|
143
|
+
},
|
|
144
|
+
"third-party": {
|
|
145
|
+
name: "Third-Party Code",
|
|
146
|
+
description: "Issue in vendored/third-party code we don't control",
|
|
147
|
+
requiresTicket: false,
|
|
148
|
+
maxExpiry: null,
|
|
149
|
+
},
|
|
150
|
+
"documentation": {
|
|
151
|
+
name: "Documentation/Example",
|
|
152
|
+
description: "Intentional example code in docs",
|
|
153
|
+
requiresTicket: false,
|
|
154
|
+
maxExpiry: null,
|
|
155
|
+
},
|
|
156
|
+
"temporary": {
|
|
157
|
+
name: "Temporary Suppression",
|
|
158
|
+
description: "Short-term suppression during development",
|
|
159
|
+
requiresTicket: false,
|
|
160
|
+
maxExpiry: 30, // Max 30 days
|
|
161
|
+
},
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Scope types
|
|
166
|
+
*/
|
|
167
|
+
const SCOPE_TYPES = {
|
|
168
|
+
"repo": {
|
|
169
|
+
name: "Repository-Wide",
|
|
170
|
+
description: "Applies to entire repo, committed to version control",
|
|
171
|
+
file: ".vibecheck/safelist.json",
|
|
172
|
+
gitIgnore: false,
|
|
173
|
+
},
|
|
174
|
+
"local": {
|
|
175
|
+
name: "Local Only",
|
|
176
|
+
description: "Only applies to this machine, not committed",
|
|
177
|
+
file: ".vibecheck/safelist.local.json",
|
|
178
|
+
gitIgnore: true,
|
|
179
|
+
},
|
|
180
|
+
"branch": {
|
|
181
|
+
name: "Branch-Specific",
|
|
182
|
+
description: "Only applies to specific branches",
|
|
183
|
+
file: ".vibecheck/safelist.json",
|
|
184
|
+
gitIgnore: false,
|
|
185
|
+
},
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Commands that respect the safelist
|
|
190
|
+
*/
|
|
191
|
+
const SAFELIST_COMMANDS = [
|
|
192
|
+
"audit", // vibecheck audit
|
|
193
|
+
"scan", // vibecheck scan (alias)
|
|
194
|
+
"ship", // vibecheck ship
|
|
195
|
+
"polish", // vibecheck polish
|
|
196
|
+
"prove", // vibecheck prove
|
|
197
|
+
"reality", // vibecheck reality
|
|
198
|
+
"guard", // vibecheck guard
|
|
199
|
+
];
|
|
200
|
+
|
|
201
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
202
|
+
// VALIDATION
|
|
203
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Validation error
|
|
207
|
+
*/
|
|
208
|
+
class SafelistValidationError extends Error {
|
|
209
|
+
constructor(message, field, value) {
|
|
210
|
+
super(message);
|
|
211
|
+
this.name = "SafelistValidationError";
|
|
212
|
+
this.field = field;
|
|
213
|
+
this.value = value;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Validate a safelist entry
|
|
219
|
+
* @param {SafelistEntry} entry - Entry to validate
|
|
220
|
+
* @param {Object} opts - Validation options
|
|
221
|
+
* @param {boolean} opts.strict - Enable strict validation (default: true)
|
|
222
|
+
* @returns {{ valid: boolean, errors: string[], warnings: string[] }}
|
|
223
|
+
*/
|
|
224
|
+
function validateEntry(entry, opts = {}) {
|
|
225
|
+
const { strict = true } = opts;
|
|
226
|
+
const errors = [];
|
|
227
|
+
const warnings = [];
|
|
228
|
+
|
|
229
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
230
|
+
// ID VALIDATION
|
|
231
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
232
|
+
|
|
233
|
+
if (!entry.id || typeof entry.id !== "string") {
|
|
234
|
+
errors.push("Missing or invalid 'id' field");
|
|
235
|
+
} else {
|
|
236
|
+
if (!entry.id.startsWith("SL_")) {
|
|
237
|
+
errors.push("Entry ID must start with 'SL_'");
|
|
238
|
+
}
|
|
239
|
+
if (entry.id.length > 50) {
|
|
240
|
+
errors.push("Entry ID too long (max 50 characters)");
|
|
241
|
+
}
|
|
242
|
+
if (!/^SL_[a-zA-Z0-9_]+$/.test(entry.id)) {
|
|
243
|
+
errors.push("Entry ID contains invalid characters (only alphanumeric and underscore allowed)");
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
248
|
+
// TYPE VALIDATION
|
|
249
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
250
|
+
|
|
251
|
+
if (!entry.type || !["finding", "pattern", "rule", "file"].includes(entry.type)) {
|
|
252
|
+
errors.push("Invalid 'type' - must be: finding, pattern, rule, or file");
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
256
|
+
// TARGET VALIDATION
|
|
257
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
258
|
+
|
|
259
|
+
if (!entry.target || typeof entry.target !== "object") {
|
|
260
|
+
errors.push("Missing 'target' object");
|
|
261
|
+
} else {
|
|
262
|
+
const hasTarget = entry.target.findingId || entry.target.pattern ||
|
|
263
|
+
entry.target.ruleId || entry.target.file;
|
|
264
|
+
if (!hasTarget) {
|
|
265
|
+
errors.push("Target must have at least one of: findingId, pattern, ruleId, file");
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// Validate finding ID format
|
|
269
|
+
if (entry.target.findingId) {
|
|
270
|
+
if (typeof entry.target.findingId !== "string") {
|
|
271
|
+
errors.push("Finding ID must be a string");
|
|
272
|
+
} else if (entry.target.findingId.length > 200) {
|
|
273
|
+
errors.push("Finding ID too long (max 200 characters)");
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// Validate pattern
|
|
278
|
+
if (entry.target.pattern) {
|
|
279
|
+
if (typeof entry.target.pattern !== "string") {
|
|
280
|
+
errors.push("Pattern must be a string");
|
|
281
|
+
} else {
|
|
282
|
+
if (entry.target.pattern.length > LIMITS.MAX_PATTERN_LENGTH) {
|
|
283
|
+
errors.push(`Pattern too long (max ${LIMITS.MAX_PATTERN_LENGTH} characters)`);
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// Validate regex syntax
|
|
287
|
+
try {
|
|
288
|
+
new RegExp(entry.target.pattern);
|
|
289
|
+
} catch (e) {
|
|
290
|
+
errors.push(`Invalid regex pattern: ${e.message}`);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// Check for dangerous patterns
|
|
294
|
+
if (strict && isDangerousPattern(entry.target.pattern)) {
|
|
295
|
+
errors.push("Pattern is too broad and would match too many findings. Be more specific.");
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
// Check for common regex mistakes
|
|
299
|
+
if (entry.target.pattern.includes("\\\\") && !entry.target.pattern.includes("\\\\\\\\")) {
|
|
300
|
+
warnings.push("Pattern contains double backslash - did you mean to escape a backslash?");
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
// Validate rule ID
|
|
306
|
+
if (entry.target.ruleId) {
|
|
307
|
+
if (typeof entry.target.ruleId !== "string") {
|
|
308
|
+
errors.push("Rule ID must be a string");
|
|
309
|
+
} else if (!/^[A-Z][A-Z0-9_]*$/i.test(entry.target.ruleId)) {
|
|
310
|
+
warnings.push("Rule ID should be uppercase with underscores (e.g., MOCK_DATA, HARDCODED_SECRET)");
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// Validate file path
|
|
315
|
+
if (entry.target.file) {
|
|
316
|
+
if (typeof entry.target.file !== "string") {
|
|
317
|
+
errors.push("File path must be a string");
|
|
318
|
+
} else {
|
|
319
|
+
if (entry.target.file.length > LIMITS.MAX_FILE_PATH_LENGTH) {
|
|
320
|
+
errors.push(`File path too long (max ${LIMITS.MAX_FILE_PATH_LENGTH} characters)`);
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
// Check for suspicious file patterns
|
|
324
|
+
if (entry.target.file === "*" || entry.target.file === "**" || entry.target.file === "**/*") {
|
|
325
|
+
errors.push("File pattern is too broad - would match all files");
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
// Normalize path separators warning
|
|
329
|
+
if (entry.target.file.includes("\\")) {
|
|
330
|
+
warnings.push("File path contains backslashes - use forward slashes for cross-platform compatibility");
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
// Check for absolute paths
|
|
334
|
+
if (/^[A-Za-z]:/.test(entry.target.file) || entry.target.file.startsWith("/")) {
|
|
335
|
+
warnings.push("File path appears to be absolute - consider using relative paths");
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
// Validate line range
|
|
341
|
+
if (entry.target.lines) {
|
|
342
|
+
if (!Array.isArray(entry.target.lines) || entry.target.lines.length !== 2) {
|
|
343
|
+
errors.push("Lines must be an array of [start, end]");
|
|
344
|
+
} else {
|
|
345
|
+
const [start, end] = entry.target.lines;
|
|
346
|
+
|
|
347
|
+
if (!Number.isInteger(start) || !Number.isInteger(end)) {
|
|
348
|
+
errors.push("Line numbers must be integers");
|
|
349
|
+
} else {
|
|
350
|
+
if (start < 1) {
|
|
351
|
+
errors.push("Line numbers must be >= 1");
|
|
352
|
+
}
|
|
353
|
+
if (start > end) {
|
|
354
|
+
errors.push("Line range start must be <= end");
|
|
355
|
+
}
|
|
356
|
+
if (end - start > LIMITS.MAX_LINE_RANGE) {
|
|
357
|
+
errors.push(`Line range too large (max ${LIMITS.MAX_LINE_RANGE} lines)`);
|
|
358
|
+
}
|
|
359
|
+
if (end > 100000) {
|
|
360
|
+
warnings.push("Line range end is very large - are you sure this is correct?");
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
// Lines require file
|
|
366
|
+
if (!entry.target.file) {
|
|
367
|
+
errors.push("Line range requires a file path");
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
373
|
+
// JUSTIFICATION VALIDATION (REQUIRED - this is a scalpel!)
|
|
374
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
375
|
+
|
|
376
|
+
if (!entry.justification || typeof entry.justification !== "object") {
|
|
377
|
+
errors.push("Missing 'justification' object - reason is REQUIRED");
|
|
378
|
+
} else {
|
|
379
|
+
// Reason validation
|
|
380
|
+
if (!entry.justification.reason) {
|
|
381
|
+
errors.push("Justification reason is REQUIRED");
|
|
382
|
+
} else if (typeof entry.justification.reason !== "string") {
|
|
383
|
+
errors.push("Justification reason must be a string");
|
|
384
|
+
} else {
|
|
385
|
+
if (entry.justification.reason.length < LIMITS.MIN_REASON_LENGTH) {
|
|
386
|
+
errors.push(`Justification reason must be at least ${LIMITS.MIN_REASON_LENGTH} characters`);
|
|
387
|
+
}
|
|
388
|
+
if (entry.justification.reason.length > LIMITS.MAX_REASON_LENGTH) {
|
|
389
|
+
errors.push(`Justification reason too long (max ${LIMITS.MAX_REASON_LENGTH} characters)`);
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
// Check for low-effort reasons
|
|
393
|
+
const lowEffortReasons = [
|
|
394
|
+
/^false positive$/i,
|
|
395
|
+
/^ignore$/i,
|
|
396
|
+
/^suppress$/i,
|
|
397
|
+
/^skip$/i,
|
|
398
|
+
/^not an issue$/i,
|
|
399
|
+
/^test$/i,
|
|
400
|
+
/^asdf/i,
|
|
401
|
+
/^xxx/i,
|
|
402
|
+
/^todo/i,
|
|
403
|
+
];
|
|
404
|
+
if (strict && lowEffortReasons.some(r => r.test(entry.justification.reason.trim()))) {
|
|
405
|
+
errors.push("Justification reason is too vague. Explain WHY this should be suppressed.");
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
// Category validation
|
|
410
|
+
if (!entry.justification.category) {
|
|
411
|
+
errors.push("Justification category is REQUIRED");
|
|
412
|
+
} else if (!JUSTIFICATION_CATEGORIES[entry.justification.category]) {
|
|
413
|
+
errors.push(`Invalid justification category. Must be one of: ${Object.keys(JUSTIFICATION_CATEGORIES).join(", ")}`);
|
|
414
|
+
} else {
|
|
415
|
+
const category = JUSTIFICATION_CATEGORIES[entry.justification.category];
|
|
416
|
+
if (category.requiresTicket && !entry.justification.ticket) {
|
|
417
|
+
errors.push(`Category '${entry.justification.category}' requires a ticket/issue link`);
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
// Ticket validation
|
|
422
|
+
if (entry.justification.ticket) {
|
|
423
|
+
if (typeof entry.justification.ticket !== "string") {
|
|
424
|
+
errors.push("Ticket must be a string");
|
|
425
|
+
} else {
|
|
426
|
+
if (entry.justification.ticket.length > LIMITS.MAX_TICKET_URL_LENGTH) {
|
|
427
|
+
errors.push(`Ticket URL too long (max ${LIMITS.MAX_TICKET_URL_LENGTH} characters)`);
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
// Validate URL format
|
|
431
|
+
if (!isValidUrl(entry.justification.ticket)) {
|
|
432
|
+
warnings.push("Ticket doesn't appear to be a valid URL");
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
439
|
+
// OWNER VALIDATION (REQUIRED - accountability)
|
|
440
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
441
|
+
|
|
442
|
+
if (!entry.owner || typeof entry.owner !== "object") {
|
|
443
|
+
errors.push("Missing 'owner' object - accountability is REQUIRED");
|
|
444
|
+
} else {
|
|
445
|
+
if (!entry.owner.name) {
|
|
446
|
+
errors.push("Owner name is REQUIRED");
|
|
447
|
+
} else if (typeof entry.owner.name !== "string") {
|
|
448
|
+
errors.push("Owner name must be a string");
|
|
449
|
+
} else {
|
|
450
|
+
if (entry.owner.name.length < 2) {
|
|
451
|
+
errors.push("Owner name must be at least 2 characters");
|
|
452
|
+
}
|
|
453
|
+
if (entry.owner.name.length > LIMITS.MAX_OWNER_NAME_LENGTH) {
|
|
454
|
+
errors.push(`Owner name too long (max ${LIMITS.MAX_OWNER_NAME_LENGTH} characters)`);
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
// Check for placeholder names
|
|
458
|
+
const placeholderNames = ["test", "user", "admin", "me", "todo", "asdf", "xxx"];
|
|
459
|
+
if (strict && placeholderNames.includes(entry.owner.name.toLowerCase())) {
|
|
460
|
+
warnings.push("Owner name appears to be a placeholder - use a real name or team");
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
// Email validation
|
|
465
|
+
if (entry.owner.email) {
|
|
466
|
+
if (typeof entry.owner.email !== "string") {
|
|
467
|
+
errors.push("Owner email must be a string");
|
|
468
|
+
} else if (!isValidEmail(entry.owner.email)) {
|
|
469
|
+
warnings.push("Owner email doesn't appear to be valid");
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
// Team validation
|
|
474
|
+
if (entry.owner.team) {
|
|
475
|
+
if (typeof entry.owner.team !== "string") {
|
|
476
|
+
errors.push("Owner team must be a string");
|
|
477
|
+
} else if (entry.owner.team.length > 100) {
|
|
478
|
+
errors.push("Owner team name too long (max 100 characters)");
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
484
|
+
// SCOPE VALIDATION
|
|
485
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
486
|
+
|
|
487
|
+
if (!entry.scope || typeof entry.scope !== "object") {
|
|
488
|
+
errors.push("Missing 'scope' object");
|
|
489
|
+
} else {
|
|
490
|
+
if (!entry.scope.type || !SCOPE_TYPES[entry.scope.type]) {
|
|
491
|
+
errors.push(`Invalid scope type. Must be one of: ${Object.keys(SCOPE_TYPES).join(", ")}`);
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
// Validate commands if specified
|
|
495
|
+
if (entry.scope.commands) {
|
|
496
|
+
if (!Array.isArray(entry.scope.commands)) {
|
|
497
|
+
errors.push("Scope commands must be an array");
|
|
498
|
+
} else {
|
|
499
|
+
const invalidCommands = entry.scope.commands.filter(c => !SAFELIST_COMMANDS.includes(c));
|
|
500
|
+
if (invalidCommands.length > 0) {
|
|
501
|
+
errors.push(`Invalid commands in scope: ${invalidCommands.join(", ")}`);
|
|
502
|
+
}
|
|
503
|
+
if (entry.scope.commands.length === 0) {
|
|
504
|
+
warnings.push("Empty commands array - entry won't apply to any commands");
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
// Validate branch if specified
|
|
510
|
+
if (entry.scope.type === "branch" && !entry.scope.branches) {
|
|
511
|
+
warnings.push("Branch scope type but no branches specified");
|
|
512
|
+
}
|
|
513
|
+
if (entry.scope.branches) {
|
|
514
|
+
if (!Array.isArray(entry.scope.branches)) {
|
|
515
|
+
errors.push("Scope branches must be an array");
|
|
516
|
+
} else if (entry.scope.branches.length === 0) {
|
|
517
|
+
warnings.push("Empty branches array");
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
523
|
+
// LIFECYCLE VALIDATION
|
|
524
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
525
|
+
|
|
526
|
+
if (!entry.lifecycle || typeof entry.lifecycle !== "object") {
|
|
527
|
+
errors.push("Missing 'lifecycle' object");
|
|
528
|
+
} else {
|
|
529
|
+
// Created timestamp
|
|
530
|
+
if (!entry.lifecycle.createdAt) {
|
|
531
|
+
errors.push("Missing lifecycle.createdAt timestamp");
|
|
532
|
+
} else if (!isValidISODate(entry.lifecycle.createdAt)) {
|
|
533
|
+
errors.push("Invalid lifecycle.createdAt timestamp (must be ISO 8601)");
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
// Expiry validation
|
|
537
|
+
if (entry.lifecycle.expiresAt) {
|
|
538
|
+
if (!isValidISODate(entry.lifecycle.expiresAt)) {
|
|
539
|
+
errors.push("Invalid lifecycle.expiresAt timestamp (must be ISO 8601)");
|
|
540
|
+
} else if (entry.lifecycle.createdAt) {
|
|
541
|
+
const expiresAt = new Date(entry.lifecycle.expiresAt);
|
|
542
|
+
const createdAt = new Date(entry.lifecycle.createdAt);
|
|
543
|
+
|
|
544
|
+
if (expiresAt <= createdAt) {
|
|
545
|
+
errors.push("Expiry date must be after creation date");
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
// Check expiry constraints based on category
|
|
551
|
+
if (entry.justification?.category) {
|
|
552
|
+
const category = JUSTIFICATION_CATEGORIES[entry.justification.category];
|
|
553
|
+
if (category?.maxExpiry) {
|
|
554
|
+
if (!entry.lifecycle.expiresAt) {
|
|
555
|
+
errors.push(`Category '${entry.justification.category}' requires an expiration date (max ${category.maxExpiry} days)`);
|
|
556
|
+
} else if (entry.lifecycle.createdAt) {
|
|
557
|
+
const expiresAt = new Date(entry.lifecycle.expiresAt);
|
|
558
|
+
const createdAt = new Date(entry.lifecycle.createdAt);
|
|
559
|
+
const daysDiff = (expiresAt - createdAt) / (1000 * 60 * 60 * 24);
|
|
560
|
+
if (daysDiff > category.maxExpiry) {
|
|
561
|
+
errors.push(`Category '${entry.justification.category}' max expiry is ${category.maxExpiry} days, got ${Math.round(daysDiff)} days`);
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
// Review date validation
|
|
568
|
+
if (entry.lifecycle.reviewAt) {
|
|
569
|
+
if (!isValidISODate(entry.lifecycle.reviewAt)) {
|
|
570
|
+
errors.push("Invalid lifecycle.reviewAt timestamp (must be ISO 8601)");
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
// Match count validation
|
|
575
|
+
if (entry.lifecycle.matchCount !== undefined) {
|
|
576
|
+
if (!Number.isInteger(entry.lifecycle.matchCount) || entry.lifecycle.matchCount < 0) {
|
|
577
|
+
errors.push("Match count must be a non-negative integer");
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
return {
|
|
583
|
+
valid: errors.length === 0,
|
|
584
|
+
errors,
|
|
585
|
+
warnings,
|
|
586
|
+
};
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
590
|
+
// VALIDATION HELPERS
|
|
591
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
592
|
+
|
|
593
|
+
/**
|
|
594
|
+
* Check if string is a valid ISO 8601 date
|
|
595
|
+
*/
|
|
596
|
+
function isValidISODate(str) {
|
|
597
|
+
if (typeof str !== "string") return false;
|
|
598
|
+
const date = new Date(str);
|
|
599
|
+
return !isNaN(date.getTime()) && str.includes("T");
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
/**
|
|
603
|
+
* Check if string is a valid email
|
|
604
|
+
*/
|
|
605
|
+
function isValidEmail(str) {
|
|
606
|
+
if (typeof str !== "string") return false;
|
|
607
|
+
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(str);
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
/**
|
|
611
|
+
* Check if string is a valid URL
|
|
612
|
+
*/
|
|
613
|
+
function isValidUrl(str) {
|
|
614
|
+
if (typeof str !== "string") return false;
|
|
615
|
+
try {
|
|
616
|
+
new URL(str);
|
|
617
|
+
return true;
|
|
618
|
+
} catch {
|
|
619
|
+
// Allow ticket IDs like JIRA-123
|
|
620
|
+
return /^[A-Z]+-\d+$/i.test(str);
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
/**
|
|
625
|
+
* Validate entire safelist file
|
|
626
|
+
* @param {Object} safelist - Safelist object
|
|
627
|
+
* @param {Object} opts - Validation options
|
|
628
|
+
* @returns {{ valid: boolean, errors: string[], warnings: string[], stats: Object }}
|
|
629
|
+
*/
|
|
630
|
+
function validateSafelist(safelist, opts = {}) {
|
|
631
|
+
const errors = [];
|
|
632
|
+
const warnings = [];
|
|
633
|
+
const stats = {
|
|
634
|
+
total: 0,
|
|
635
|
+
valid: 0,
|
|
636
|
+
invalid: 0,
|
|
637
|
+
expired: 0,
|
|
638
|
+
expiringSoon: 0,
|
|
639
|
+
dueForReview: 0,
|
|
640
|
+
stale: 0,
|
|
641
|
+
conflicts: [],
|
|
642
|
+
};
|
|
643
|
+
|
|
644
|
+
if (!safelist || typeof safelist !== "object") {
|
|
645
|
+
errors.push("Invalid safelist format");
|
|
646
|
+
return { valid: false, errors, warnings, stats };
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
// Version check
|
|
650
|
+
if (safelist.version !== SCHEMA_VERSION) {
|
|
651
|
+
if (!safelist.version) {
|
|
652
|
+
errors.push("Missing schema version");
|
|
653
|
+
} else if (safelist.version < SCHEMA_VERSION) {
|
|
654
|
+
warnings.push(`Safelist version ${safelist.version} is outdated (current: ${SCHEMA_VERSION}). Run 'vibecheck safelist migrate'.`);
|
|
655
|
+
} else {
|
|
656
|
+
errors.push(`Safelist version ${safelist.version} is newer than supported (${SCHEMA_VERSION}). Update vibecheck CLI.`);
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
if (!Array.isArray(safelist.entries)) {
|
|
661
|
+
errors.push("Safelist must have 'entries' array");
|
|
662
|
+
return { valid: false, errors, warnings, stats };
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
stats.total = safelist.entries.length;
|
|
666
|
+
|
|
667
|
+
// Check entry count limits
|
|
668
|
+
if (safelist.entries.length > LIMITS.MAX_ENTRIES_PER_FILE) {
|
|
669
|
+
errors.push(`Too many entries (${safelist.entries.length}). Max: ${LIMITS.MAX_ENTRIES_PER_FILE}. Consider cleaning up expired/unused entries.`);
|
|
670
|
+
} else if (safelist.entries.length > LIMITS.WARN_ENTRIES_THRESHOLD) {
|
|
671
|
+
warnings.push(`High number of entries (${safelist.entries.length}). Consider reviewing and cleaning up.`);
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
const now = Date.now();
|
|
675
|
+
const sevenDays = 7 * 24 * 60 * 60 * 1000;
|
|
676
|
+
const staleDays = LIMITS.STALE_DAYS_THRESHOLD * 24 * 60 * 60 * 1000;
|
|
677
|
+
|
|
678
|
+
// Validate each entry
|
|
679
|
+
const ids = new Set();
|
|
680
|
+
const patternMap = new Map(); // For conflict detection
|
|
681
|
+
const fileMap = new Map(); // For conflict detection
|
|
682
|
+
|
|
683
|
+
for (let i = 0; i < safelist.entries.length; i++) {
|
|
684
|
+
const entry = safelist.entries[i];
|
|
685
|
+
const validation = validateEntry(entry, opts);
|
|
686
|
+
|
|
687
|
+
if (!validation.valid) {
|
|
688
|
+
stats.invalid++;
|
|
689
|
+
errors.push(`Entry ${i} (${entry.id || "no-id"}): ${validation.errors.join("; ")}`);
|
|
690
|
+
} else {
|
|
691
|
+
stats.valid++;
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
// Add entry warnings
|
|
695
|
+
if (validation.warnings) {
|
|
696
|
+
warnings.push(...validation.warnings.map(w => `Entry ${entry.id}: ${w}`));
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
// Check for duplicate IDs
|
|
700
|
+
if (entry.id) {
|
|
701
|
+
if (ids.has(entry.id)) {
|
|
702
|
+
errors.push(`Duplicate entry ID: ${entry.id}`);
|
|
703
|
+
}
|
|
704
|
+
ids.add(entry.id);
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
// Check for expired entries
|
|
708
|
+
if (entry.lifecycle?.expiresAt) {
|
|
709
|
+
const expiresAt = new Date(entry.lifecycle.expiresAt).getTime();
|
|
710
|
+
if (expiresAt < now) {
|
|
711
|
+
stats.expired++;
|
|
712
|
+
warnings.push(`Entry ${entry.id} has expired (${new Date(expiresAt).toLocaleDateString()})`);
|
|
713
|
+
} else if (expiresAt < now + sevenDays) {
|
|
714
|
+
stats.expiringSoon++;
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
// Check for entries due for review
|
|
719
|
+
if (entry.lifecycle?.reviewAt) {
|
|
720
|
+
const reviewAt = new Date(entry.lifecycle.reviewAt).getTime();
|
|
721
|
+
if (reviewAt < now) {
|
|
722
|
+
stats.dueForReview++;
|
|
723
|
+
warnings.push(`Entry ${entry.id} is due for review`);
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
// Check for stale entries (never matched after creation period)
|
|
728
|
+
if (entry.lifecycle?.createdAt) {
|
|
729
|
+
const createdAt = new Date(entry.lifecycle.createdAt).getTime();
|
|
730
|
+
const matchCount = entry.lifecycle?.matchCount || 0;
|
|
731
|
+
if (createdAt < now - staleDays && matchCount === 0) {
|
|
732
|
+
stats.stale++;
|
|
733
|
+
warnings.push(`Entry ${entry.id} has never matched any findings (${LIMITS.STALE_DAYS_THRESHOLD}+ days old)`);
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
// Conflict detection - overlapping patterns
|
|
738
|
+
if (entry.target?.pattern) {
|
|
739
|
+
for (const [existingId, existingPattern] of patternMap) {
|
|
740
|
+
if (patternsOverlap(entry.target.pattern, existingPattern)) {
|
|
741
|
+
stats.conflicts.push({ entry1: existingId, entry2: entry.id, type: "pattern" });
|
|
742
|
+
warnings.push(`Potential overlap: ${existingId} and ${entry.id} have similar patterns`);
|
|
743
|
+
}
|
|
744
|
+
}
|
|
745
|
+
patternMap.set(entry.id, entry.target.pattern);
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
// Conflict detection - same file
|
|
749
|
+
if (entry.target?.file) {
|
|
750
|
+
for (const [existingId, existingFile] of fileMap) {
|
|
751
|
+
if (filesOverlap(entry.target.file, existingFile)) {
|
|
752
|
+
// Only warn if they don't have different line ranges
|
|
753
|
+
const existingEntry = safelist.entries.find(e => e.id === existingId);
|
|
754
|
+
if (!entry.target.lines || !existingEntry?.target?.lines) {
|
|
755
|
+
stats.conflicts.push({ entry1: existingId, entry2: entry.id, type: "file" });
|
|
756
|
+
warnings.push(`Potential overlap: ${existingId} and ${entry.id} target same file`);
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
fileMap.set(entry.id, entry.target.file);
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
return {
|
|
765
|
+
valid: errors.length === 0,
|
|
766
|
+
errors,
|
|
767
|
+
warnings,
|
|
768
|
+
stats,
|
|
769
|
+
};
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
/**
|
|
773
|
+
* Check if two patterns might overlap
|
|
774
|
+
*/
|
|
775
|
+
function patternsOverlap(pattern1, pattern2) {
|
|
776
|
+
if (pattern1 === pattern2) return true;
|
|
777
|
+
|
|
778
|
+
// Check if one contains the other
|
|
779
|
+
if (pattern1.includes(pattern2) || pattern2.includes(pattern1)) return true;
|
|
780
|
+
|
|
781
|
+
// Try to detect regex subset relationships
|
|
782
|
+
try {
|
|
783
|
+
const testStrings = ["test", "error", "mock_data", "console.log", "TODO", "FIXME"];
|
|
784
|
+
const regex1 = new RegExp(pattern1, "i");
|
|
785
|
+
const regex2 = new RegExp(pattern2, "i");
|
|
786
|
+
|
|
787
|
+
const matches1 = testStrings.filter(s => regex1.test(s));
|
|
788
|
+
const matches2 = testStrings.filter(s => regex2.test(s));
|
|
789
|
+
|
|
790
|
+
// Check for overlap in matches
|
|
791
|
+
const overlap = matches1.filter(m => matches2.includes(m));
|
|
792
|
+
return overlap.length > 0 && (overlap.length === matches1.length || overlap.length === matches2.length);
|
|
793
|
+
} catch {
|
|
794
|
+
return false;
|
|
795
|
+
}
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
/**
|
|
799
|
+
* Check if two file patterns might overlap
|
|
800
|
+
*/
|
|
801
|
+
function filesOverlap(file1, file2) {
|
|
802
|
+
if (file1 === file2) return true;
|
|
803
|
+
|
|
804
|
+
// Normalize paths
|
|
805
|
+
const norm1 = file1.replace(/\\/g, "/");
|
|
806
|
+
const norm2 = file2.replace(/\\/g, "/");
|
|
807
|
+
|
|
808
|
+
// Check if one is a prefix of the other
|
|
809
|
+
if (norm1.startsWith(norm2) || norm2.startsWith(norm1)) return true;
|
|
810
|
+
|
|
811
|
+
// Check glob overlap
|
|
812
|
+
if (file1.includes("*") || file2.includes("*")) {
|
|
813
|
+
// Both are globs - check if they're the same base pattern
|
|
814
|
+
const base1 = norm1.replace(/\*+/g, "*");
|
|
815
|
+
const base2 = norm2.replace(/\*+/g, "*");
|
|
816
|
+
if (base1 === base2) return true;
|
|
817
|
+
}
|
|
818
|
+
|
|
819
|
+
return false;
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
823
|
+
// FACTORY FUNCTIONS
|
|
824
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
825
|
+
|
|
826
|
+
/**
|
|
827
|
+
* Generate a new safelist entry ID
|
|
828
|
+
*/
|
|
829
|
+
function generateEntryId() {
|
|
830
|
+
const timestamp = Date.now().toString(36);
|
|
831
|
+
const random = Math.random().toString(36).substring(2, 8);
|
|
832
|
+
return `SL_${timestamp}_${random}`;
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
/**
|
|
836
|
+
* Create a new safelist entry with defaults
|
|
837
|
+
* @param {Object} opts - Entry options
|
|
838
|
+
* @returns {SafelistEntry}
|
|
839
|
+
*/
|
|
840
|
+
function createEntry(opts) {
|
|
841
|
+
const {
|
|
842
|
+
type = "finding",
|
|
843
|
+
findingId,
|
|
844
|
+
pattern,
|
|
845
|
+
ruleId,
|
|
846
|
+
file,
|
|
847
|
+
lines,
|
|
848
|
+
reason,
|
|
849
|
+
category = "false-positive",
|
|
850
|
+
ticket,
|
|
851
|
+
ownerName,
|
|
852
|
+
ownerEmail,
|
|
853
|
+
ownerTeam,
|
|
854
|
+
scopeType = "repo",
|
|
855
|
+
commands,
|
|
856
|
+
expiresIn, // Days from now
|
|
857
|
+
reviewIn, // Days from now
|
|
858
|
+
} = opts;
|
|
859
|
+
|
|
860
|
+
const now = new Date().toISOString();
|
|
861
|
+
|
|
862
|
+
const entry = {
|
|
863
|
+
id: generateEntryId(),
|
|
864
|
+
type,
|
|
865
|
+
target: {},
|
|
866
|
+
justification: {
|
|
867
|
+
reason,
|
|
868
|
+
category,
|
|
869
|
+
},
|
|
870
|
+
owner: {
|
|
871
|
+
name: ownerName,
|
|
872
|
+
},
|
|
873
|
+
scope: {
|
|
874
|
+
type: scopeType,
|
|
875
|
+
},
|
|
876
|
+
lifecycle: {
|
|
877
|
+
createdAt: now,
|
|
878
|
+
createdBy: process.env.USER || process.env.USERNAME || "cli",
|
|
879
|
+
matchCount: 0,
|
|
880
|
+
},
|
|
881
|
+
};
|
|
882
|
+
|
|
883
|
+
// Target
|
|
884
|
+
if (findingId) entry.target.findingId = findingId;
|
|
885
|
+
if (pattern) entry.target.pattern = pattern;
|
|
886
|
+
if (ruleId) entry.target.ruleId = ruleId;
|
|
887
|
+
if (file) entry.target.file = file;
|
|
888
|
+
if (lines) entry.target.lines = lines;
|
|
889
|
+
|
|
890
|
+
// Justification
|
|
891
|
+
if (ticket) entry.justification.ticket = ticket;
|
|
892
|
+
|
|
893
|
+
// Owner
|
|
894
|
+
if (ownerEmail) entry.owner.email = ownerEmail;
|
|
895
|
+
if (ownerTeam) entry.owner.team = ownerTeam;
|
|
896
|
+
|
|
897
|
+
// Scope
|
|
898
|
+
if (commands) entry.scope.commands = commands;
|
|
899
|
+
|
|
900
|
+
// Lifecycle
|
|
901
|
+
if (expiresIn) {
|
|
902
|
+
entry.lifecycle.expiresAt = new Date(Date.now() + expiresIn * 24 * 60 * 60 * 1000).toISOString();
|
|
903
|
+
}
|
|
904
|
+
if (reviewIn) {
|
|
905
|
+
entry.lifecycle.reviewAt = new Date(Date.now() + reviewIn * 24 * 60 * 60 * 1000).toISOString();
|
|
906
|
+
}
|
|
907
|
+
|
|
908
|
+
return entry;
|
|
909
|
+
}
|
|
910
|
+
|
|
911
|
+
/**
|
|
912
|
+
* Create empty safelist
|
|
913
|
+
*/
|
|
914
|
+
function createEmptySafelist() {
|
|
915
|
+
return {
|
|
916
|
+
version: SCHEMA_VERSION,
|
|
917
|
+
metadata: {
|
|
918
|
+
createdAt: new Date().toISOString(),
|
|
919
|
+
description: "vibecheck safelist - Responsible finding suppression",
|
|
920
|
+
},
|
|
921
|
+
entries: [],
|
|
922
|
+
};
|
|
923
|
+
}
|
|
924
|
+
|
|
925
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
926
|
+
// EXPORTS
|
|
927
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
928
|
+
|
|
929
|
+
module.exports = {
|
|
930
|
+
SCHEMA_VERSION,
|
|
931
|
+
LIMITS,
|
|
932
|
+
JUSTIFICATION_CATEGORIES,
|
|
933
|
+
SCOPE_TYPES,
|
|
934
|
+
SAFELIST_COMMANDS,
|
|
935
|
+
DANGEROUS_PATTERNS,
|
|
936
|
+
SafelistValidationError,
|
|
937
|
+
isDangerousPattern,
|
|
938
|
+
isValidISODate,
|
|
939
|
+
isValidEmail,
|
|
940
|
+
isValidUrl,
|
|
941
|
+
validateEntry,
|
|
942
|
+
validateSafelist,
|
|
943
|
+
patternsOverlap,
|
|
944
|
+
filesOverlap,
|
|
945
|
+
generateEntryId,
|
|
946
|
+
createEntry,
|
|
947
|
+
createEmptySafelist,
|
|
948
|
+
};
|