code-auditor-mcp 3.1.0 → 3.4.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/.claude-plugin/marketplace.json +1 -1
- package/CHANGELOG.md +665 -1
- package/README.md +85 -11
- package/dist/__test_tmp__/spec18_test.d.ts +2 -0
- package/dist/__test_tmp__/spec18_test.d.ts.map +1 -0
- package/dist/__test_tmp__/spec18_test.js +5 -0
- package/dist/__test_tmp__/spec18_test.js.map +1 -0
- package/dist/__tests__/fixtures/spec-20/scripts/tool.d.ts +6 -0
- package/dist/__tests__/fixtures/spec-20/scripts/tool.d.ts.map +1 -0
- package/dist/__tests__/fixtures/spec-20/scripts/tool.js +12 -0
- package/dist/__tests__/fixtures/spec-20/scripts/tool.js.map +1 -0
- package/dist/__tests__/fixtures/spec-20/src/demo-module.d.ts +7 -0
- package/dist/__tests__/fixtures/spec-20/src/demo-module.d.ts.map +1 -0
- package/dist/__tests__/fixtures/spec-20/src/demo-module.js +13 -0
- package/dist/__tests__/fixtures/spec-20/src/demo-module.js.map +1 -0
- package/dist/analyzers/analyzerUtils.js +2 -2
- package/dist/analyzers/analyzerUtils.js.map +1 -1
- package/dist/analyzers/cross-language/APIContractAnalyzer.d.ts +1 -1
- package/dist/analyzers/cross-language/APIContractAnalyzer.d.ts.map +1 -1
- package/dist/analyzers/cross-language/APIContractAnalyzer.js +2 -4
- package/dist/analyzers/cross-language/APIContractAnalyzer.js.map +1 -1
- package/dist/analyzers/cross-language/CrossLanguageSOLIDAnalyzer.d.ts +3 -0
- package/dist/analyzers/cross-language/CrossLanguageSOLIDAnalyzer.d.ts.map +1 -1
- package/dist/analyzers/cross-language/CrossLanguageSOLIDAnalyzer.js +3 -5
- package/dist/analyzers/cross-language/CrossLanguageSOLIDAnalyzer.js.map +1 -1
- package/dist/analyzers/cross-language/DependencyGraphBuilder.d.ts.map +1 -1
- package/dist/analyzers/cross-language/DependencyGraphBuilder.js +0 -3
- package/dist/analyzers/cross-language/DependencyGraphBuilder.js.map +1 -1
- package/dist/analyzers/cross-language/SchemaValidator.d.ts +1 -1
- package/dist/analyzers/cross-language/SchemaValidator.d.ts.map +1 -1
- package/dist/analyzers/cross-language/SchemaValidator.js +2 -5
- package/dist/analyzers/cross-language/SchemaValidator.js.map +1 -1
- package/dist/analyzers/crossDomain/CrossDomainAnalyzer.d.ts +83 -0
- package/dist/analyzers/crossDomain/CrossDomainAnalyzer.d.ts.map +1 -0
- package/dist/analyzers/crossDomain/CrossDomainAnalyzer.js +611 -0
- package/dist/analyzers/crossDomain/CrossDomainAnalyzer.js.map +1 -0
- package/dist/analyzers/documentationAnalyzer.d.ts.map +1 -1
- package/dist/analyzers/documentationAnalyzer.js +8 -4
- package/dist/analyzers/documentationAnalyzer.js.map +1 -1
- package/dist/analyzers/orm/adapterRegistry.d.ts +41 -0
- package/dist/analyzers/orm/adapterRegistry.d.ts.map +1 -0
- package/dist/analyzers/orm/adapterRegistry.js +73 -0
- package/dist/analyzers/orm/adapterRegistry.js.map +1 -0
- package/dist/analyzers/orm/drizzleAdapter.d.ts +42 -0
- package/dist/analyzers/orm/drizzleAdapter.d.ts.map +1 -0
- package/dist/analyzers/orm/drizzleAdapter.js +166 -0
- package/dist/analyzers/orm/drizzleAdapter.js.map +1 -0
- package/dist/analyzers/orm/index.d.ts +14 -0
- package/dist/analyzers/orm/index.d.ts.map +1 -0
- package/dist/analyzers/orm/index.js +24 -0
- package/dist/analyzers/orm/index.js.map +1 -0
- package/dist/analyzers/orm/prismaAdapter.d.ts +45 -0
- package/dist/analyzers/orm/prismaAdapter.d.ts.map +1 -0
- package/dist/analyzers/orm/prismaAdapter.js +170 -0
- package/dist/analyzers/orm/prismaAdapter.js.map +1 -0
- package/dist/analyzers/orm/types.d.ts +63 -0
- package/dist/analyzers/orm/types.d.ts.map +1 -0
- package/dist/analyzers/orm/types.js +10 -0
- package/dist/analyzers/orm/types.js.map +1 -0
- package/dist/analyzers/provenance.d.ts +133 -0
- package/dist/analyzers/provenance.d.ts.map +1 -0
- package/dist/analyzers/provenance.js +1041 -0
- package/dist/analyzers/provenance.js.map +1 -0
- package/dist/analyzers/reactAnalyzer.d.ts.map +1 -1
- package/dist/analyzers/reactAnalyzer.js +117 -1
- package/dist/analyzers/reactAnalyzer.js.map +1 -1
- package/dist/analyzers/ruleRegistry.d.ts +38 -0
- package/dist/analyzers/ruleRegistry.d.ts.map +1 -0
- package/dist/analyzers/ruleRegistry.js +123 -0
- package/dist/analyzers/ruleRegistry.js.map +1 -0
- package/dist/analyzers/universal/UniversalConventionsAnalyzer.d.ts +58 -0
- package/dist/analyzers/universal/UniversalConventionsAnalyzer.d.ts.map +1 -0
- package/dist/analyzers/universal/UniversalConventionsAnalyzer.js +445 -0
- package/dist/analyzers/universal/UniversalConventionsAnalyzer.js.map +1 -0
- package/dist/analyzers/universal/UniversalDRYAnalyzer.d.ts +52 -0
- package/dist/analyzers/universal/UniversalDRYAnalyzer.d.ts.map +1 -1
- package/dist/analyzers/universal/UniversalDRYAnalyzer.js +108 -26
- package/dist/analyzers/universal/UniversalDRYAnalyzer.js.map +1 -1
- package/dist/analyzers/universal/UniversalDataAccessAnalyzer.d.ts +64 -0
- package/dist/analyzers/universal/UniversalDataAccessAnalyzer.d.ts.map +1 -1
- package/dist/analyzers/universal/UniversalDataAccessAnalyzer.js +259 -103
- package/dist/analyzers/universal/UniversalDataAccessAnalyzer.js.map +1 -1
- package/dist/analyzers/universal/UniversalDocumentationAnalyzer.d.ts.map +1 -1
- package/dist/analyzers/universal/UniversalDocumentationAnalyzer.js +5 -15
- package/dist/analyzers/universal/UniversalDocumentationAnalyzer.js.map +1 -1
- package/dist/analyzers/universal/UniversalSOLIDAnalyzer.d.ts.map +1 -1
- package/dist/analyzers/universal/UniversalSOLIDAnalyzer.js +10 -10
- package/dist/analyzers/universal/UniversalSOLIDAnalyzer.js.map +1 -1
- package/dist/analyzers/universal/UniversalSchemaAnalyzer.d.ts +25 -0
- package/dist/analyzers/universal/UniversalSchemaAnalyzer.d.ts.map +1 -1
- package/dist/analyzers/universal/UniversalSchemaAnalyzer.js +250 -59
- package/dist/analyzers/universal/UniversalSchemaAnalyzer.js.map +1 -1
- package/dist/analyzers/universal/UniversalStylesAnalyzer.d.ts +103 -0
- package/dist/analyzers/universal/UniversalStylesAnalyzer.d.ts.map +1 -0
- package/dist/analyzers/universal/UniversalStylesAnalyzer.js +750 -0
- package/dist/analyzers/universal/UniversalStylesAnalyzer.js.map +1 -0
- package/dist/auditRunner.d.ts.map +1 -1
- package/dist/auditRunner.js +483 -3
- package/dist/auditRunner.js.map +1 -1
- package/dist/baseline.d.ts +98 -0
- package/dist/baseline.d.ts.map +1 -0
- package/dist/baseline.js +184 -0
- package/dist/baseline.js.map +1 -0
- package/dist/churn/churnExtractor.d.ts +32 -0
- package/dist/churn/churnExtractor.d.ts.map +1 -0
- package/dist/churn/churnExtractor.js +261 -0
- package/dist/churn/churnExtractor.js.map +1 -0
- package/dist/cli.js +1215 -11
- package/dist/cli.js.map +1 -1
- package/dist/codeIndexDB-enhanced.js +1 -1
- package/dist/codeIndexDB-enhanced.js.map +1 -1
- package/dist/codeIndexDB.d.ts +101 -0
- package/dist/codeIndexDB.d.ts.map +1 -1
- package/dist/codeIndexDB.js +661 -5
- package/dist/codeIndexDB.js.map +1 -1
- package/dist/config/configLoader.d.ts.map +1 -1
- package/dist/config/configLoader.js +95 -1
- package/dist/config/configLoader.js.map +1 -1
- package/dist/config/defaults.d.ts +72 -1
- package/dist/config/defaults.d.ts.map +1 -1
- package/dist/config/defaults.js +146 -15
- package/dist/config/defaults.js.map +1 -1
- package/dist/config/pathProfiles.d.ts +38 -0
- package/dist/config/pathProfiles.d.ts.map +1 -0
- package/dist/config/pathProfiles.js +47 -0
- package/dist/config/pathProfiles.js.map +1 -0
- package/dist/conventions/conventionMiner.d.ts +63 -0
- package/dist/conventions/conventionMiner.d.ts.map +1 -0
- package/dist/conventions/conventionMiner.js +696 -0
- package/dist/conventions/conventionMiner.js.map +1 -0
- package/dist/coverage/coverageService.d.ts +49 -0
- package/dist/coverage/coverageService.d.ts.map +1 -0
- package/dist/coverage/coverageService.js +117 -0
- package/dist/coverage/coverageService.js.map +1 -0
- package/dist/coverage/istanbulParser.d.ts +84 -0
- package/dist/coverage/istanbulParser.d.ts.map +1 -0
- package/dist/coverage/istanbulParser.js +120 -0
- package/dist/coverage/istanbulParser.js.map +1 -0
- package/dist/coverage/lcovParser.d.ts +40 -0
- package/dist/coverage/lcovParser.d.ts.map +1 -0
- package/dist/coverage/lcovParser.js +168 -0
- package/dist/coverage/lcovParser.js.map +1 -0
- package/dist/fingerprint.d.ts +17 -0
- package/dist/fingerprint.d.ts.map +1 -1
- package/dist/fingerprint.js +42 -0
- package/dist/fingerprint.js.map +1 -1
- package/dist/grammars/tree-sitter-css.wasm +0 -0
- package/dist/graph/blastRadius.d.ts +27 -0
- package/dist/graph/blastRadius.d.ts.map +1 -0
- package/dist/graph/blastRadius.js +150 -0
- package/dist/graph/blastRadius.js.map +1 -0
- package/dist/graph/callGraph.d.ts +78 -0
- package/dist/graph/callGraph.d.ts.map +1 -0
- package/dist/graph/callGraph.js +484 -0
- package/dist/graph/callGraph.js.map +1 -0
- package/dist/graph/importGraph.d.ts +91 -0
- package/dist/graph/importGraph.d.ts.map +1 -0
- package/dist/graph/importGraph.js +602 -0
- package/dist/graph/importGraph.js.map +1 -0
- package/dist/graph/outputFormatter.d.ts +55 -0
- package/dist/graph/outputFormatter.d.ts.map +1 -0
- package/dist/graph/outputFormatter.js +283 -0
- package/dist/graph/outputFormatter.js.map +1 -0
- package/dist/hooks/codex.d.ts +36 -1
- package/dist/hooks/codex.d.ts.map +1 -1
- package/dist/hooks/codex.js +88 -54
- package/dist/hooks/codex.js.map +1 -1
- package/dist/hotspots/hotspotScorer.d.ts +20 -0
- package/dist/hotspots/hotspotScorer.d.ts.map +1 -0
- package/dist/hotspots/hotspotScorer.js +202 -0
- package/dist/hotspots/hotspotScorer.js.map +1 -0
- package/dist/invariants/invariant-rules.schema.json +42 -1
- package/dist/invariants/ruleEngine.d.ts +4 -1
- package/dist/invariants/ruleEngine.d.ts.map +1 -1
- package/dist/invariants/ruleEngine.js +141 -5
- package/dist/invariants/ruleEngine.js.map +1 -1
- package/dist/invariants/ruleValidator.d.ts.map +1 -1
- package/dist/invariants/ruleValidator.js +62 -1
- package/dist/invariants/ruleValidator.js.map +1 -1
- package/dist/invariants/types.d.ts +23 -2
- package/dist/invariants/types.d.ts.map +1 -1
- package/dist/languages/UniversalAnalyzer.d.ts +1 -1
- package/dist/languages/UniversalAnalyzer.d.ts.map +1 -1
- package/dist/languages/UniversalAnalyzer.js +59 -24
- package/dist/languages/UniversalAnalyzer.js.map +1 -1
- package/dist/languages/index.d.ts +1 -0
- package/dist/languages/index.d.ts.map +1 -1
- package/dist/languages/index.js +3 -0
- package/dist/languages/index.js.map +1 -1
- package/dist/languages/tree-sitter/TreeSitterCssAdapter.d.ts +59 -0
- package/dist/languages/tree-sitter/TreeSitterCssAdapter.d.ts.map +1 -0
- package/dist/languages/tree-sitter/TreeSitterCssAdapter.js +236 -0
- package/dist/languages/tree-sitter/TreeSitterCssAdapter.js.map +1 -0
- package/dist/languages/tree-sitter/parser.d.ts.map +1 -1
- package/dist/languages/tree-sitter/parser.js +2 -0
- package/dist/languages/tree-sitter/parser.js.map +1 -1
- package/dist/languages/typescript/TreeSitterTypeScriptAdapter.d.ts.map +1 -1
- package/dist/languages/typescript/TreeSitterTypeScriptAdapter.js +61 -10
- package/dist/languages/typescript/TreeSitterTypeScriptAdapter.js.map +1 -1
- package/dist/ledger.d.ts +128 -0
- package/dist/ledger.d.ts.map +1 -0
- package/dist/ledger.js +345 -0
- package/dist/ledger.js.map +1 -0
- package/dist/mcp-tools/projectTasks.d.ts.map +1 -1
- package/dist/mcp-tools/projectTasks.js +9 -14
- package/dist/mcp-tools/projectTasks.js.map +1 -1
- package/dist/mcp.js +0 -0
- package/dist/mcpAuditJobs.d.ts.map +1 -1
- package/dist/mcpAuditJobs.js +3 -0
- package/dist/mcpAuditJobs.js.map +1 -1
- package/dist/reporting/csvReportGenerator.d.ts.map +1 -1
- package/dist/reporting/csvReportGenerator.js +7 -1
- package/dist/reporting/csvReportGenerator.js.map +1 -1
- package/dist/reporting/htmlReportGenerator.d.ts.map +1 -1
- package/dist/reporting/htmlReportGenerator.js +5 -1
- package/dist/reporting/htmlReportGenerator.js.map +1 -1
- package/dist/reporting/jsonReportGenerator.d.ts.map +1 -1
- package/dist/reporting/jsonReportGenerator.js +10 -2
- package/dist/reporting/jsonReportGenerator.js.map +1 -1
- package/dist/reporting/sarifReportGenerator.d.ts.map +1 -1
- package/dist/reporting/sarifReportGenerator.js +17 -17
- package/dist/reporting/sarifReportGenerator.js.map +1 -1
- package/dist/scripts/runBench.d.ts +94 -0
- package/dist/scripts/runBench.d.ts.map +1 -0
- package/dist/scripts/runBench.js +1559 -0
- package/dist/scripts/runBench.js.map +1 -0
- package/dist/search/QueryParser.d.ts.map +1 -1
- package/dist/search/QueryParser.js +51 -4
- package/dist/search/QueryParser.js.map +1 -1
- package/dist/services/CodeMapGenerator.d.ts +33 -0
- package/dist/services/CodeMapGenerator.d.ts.map +1 -1
- package/dist/services/CodeMapGenerator.js +404 -0
- package/dist/services/CodeMapGenerator.js.map +1 -1
- package/dist/styles/normalizer.d.ts +37 -0
- package/dist/styles/normalizer.d.ts.map +1 -0
- package/dist/styles/normalizer.js +301 -0
- package/dist/styles/normalizer.js.map +1 -0
- package/dist/styles/styleExtractor.d.ts +38 -0
- package/dist/styles/styleExtractor.d.ts.map +1 -0
- package/dist/styles/styleExtractor.js +630 -0
- package/dist/styles/styleExtractor.js.map +1 -0
- package/dist/styles/styleIndexer.d.ts +35 -0
- package/dist/styles/styleIndexer.d.ts.map +1 -0
- package/dist/styles/styleIndexer.js +266 -0
- package/dist/styles/styleIndexer.js.map +1 -0
- package/dist/styles/tailwindConfigLoader.d.ts +38 -0
- package/dist/styles/tailwindConfigLoader.d.ts.map +1 -0
- package/dist/styles/tailwindConfigLoader.js +374 -0
- package/dist/styles/tailwindConfigLoader.js.map +1 -0
- package/dist/styles/tailwindExpander.d.ts +38 -0
- package/dist/styles/tailwindExpander.d.ts.map +1 -0
- package/dist/styles/tailwindExpander.js +637 -0
- package/dist/styles/tailwindExpander.js.map +1 -0
- package/dist/styles/types.d.ts +92 -0
- package/dist/styles/types.d.ts.map +1 -0
- package/dist/styles/types.js +10 -0
- package/dist/styles/types.js.map +1 -0
- package/dist/symbols.d.ts +21 -0
- package/dist/symbols.d.ts.map +1 -0
- package/dist/symbols.js +31 -0
- package/dist/symbols.js.map +1 -0
- package/dist/types.d.ts +326 -3
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/package.json +9 -5
- package/plugin/.claude-plugin/plugin.json +1 -1
- package/plugin/scripts/hook-audit.sh +5 -1
- package/plugin/skills/code-auditor/SKILL.md +51 -0
- package/dist/analyzers/__tests__/fixtures/spec-17/anonymous-callback-map.d.ts +0 -21
- package/dist/analyzers/__tests__/fixtures/spec-17/anonymous-callback-map.d.ts.map +0 -1
- package/dist/analyzers/__tests__/fixtures/spec-17/anonymous-callback-map.js +0 -21
- package/dist/analyzers/__tests__/fixtures/spec-17/anonymous-callback-map.js.map +0 -1
- package/dist/analyzers/__tests__/fixtures/spec-17/barrel-test-migration.d.ts +0 -13
- package/dist/analyzers/__tests__/fixtures/spec-17/barrel-test-migration.d.ts.map +0 -1
- package/dist/analyzers/__tests__/fixtures/spec-17/barrel-test-migration.js +0 -12
- package/dist/analyzers/__tests__/fixtures/spec-17/barrel-test-migration.js.map +0 -1
- package/dist/analyzers/__tests__/fixtures/spec-17/direct-access-allow-config.d.ts +0 -11
- package/dist/analyzers/__tests__/fixtures/spec-17/direct-access-allow-config.d.ts.map +0 -1
- package/dist/analyzers/__tests__/fixtures/spec-17/direct-access-allow-config.js +0 -22
- package/dist/analyzers/__tests__/fixtures/spec-17/direct-access-allow-config.js.map +0 -1
- package/dist/analyzers/__tests__/fixtures/spec-17/exported-undocumented.d.ts +0 -9
- package/dist/analyzers/__tests__/fixtures/spec-17/exported-undocumented.d.ts.map +0 -1
- package/dist/analyzers/__tests__/fixtures/spec-17/exported-undocumented.js +0 -13
- package/dist/analyzers/__tests__/fixtures/spec-17/exported-undocumented.js.map +0 -1
- package/dist/analyzers/__tests__/fixtures/spec-17/for-loop-query.d.ts +0 -13
- package/dist/analyzers/__tests__/fixtures/spec-17/for-loop-query.d.ts.map +0 -1
- package/dist/analyzers/__tests__/fixtures/spec-17/for-loop-query.js +0 -18
- package/dist/analyzers/__tests__/fixtures/spec-17/for-loop-query.js.map +0 -1
- package/dist/analyzers/__tests__/fixtures/spec-17/import-node-builtin.d.ts +0 -10
- package/dist/analyzers/__tests__/fixtures/spec-17/import-node-builtin.d.ts.map +0 -1
- package/dist/analyzers/__tests__/fixtures/spec-17/import-node-builtin.js +0 -16
- package/dist/analyzers/__tests__/fixtures/spec-17/import-node-builtin.js.map +0 -1
- package/dist/analyzers/__tests__/fixtures/spec-17/jsx-event-handler.d.ts +0 -14
- package/dist/analyzers/__tests__/fixtures/spec-17/jsx-event-handler.d.ts.map +0 -1
- package/dist/analyzers/__tests__/fixtures/spec-17/jsx-event-handler.js +0 -18
- package/dist/analyzers/__tests__/fixtures/spec-17/jsx-event-handler.js.map +0 -1
- package/dist/analyzers/__tests__/fixtures/spec-17/nested-loops-query.d.ts +0 -13
- package/dist/analyzers/__tests__/fixtures/spec-17/nested-loops-query.d.ts.map +0 -1
- package/dist/analyzers/__tests__/fixtures/spec-17/nested-loops-query.js +0 -22
- package/dist/analyzers/__tests__/fixtures/spec-17/nested-loops-query.js.map +0 -1
- package/dist/analyzers/__tests__/fixtures/spec-17/nine-line-repeated.d.ts +0 -10
- package/dist/analyzers/__tests__/fixtures/spec-17/nine-line-repeated.d.ts.map +0 -1
- package/dist/analyzers/__tests__/fixtures/spec-17/nine-line-repeated.js +0 -20
- package/dist/analyzers/__tests__/fixtures/spec-17/nine-line-repeated.js.map +0 -1
- package/dist/analyzers/__tests__/fixtures/spec-17/private-methods.d.ts +0 -18
- package/dist/analyzers/__tests__/fixtures/spec-17/private-methods.d.ts.map +0 -1
- package/dist/analyzers/__tests__/fixtures/spec-17/private-methods.js +0 -28
- package/dist/analyzers/__tests__/fixtures/spec-17/private-methods.js.map +0 -1
- package/dist/analyzers/__tests__/fixtures/spec-17/scope-all-config.d.ts +0 -11
- package/dist/analyzers/__tests__/fixtures/spec-17/scope-all-config.d.ts.map +0 -1
- package/dist/analyzers/__tests__/fixtures/spec-17/scope-all-config.js +0 -31
- package/dist/analyzers/__tests__/fixtures/spec-17/scope-all-config.js.map +0 -1
- package/dist/analyzers/__tests__/fixtures/spec-17/sql-tagged-template.d.ts +0 -11
- package/dist/analyzers/__tests__/fixtures/spec-17/sql-tagged-template.d.ts.map +0 -1
- package/dist/analyzers/__tests__/fixtures/spec-17/sql-tagged-template.js +0 -18
- package/dist/analyzers/__tests__/fixtures/spec-17/sql-tagged-template.js.map +0 -1
- package/dist/analyzers/__tests__/fixtures/spec-17/structural-similar-methods.d.ts +0 -13
- package/dist/analyzers/__tests__/fixtures/spec-17/structural-similar-methods.d.ts.map +0 -1
- package/dist/analyzers/__tests__/fixtures/spec-17/structural-similar-methods.js +0 -61
- package/dist/analyzers/__tests__/fixtures/spec-17/structural-similar-methods.js.map +0 -1
- package/dist/analyzers/__tests__/fixtures/spec-17/template-prefix-table.d.ts +0 -10
- package/dist/analyzers/__tests__/fixtures/spec-17/template-prefix-table.d.ts.map +0 -1
- package/dist/analyzers/__tests__/fixtures/spec-17/template-prefix-table.js +0 -15
- package/dist/analyzers/__tests__/fixtures/spec-17/template-prefix-table.js.map +0 -1
- package/dist/analyzers/__tests__/fixtures/spec-17/token-identical-20-lines.d.ts +0 -11
- package/dist/analyzers/__tests__/fixtures/spec-17/token-identical-20-lines.d.ts.map +0 -1
- package/dist/analyzers/__tests__/fixtures/spec-17/token-identical-20-lines.js +0 -49
- package/dist/analyzers/__tests__/fixtures/spec-17/token-identical-20-lines.js.map +0 -1
- package/dist/analyzers/__tests__/fixtures/spec-17/tsx-no-db-usage.d.ts +0 -10
- package/dist/analyzers/__tests__/fixtures/spec-17/tsx-no-db-usage.d.ts.map +0 -1
- package/dist/analyzers/__tests__/fixtures/spec-17/tsx-no-db-usage.js +0 -19
- package/dist/analyzers/__tests__/fixtures/spec-17/tsx-no-db-usage.js.map +0 -1
- package/dist/analyzers/__tests__/fixtures/spec-17/twenty-methods-vs-complex.d.ts +0 -37
- package/dist/analyzers/__tests__/fixtures/spec-17/twenty-methods-vs-complex.d.ts.map +0 -1
- package/dist/analyzers/__tests__/fixtures/spec-17/twenty-methods-vs-complex.js +0 -73
- package/dist/analyzers/__tests__/fixtures/spec-17/twenty-methods-vs-complex.js.map +0 -1
- package/dist/analyzers/__tests__/fixtures/spec-17/unique-method-74-lines.d.ts +0 -12
- package/dist/analyzers/__tests__/fixtures/spec-17/unique-method-74-lines.d.ts.map +0 -1
- package/dist/analyzers/__tests__/fixtures/spec-17/unique-method-74-lines.js +0 -82
- package/dist/analyzers/__tests__/fixtures/spec-17/unique-method-74-lines.js.map +0 -1
- package/dist/analyzers/__tests__/fixtures/spec-17/word-the-in-comment.d.ts +0 -10
- package/dist/analyzers/__tests__/fixtures/spec-17/word-the-in-comment.d.ts.map +0 -1
- package/dist/analyzers/__tests__/fixtures/spec-17/word-the-in-comment.js +0 -15
- package/dist/analyzers/__tests__/fixtures/spec-17/word-the-in-comment.js.map +0 -1
- package/dist/analyzers/dataAccessAnalyzer.d.ts +0 -46
- package/dist/analyzers/dataAccessAnalyzer.d.ts.map +0 -1
- package/dist/analyzers/dataAccessAnalyzer.js +0 -474
- package/dist/analyzers/dataAccessAnalyzer.js.map +0 -1
- package/dist/analyzers/dryAnalyzer.d.ts +0 -31
- package/dist/analyzers/dryAnalyzer.d.ts.map +0 -1
- package/dist/analyzers/dryAnalyzer.js +0 -734
- package/dist/analyzers/dryAnalyzer.js.map +0 -1
- package/dist/analyzers/schemaAnalyzer.d.ts +0 -23
- package/dist/analyzers/schemaAnalyzer.d.ts.map +0 -1
- package/dist/analyzers/schemaAnalyzer.js +0 -564
- package/dist/analyzers/schemaAnalyzer.js.map +0 -1
- package/dist/analyzers/solidAnalyzer.d.ts +0 -33
- package/dist/analyzers/solidAnalyzer.d.ts.map +0 -1
- package/dist/analyzers/solidAnalyzer.js +0 -847
- package/dist/analyzers/solidAnalyzer.js.map +0 -1
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Analyzes database access patterns and data layer interactions
|
|
5
5
|
*/
|
|
6
6
|
import { UniversalAnalyzer } from '../../languages/UniversalAnalyzer.js';
|
|
7
|
-
import {
|
|
7
|
+
import { buildProvenanceContext, isDBProvenanced, DB_CALL_METHODS, } from '../provenance.js';
|
|
8
8
|
export const DEFAULT_DATA_ACCESS_CONFIG = {
|
|
9
9
|
checkOrgFilters: true,
|
|
10
10
|
checkSQLInjection: true,
|
|
@@ -28,18 +28,22 @@ export const DEFAULT_DATA_ACCESS_CONFIG = {
|
|
|
28
28
|
'companyId',
|
|
29
29
|
'company_id'
|
|
30
30
|
],
|
|
31
|
+
// Spec 21 R6.2: three-tier org-filter detection
|
|
32
|
+
orgFilterTables: [], // Tier 1: empty — user must declare
|
|
33
|
+
orgFilterColumns: ['org_id', 'tenant_id', 'organization_id', 'workspace_id'], // Tier 2
|
|
34
|
+
schemas: [], // Tier 2: schema definitions for column-based inference
|
|
31
35
|
tablePatterns: {
|
|
32
36
|
orm: [
|
|
33
|
-
/from\s*\(\s*["'`]?(\
|
|
34
|
-
/table\s*[:=]\s*["'`]?(\
|
|
35
|
-
/\.from\s*\(\s*(\
|
|
36
|
-
/join\s*\(\s*(\
|
|
37
|
-
/leftJoin\s*\(\s*(\
|
|
38
|
-
/rightJoin\s*\(\s*(\
|
|
39
|
-
/innerJoin\s*\(\s*(\
|
|
37
|
+
/from\s*\(\s*["'`]?([\p{L}\p{N}_]+)["'`]?\s*\)/giu,
|
|
38
|
+
/table\s*[:=]\s*["'`]?([\p{L}\p{N}_]+)["'`]?/giu,
|
|
39
|
+
/\.from\s*\(\s*([\p{L}\p{N}_]+)\s*\)/giu, // Handle .from(users) where users is a variable
|
|
40
|
+
/join\s*\(\s*([\p{L}\p{N}_]+)\s*,/giu, // Handle joins
|
|
41
|
+
/leftJoin\s*\(\s*([\p{L}\p{N}_]+)\s*,/giu,
|
|
42
|
+
/rightJoin\s*\(\s*([\p{L}\p{N}_]+)\s*,/giu,
|
|
43
|
+
/innerJoin\s*\(\s*([\p{L}\p{N}_]+)\s*,/giu
|
|
40
44
|
],
|
|
41
|
-
sql: [/FROM\s+["'`]?(\
|
|
42
|
-
queryBuilder: [/\.from\s*\(\s*["'`]?(\
|
|
45
|
+
sql: [/FROM\s+["'`]?([\p{L}\p{N}_]+)["'`]?/giu, /JOIN\s+["'`]?([\p{L}\p{N}_]+)["'`]?/giu, /UPDATE\s+["'`]?([\p{L}\p{N}_]+)["'`]?/giu],
|
|
46
|
+
queryBuilder: [/\.from\s*\(\s*["'`]?([\p{L}\p{N}_]+)["'`]?\s*\)/giu]
|
|
43
47
|
},
|
|
44
48
|
performanceThresholds: {
|
|
45
49
|
complexQueryCount: 3,
|
|
@@ -58,26 +62,33 @@ export class UniversalDataAccessAnalyzer extends UniversalAnalyzer {
|
|
|
58
62
|
async analyzeAST(ast, adapter, config, sourceCode) {
|
|
59
63
|
const violations = [];
|
|
60
64
|
const finalConfig = { ...DEFAULT_DATA_ACCESS_CONFIG, ...config };
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
65
|
+
// Spec 21: Build provenance context for this file (R1 — provenance-primary detection)
|
|
66
|
+
const detectionMode = config.detection?.mode ?? 'hybrid';
|
|
67
|
+
const schemaConfig = config;
|
|
68
|
+
const p0 = performance.now();
|
|
69
|
+
const provenanceContext = buildProvenanceContext(ast, adapter, sourceCode, {
|
|
70
|
+
mode: detectionMode,
|
|
71
|
+
dbReceiverNames: schemaConfig.dbReceiverNames ?? ['db', 'database', 'sql', 'stmt', 'connection', 'pool', 'client'],
|
|
72
|
+
dbBindingNames: schemaConfig.dbBindingNames ?? ['env.DB'],
|
|
73
|
+
dbCallMethods: schemaConfig.dbCallMethods ?? [...DB_CALL_METHODS],
|
|
74
|
+
});
|
|
75
|
+
const timingAcc = schemaConfig._provenanceTiming;
|
|
76
|
+
if (timingAcc)
|
|
77
|
+
timingAcc.totalMs += performance.now() - p0;
|
|
68
78
|
// Check imports for database libraries
|
|
69
79
|
const imports = adapter.extractImports(ast);
|
|
70
80
|
const dbImports = this.mapDatabaseImports(imports, finalConfig);
|
|
71
|
-
// Find database calls
|
|
72
|
-
const calls = this.extractDatabaseCalls(ast, adapter, sourceCode, dbImports, finalConfig);
|
|
73
|
-
// Analyze each call
|
|
81
|
+
// Find database calls (Spec 21: uses provenance context)
|
|
82
|
+
const calls = this.extractDatabaseCalls(ast, adapter, sourceCode, dbImports, finalConfig, provenanceContext);
|
|
83
|
+
// Analyze each call — track symbol ordinals for fingerprint stability
|
|
84
|
+
const symbolOrdinals = new Map();
|
|
74
85
|
for (const call of calls) {
|
|
75
86
|
const analysis = this.analyzeQuery(call, sourceCode, finalConfig);
|
|
76
87
|
// Check for violations
|
|
77
|
-
violations.push(...this.checkViolations(call, analysis, ast.filePath, finalConfig));
|
|
88
|
+
violations.push(...this.checkViolations(call, analysis, ast.filePath, finalConfig, symbolOrdinals));
|
|
78
89
|
}
|
|
79
|
-
// R4.1: Check for database queries inside loops (N+1 detection)
|
|
80
|
-
violations.push(...this.checkLoopQueries(ast, adapter, sourceCode, finalConfig));
|
|
90
|
+
// R4.1: Check for database queries inside loops (N+1 detection, Spec 21: provenance-gated)
|
|
91
|
+
violations.push(...this.checkLoopQueries(ast, adapter, sourceCode, finalConfig, provenanceContext));
|
|
81
92
|
// Check for general data access patterns
|
|
82
93
|
violations.push(...this.checkGeneralPatterns(ast, adapter, sourceCode, finalConfig));
|
|
83
94
|
return violations;
|
|
@@ -99,26 +110,28 @@ export class UniversalDataAccessAnalyzer extends UniversalAnalyzer {
|
|
|
99
110
|
/**
|
|
100
111
|
* Extract database calls from AST
|
|
101
112
|
*/
|
|
102
|
-
extractDatabaseCalls(ast, adapter, sourceCode, dbImports, config) {
|
|
113
|
+
extractDatabaseCalls(ast, adapter, sourceCode, dbImports, config, provenanceContext) {
|
|
103
114
|
const calls = [];
|
|
104
115
|
// Find all relevant nodes
|
|
105
116
|
const allNodes = adapter.findNodes(ast, {
|
|
106
117
|
custom: (node) => {
|
|
107
118
|
const nodeText = adapter.getNodeText(node, sourceCode);
|
|
108
|
-
// Check if it's a function call
|
|
119
|
+
// Check if it's a function call whose callee is DB-related
|
|
109
120
|
if (this.isFunctionCall(node, adapter)) {
|
|
110
|
-
//
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
121
|
+
// Spec 21: Use provenance when available, fall back to name-based check
|
|
122
|
+
if (provenanceContext) {
|
|
123
|
+
if (isDBProvenanced(node, adapter, sourceCode, provenanceContext, DB_CALL_METHODS)) {
|
|
124
|
+
return true;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
else if (this.isDBCallee(node, adapter, sourceCode)) {
|
|
116
128
|
return true;
|
|
117
129
|
}
|
|
118
130
|
}
|
|
119
|
-
// Check if it's a template literal with SQL
|
|
131
|
+
// Check if it's a template literal with SQL in a DB-call context
|
|
120
132
|
if (this.isTemplateLiteral(node, adapter)) {
|
|
121
|
-
return this.containsSQLKeywords(nodeText)
|
|
133
|
+
return this.containsSQLKeywords(nodeText) &&
|
|
134
|
+
this.isTemplateInDBCallOrVariableContext(node, adapter, sourceCode, provenanceContext);
|
|
122
135
|
}
|
|
123
136
|
// Check if it's a variable declaration with SQL (but not if it contains a template literal)
|
|
124
137
|
if (this.isVariableAssignment(node, adapter)) {
|
|
@@ -171,24 +184,7 @@ export class UniversalDataAccessAnalyzer extends UniversalAnalyzer {
|
|
|
171
184
|
if (isSqlQuery || isOrmCall) {
|
|
172
185
|
const tables = this.extractTables(nodeText, config);
|
|
173
186
|
const hasOrgFilter = this.hasOrganizationFilter(nodeText, config);
|
|
174
|
-
// Debug: Log the analysis
|
|
175
|
-
if (IS_DEV_MODE) {
|
|
176
|
-
console.error('[DEBUG] Database call analysis:', {
|
|
177
|
-
line: node.location.start.line,
|
|
178
|
-
nodeText: nodeText.substring(0, 100),
|
|
179
|
-
tables,
|
|
180
|
-
hasOrgFilter,
|
|
181
|
-
patterns: config.organizationPatterns || []
|
|
182
|
-
});
|
|
183
|
-
}
|
|
184
187
|
const security = this.checkQuerySecurity(nodeText, config);
|
|
185
|
-
// Debug: Log extracted tables
|
|
186
|
-
if (tables.length === 0 && isOrmCall) {
|
|
187
|
-
console.error('[DataAccess Debug] No tables extracted from ORM call:', {
|
|
188
|
-
line: node.location.start.line,
|
|
189
|
-
nodeText: nodeText.substring(0, 100)
|
|
190
|
-
});
|
|
191
|
-
}
|
|
192
188
|
// Determine the type based on imports or patterns
|
|
193
189
|
let callType = 'unknown';
|
|
194
190
|
if (isSqlQuery) {
|
|
@@ -212,7 +208,8 @@ export class UniversalDataAccessAnalyzer extends UniversalAnalyzer {
|
|
|
212
208
|
tables,
|
|
213
209
|
hasOrganizationFilter: hasOrgFilter,
|
|
214
210
|
hasParameterizedQuery: security.parameterized,
|
|
215
|
-
hasSqlInjectionRisk: security.injectionRisk
|
|
211
|
+
hasSqlInjectionRisk: security.injectionRisk,
|
|
212
|
+
enclosingFunction: this.findEnclosingFunctionName(node, adapter),
|
|
216
213
|
});
|
|
217
214
|
}
|
|
218
215
|
}
|
|
@@ -251,8 +248,15 @@ export class UniversalDataAccessAnalyzer extends UniversalAnalyzer {
|
|
|
251
248
|
/**
|
|
252
249
|
* Check for violations in a database call
|
|
253
250
|
*/
|
|
254
|
-
checkViolations(call, analysis, filePath, config) {
|
|
251
|
+
checkViolations(call, analysis, filePath, config, symbolOrdinals) {
|
|
255
252
|
const violations = [];
|
|
253
|
+
// Build a stable base symbol key — enclosing function + callee method.
|
|
254
|
+
// Ordinal added for genuine repeats within the same function.
|
|
255
|
+
const fnName = call.enclosingFunction ?? 'top-level';
|
|
256
|
+
const baseSymbol = `${fnName}:${call.method}`;
|
|
257
|
+
const ordinal = (symbolOrdinals.get(baseSymbol) ?? 0) + 1;
|
|
258
|
+
symbolOrdinals.set(baseSymbol, ordinal);
|
|
259
|
+
const symbol = ordinal > 1 ? `${baseSymbol}:${ordinal}` : baseSymbol;
|
|
256
260
|
// Security: SQL Injection Risk
|
|
257
261
|
// Spec 11 (calibration): Detection uses AST-level heuristics (string
|
|
258
262
|
// concatenation in query construction) without type information. Findings
|
|
@@ -261,19 +265,19 @@ export class UniversalDataAccessAnalyzer extends UniversalAnalyzer {
|
|
|
261
265
|
// rate on ExcAlDraw and Gin corpora to decide whether heuristics should be
|
|
262
266
|
// tightened or severity re-escalated.
|
|
263
267
|
if (config.checkSQLInjection && call.hasSqlInjectionRisk) {
|
|
264
|
-
violations.push(this.createViolation(filePath, { line: call.line, column: call.column }, `Potential SQL injection risk in ${call.method}. Use parameterized queries.`, '
|
|
268
|
+
violations.push(this.createViolation(filePath, { line: call.line, column: call.column }, `Potential SQL injection risk in ${call.method}. Use parameterized queries.`, 'suggestion', 'sql-injection-risk', undefined, symbol));
|
|
265
269
|
}
|
|
266
270
|
// Security: Missing Organization Filter
|
|
267
|
-
if (config.checkOrgFilters && !call.hasOrganizationFilter && call.tables.length > 0 && this.requiresOrgFilter(call.tables)) {
|
|
268
|
-
violations.push(this.createViolation(filePath, { line: call.line, column: call.column }, `Query on ${call.tables.join(', ')} missing organization/tenant filter`, 'warning', 'missing-org-filter'));
|
|
271
|
+
if (config.checkOrgFilters && !call.hasOrganizationFilter && call.tables.length > 0 && this.requiresOrgFilter(call.tables, config)) {
|
|
272
|
+
violations.push(this.createViolation(filePath, { line: call.line, column: call.column }, `Query on ${call.tables.join(', ')} missing organization/tenant filter`, 'warning', 'missing-org-filter', undefined, symbol));
|
|
269
273
|
}
|
|
270
274
|
// Performance: Complex Query
|
|
271
275
|
if (analysis.performanceRisk === 'high') {
|
|
272
|
-
violations.push(this.createViolation(filePath, { line: call.line, column: call.column }, `Complex query with ${call.tables.length} tables may have performance issues`, 'warning', 'complex-query'));
|
|
276
|
+
violations.push(this.createViolation(filePath, { line: call.line, column: call.column }, `Complex query with ${call.tables.length} tables may have performance issues`, 'warning', 'complex-query', undefined, symbol));
|
|
273
277
|
}
|
|
274
278
|
// Performance: Unfiltered Query
|
|
275
279
|
if (!call.hasOrganizationFilter && analysis.performanceRisk === 'medium') {
|
|
276
|
-
violations.push(this.createViolation(filePath, { line: call.line, column: call.column }, `Unfiltered query on ${call.tables.join(', ')} may cause performance issues`, 'suggestion', 'unfiltered-query'));
|
|
280
|
+
violations.push(this.createViolation(filePath, { line: call.line, column: call.column }, `Unfiltered query on ${call.tables.join(', ')} may cause performance issues`, 'suggestion', 'unfiltered-query', undefined, symbol));
|
|
277
281
|
}
|
|
278
282
|
return violations;
|
|
279
283
|
}
|
|
@@ -290,11 +294,17 @@ export class UniversalDataAccessAnalyzer extends UniversalAnalyzer {
|
|
|
290
294
|
const stringNodes = adapter.findNodes(ast, {
|
|
291
295
|
custom: (node) => this.isStringLiteral(node, adapter)
|
|
292
296
|
});
|
|
297
|
+
const hardcodedOrdinals = new Map();
|
|
293
298
|
for (const node of stringNodes) {
|
|
294
299
|
const text = adapter.getNodeText(node, sourceCode);
|
|
295
300
|
if (this.isConnectionString(text)) {
|
|
301
|
+
const fnName = this.findEnclosingFunctionName(node, adapter);
|
|
302
|
+
const baseSym = `${fnName}:hardcoded-connection`;
|
|
303
|
+
const count = (hardcodedOrdinals.get(baseSym) ?? 0) + 1;
|
|
304
|
+
hardcodedOrdinals.set(baseSym, count);
|
|
305
|
+
const sym = count > 1 ? `${baseSym}:${count}` : baseSym;
|
|
296
306
|
violations.push(this.createViolation(ast.filePath, node.location.start, 'Hardcoded database connection string detected. Use environment variables. (On Cloudflare Workers/D1, connection strings are injected via bindings.)', 'suggestion', // R7: direct-access → suggestion
|
|
297
|
-
'hardcoded-connection'));
|
|
307
|
+
'hardcoded-connection', undefined, sym));
|
|
298
308
|
}
|
|
299
309
|
}
|
|
300
310
|
// Check for direct SQL execution without ORM
|
|
@@ -302,7 +312,7 @@ export class UniversalDataAccessAnalyzer extends UniversalAnalyzer {
|
|
|
302
312
|
for (const pattern of sqlPatterns) {
|
|
303
313
|
if (pattern.test(sourceCode)) {
|
|
304
314
|
violations.push(this.createViolation(ast.filePath, { line: 1, column: 1 }, 'Direct SQL execution detected. Consider using an ORM or query builder.', 'suggestion', // R7: direct-access → suggestion
|
|
305
|
-
'direct-sql'));
|
|
315
|
+
'direct-sql', undefined, 'direct-sql'));
|
|
306
316
|
break;
|
|
307
317
|
}
|
|
308
318
|
}
|
|
@@ -328,6 +338,59 @@ export class UniversalDataAccessAnalyzer extends UniversalAnalyzer {
|
|
|
328
338
|
const upperText = text.toUpperCase();
|
|
329
339
|
return sqlKeywords.some(keyword => upperText.includes(keyword));
|
|
330
340
|
}
|
|
341
|
+
/**
|
|
342
|
+
* R3.1: Check if a call expression's callee (excluding arguments)
|
|
343
|
+
* matches database-related patterns. Prevents false positives from
|
|
344
|
+
* non-DB calls like page.evaluate() whose arguments happen to contain
|
|
345
|
+
* SQL-like substrings.
|
|
346
|
+
*/
|
|
347
|
+
isDBCallee(node, adapter, sourceCode) {
|
|
348
|
+
const children = adapter.getChildren(node);
|
|
349
|
+
const calleeParts = [];
|
|
350
|
+
for (const child of children) {
|
|
351
|
+
if (adapter.getNodeType(child) === 'arguments')
|
|
352
|
+
break;
|
|
353
|
+
calleeParts.push(adapter.getNodeText(child, sourceCode));
|
|
354
|
+
}
|
|
355
|
+
const calleeText = calleeParts.join('');
|
|
356
|
+
const dbPatterns = ['select', 'insert', 'update', 'delete', 'from', 'where', 'execute', 'query', 'find', 'aggregate', 'count', 'distinct'];
|
|
357
|
+
return dbPatterns.some(pattern => calleeText.toLowerCase().includes(pattern));
|
|
358
|
+
}
|
|
359
|
+
/**
|
|
360
|
+
* R3.1: Gate template literal SQL detection by parent context.
|
|
361
|
+
* Template literals in non-DB sinks (console.log, page.evaluate) must not
|
|
362
|
+
* trigger sql-injection-risk. Conservative: variable assignments and bare
|
|
363
|
+
* statements pass the gate (they may flow to a DB call).
|
|
364
|
+
*
|
|
365
|
+
* Spec 21: When provenance context is available, uses provenance-based callee
|
|
366
|
+
* check (conjunctive guard). Falls back to isDBCallee() when no context.
|
|
367
|
+
*/
|
|
368
|
+
isTemplateInDBCallOrVariableContext(node, adapter, sourceCode, provenanceContext) {
|
|
369
|
+
const parent = adapter.getParent(node);
|
|
370
|
+
if (!parent)
|
|
371
|
+
return false;
|
|
372
|
+
const parentType = adapter.getNodeType(parent);
|
|
373
|
+
// Inside arguments of a call expression → check callee is DB-related
|
|
374
|
+
if (parentType === 'arguments') {
|
|
375
|
+
const callExpr = adapter.getParent(parent);
|
|
376
|
+
if (!callExpr || adapter.getNodeType(callExpr) !== 'call_expression')
|
|
377
|
+
return false;
|
|
378
|
+
// Spec 21: Use provenance when available
|
|
379
|
+
if (provenanceContext) {
|
|
380
|
+
return isDBProvenanced(callExpr, adapter, sourceCode, provenanceContext, DB_CALL_METHODS);
|
|
381
|
+
}
|
|
382
|
+
return this.isDBCallee(callExpr, adapter, sourceCode);
|
|
383
|
+
}
|
|
384
|
+
// Variable assignment → conservative (may eventually reach a DB call)
|
|
385
|
+
if (parentType === 'binary_expression' || parentType === 'variable_declarator' || parentType === 'variable_declaration') {
|
|
386
|
+
return true;
|
|
387
|
+
}
|
|
388
|
+
// Statement-level or program-level → conservative
|
|
389
|
+
if (parentType === 'expression_statement' || parentType === 'program') {
|
|
390
|
+
return true;
|
|
391
|
+
}
|
|
392
|
+
return false;
|
|
393
|
+
}
|
|
331
394
|
isOrmPattern(text) {
|
|
332
395
|
// Common ORM method patterns
|
|
333
396
|
const ormPatterns = [
|
|
@@ -381,7 +444,7 @@ export class UniversalDataAccessAnalyzer extends UniversalAnalyzer {
|
|
|
381
444
|
});
|
|
382
445
|
// Additional check for common ORM patterns that might be missed
|
|
383
446
|
// Handle patterns like db.select().from(users) where 'users' is a variable
|
|
384
|
-
const ormVariablePattern = /\.from\s*\(\s*([
|
|
447
|
+
const ormVariablePattern = /\.from\s*\(\s*([\p{L}_][\p{L}\p{N}_]*)\s*\)/gu;
|
|
385
448
|
const ormMatches = text.matchAll(ormVariablePattern);
|
|
386
449
|
for (const match of ormMatches) {
|
|
387
450
|
if (match[1] && !match[1].includes('"') && !match[1].includes("'")) {
|
|
@@ -389,7 +452,7 @@ export class UniversalDataAccessAnalyzer extends UniversalAnalyzer {
|
|
|
389
452
|
}
|
|
390
453
|
}
|
|
391
454
|
// Handle patterns like db.users.find() or db.orders.findOne()
|
|
392
|
-
const dbTablePattern = /db\.([
|
|
455
|
+
const dbTablePattern = /db\.([\p{L}_][\p{L}\p{N}_]*)\.\p{L}[\p{L}\p{N}_]*\s*\(/gu;
|
|
393
456
|
const dbMatches = text.matchAll(dbTablePattern);
|
|
394
457
|
for (const match of dbMatches) {
|
|
395
458
|
if (match[1]) {
|
|
@@ -401,33 +464,17 @@ export class UniversalDataAccessAnalyzer extends UniversalAnalyzer {
|
|
|
401
464
|
hasOrganizationFilter(text, config) {
|
|
402
465
|
const patterns = config.organizationPatterns || [];
|
|
403
466
|
const lowerText = text.toLowerCase();
|
|
404
|
-
if (IS_DEV_MODE) {
|
|
405
|
-
console.error('[DEBUG] hasOrganizationFilter called with:', {
|
|
406
|
-
text: text.substring(0, 100),
|
|
407
|
-
patternsFromConfig: patterns,
|
|
408
|
-
patternsLength: patterns.length
|
|
409
|
-
});
|
|
410
|
-
}
|
|
411
467
|
// If no patterns provided, check for hardcoded common patterns as fallback
|
|
412
468
|
const fallbackPatterns = patterns.length === 0 ? [
|
|
413
469
|
'organizationid', 'organization_id', 'orgid', 'org_id',
|
|
414
470
|
'tenantid', 'tenant_id', 'companyid', 'company_id'
|
|
415
471
|
] : patterns;
|
|
416
|
-
if (IS_DEV_MODE) {
|
|
417
|
-
console.error('[DEBUG] Using fallback patterns:', fallbackPatterns);
|
|
418
|
-
}
|
|
419
472
|
// Check for simple pattern matches first
|
|
420
473
|
const hasSimpleMatch = fallbackPatterns.some(pattern => {
|
|
421
474
|
const match = lowerText.includes(pattern.toLowerCase());
|
|
422
|
-
if (IS_DEV_MODE) {
|
|
423
|
-
console.error(`[DEBUG] Pattern "${pattern}" -> "${pattern.toLowerCase()}" in "${lowerText}": ${match}`);
|
|
424
|
-
}
|
|
425
475
|
return match;
|
|
426
476
|
});
|
|
427
477
|
if (hasSimpleMatch) {
|
|
428
|
-
if (IS_DEV_MODE) {
|
|
429
|
-
console.error('[DEBUG] Found simple match, returning true');
|
|
430
|
-
}
|
|
431
478
|
return true;
|
|
432
479
|
}
|
|
433
480
|
// Enhanced pattern matching for object properties and SQL WHERE clauses
|
|
@@ -456,15 +503,6 @@ export class UniversalDataAccessAnalyzer extends UniversalAnalyzer {
|
|
|
456
503
|
return true;
|
|
457
504
|
}
|
|
458
505
|
}
|
|
459
|
-
// Debug logging
|
|
460
|
-
if (IS_DEV_MODE) {
|
|
461
|
-
console.error('[DEBUG] hasOrganizationFilter check:', {
|
|
462
|
-
text: text.substring(0, 150),
|
|
463
|
-
patterns,
|
|
464
|
-
fallbackPatterns,
|
|
465
|
-
hasFilter: false
|
|
466
|
-
});
|
|
467
|
-
}
|
|
468
506
|
return false;
|
|
469
507
|
}
|
|
470
508
|
checkQuerySecurity(text, config) {
|
|
@@ -482,23 +520,60 @@ export class UniversalDataAccessAnalyzer extends UniversalAnalyzer {
|
|
|
482
520
|
];
|
|
483
521
|
const configPatterns = config.securityPatterns?.sqlInjectionRisks || [];
|
|
484
522
|
const allPatterns = [...sqlInjectionPatterns, ...configPatterns];
|
|
485
|
-
const injectionRisk =
|
|
523
|
+
const injectionRisk = !parameterized &&
|
|
524
|
+
allPatterns.some(pattern => text.includes(pattern)) &&
|
|
486
525
|
this.containsSQLKeywords(text);
|
|
487
526
|
return { parameterized, injectionRisk };
|
|
488
527
|
}
|
|
489
528
|
extractMethodName(node, adapter, sourceCode) {
|
|
490
529
|
const text = adapter.getNodeText(node, sourceCode);
|
|
491
|
-
const match = text.match(/(\
|
|
530
|
+
const match = text.match(/([\p{L}\p{N}_]+)\s*\(/u);
|
|
492
531
|
return match ? match[1] : 'unknown';
|
|
493
532
|
}
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
533
|
+
/**
|
|
534
|
+
* Spec 21 R6.2: Three-tier org-filter detection.
|
|
535
|
+
*
|
|
536
|
+
* Tier 1 (config-primary): `orgFilterTables` — the user's explicit declaration
|
|
537
|
+
* of which tables are multi-tenant. Tenancy is policy; this is the
|
|
538
|
+
* declaration of record.
|
|
539
|
+
*
|
|
540
|
+
* Tier 2 (usage-inference secondary): A table requires the filter if the
|
|
541
|
+
* project's own corpus shows it scoped — i.e., a column matching
|
|
542
|
+
* `orgFilterColumns` (default: org_id/tenant_id/organization_id/workspace_id)
|
|
543
|
+
* exists on it in the schema definitions in config.
|
|
544
|
+
* This is what makes non-English table names (e.g., 注文) detectable
|
|
545
|
+
* with zero explicit orgFilterTables declaration.
|
|
546
|
+
*
|
|
547
|
+
* Tier 3 (fallback): English table list retained as defaults, evidence-tagged
|
|
548
|
+
* `fallback` like every other name list in Spec 21.
|
|
549
|
+
*/
|
|
550
|
+
requiresOrgFilter(tables, config) {
|
|
551
|
+
const orgFilterTables = config.orgFilterTables ?? [];
|
|
552
|
+
const orgFilterColumns = config.orgFilterColumns ?? ['org_id', 'tenant_id', 'organization_id', 'workspace_id'];
|
|
553
|
+
const schemas = config.schemas ?? [];
|
|
554
|
+
const fallbackOrgTables = ['users', 'projects', 'orders', 'customers', 'accounts', 'teams'];
|
|
555
|
+
// Build a lookup set of tables from schema definitions that have an
|
|
556
|
+
// org-filter column — this is the usage-inference tier.
|
|
557
|
+
const schemaOrgTables = new Set();
|
|
558
|
+
for (const schema of schemas) {
|
|
559
|
+
for (const table of schema.tables) {
|
|
560
|
+
if (table.columns.some(c => orgFilterColumns.includes(c.name.toLowerCase()))) {
|
|
561
|
+
schemaOrgTables.add(table.name.toLowerCase());
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
}
|
|
497
565
|
return tables.some(table => {
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
566
|
+
const tableLower = table.toLowerCase();
|
|
567
|
+
// Tier 1: config-primary — explicit user declaration
|
|
568
|
+
if (orgFilterTables.some(t => t.toLowerCase() === tableLower)) {
|
|
569
|
+
return true;
|
|
570
|
+
}
|
|
571
|
+
// Tier 2: schema-based inference — table has an org-filter column
|
|
572
|
+
if (schemaOrgTables.has(tableLower)) {
|
|
573
|
+
return true;
|
|
574
|
+
}
|
|
575
|
+
// Tier 3: English fallback
|
|
576
|
+
return fallbackOrgTables.includes(tableLower);
|
|
502
577
|
});
|
|
503
578
|
}
|
|
504
579
|
isStringLiteral(node, adapter) {
|
|
@@ -519,14 +594,16 @@ export class UniversalDataAccessAnalyzer extends UniversalAnalyzer {
|
|
|
519
594
|
* R4.1: Find database queries inside loops and flag them as N+1 risks.
|
|
520
595
|
* Each finding carries the query call location (never line 1).
|
|
521
596
|
*/
|
|
522
|
-
checkLoopQueries(ast, adapter, sourceCode, config) {
|
|
597
|
+
checkLoopQueries(ast, adapter, sourceCode, config, provenanceContext) {
|
|
523
598
|
const violations = [];
|
|
524
|
-
// Find all nodes that look like database calls
|
|
599
|
+
// Find all nodes that look like database calls (Spec 21: provenance-gated)
|
|
525
600
|
const dbNodes = adapter.findNodes(ast, {
|
|
526
|
-
custom: (node) => this.isDbCallNode(node, adapter, sourceCode),
|
|
601
|
+
custom: (node) => this.isDbCallNode(node, adapter, sourceCode, provenanceContext),
|
|
527
602
|
});
|
|
528
|
-
// Track reported line+loop combos to avoid duplicates
|
|
603
|
+
// Track reported line+loop combos to avoid duplicates (runtime dedup only)
|
|
529
604
|
const reported = new Set();
|
|
605
|
+
// Track symbol ordinals per enclosing function for fingerprint stability
|
|
606
|
+
const loopOrdinals = new Map();
|
|
530
607
|
for (const node of dbNodes) {
|
|
531
608
|
const nodeText = adapter.getNodeText(node, sourceCode);
|
|
532
609
|
if (!nodeText || nodeText.trim().length < 10)
|
|
@@ -541,6 +618,12 @@ export class UniversalDataAccessAnalyzer extends UniversalAnalyzer {
|
|
|
541
618
|
if (reported.has(dedupKey))
|
|
542
619
|
continue;
|
|
543
620
|
reported.add(dedupKey);
|
|
621
|
+
// Stable fingerprint symbol: enclosing function + ordinal
|
|
622
|
+
const enclosingFn = this.findEnclosingFunctionName(node, adapter);
|
|
623
|
+
const baseSym = `${enclosingFn}:loop-query`;
|
|
624
|
+
const count = (loopOrdinals.get(baseSym) ?? 0) + 1;
|
|
625
|
+
loopOrdinals.set(baseSym, count);
|
|
626
|
+
const sym = count > 1 ? `${baseSym}:${count}` : baseSym;
|
|
544
627
|
// R4.2: Nested-loop attribution
|
|
545
628
|
const depthMsg = loopInfo.depth > 1
|
|
546
629
|
? ` (nested ${loopInfo.depth} levels deep)`
|
|
@@ -549,7 +632,7 @@ export class UniversalDataAccessAnalyzer extends UniversalAnalyzer {
|
|
|
549
632
|
`Database query inside loop${depthMsg} ` +
|
|
550
633
|
`(loop at line ${loopInfo.loopNode.location.start.line}). ` +
|
|
551
634
|
`This may cause N+1 performance issues. Consider batching queries or using a join.`, 'warning', // R7: loop-query → warning
|
|
552
|
-
'loop-query'));
|
|
635
|
+
'loop-query', undefined, sym));
|
|
553
636
|
}
|
|
554
637
|
return violations;
|
|
555
638
|
}
|
|
@@ -557,16 +640,35 @@ export class UniversalDataAccessAnalyzer extends UniversalAnalyzer {
|
|
|
557
640
|
* R4.1: Determine if a node is a database call expression.
|
|
558
641
|
* Lightweight check — reused from extractDatabaseCalls logic.
|
|
559
642
|
*/
|
|
560
|
-
|
|
643
|
+
/**
|
|
644
|
+
* R4.1: Determine if a node is a database call expression.
|
|
645
|
+
* Spec 21: When provenance context is available, uses provenance-based detection
|
|
646
|
+
* (conjunctive guard — never name alone). In names mode or without context,
|
|
647
|
+
* falls back to the legacy dbPatterns text match.
|
|
648
|
+
*/
|
|
649
|
+
isDbCallNode(node, adapter, sourceCode, provenanceContext) {
|
|
650
|
+
// Spec 21: Provenance-first detection when context is available
|
|
651
|
+
if (provenanceContext && provenanceContext.mode !== 'names') {
|
|
652
|
+
// In provenance or hybrid mode, use provenance check
|
|
653
|
+
if (this.isFunctionCall(node, adapter)) {
|
|
654
|
+
if (isDBProvenanced(node, adapter, sourceCode, provenanceContext, DB_CALL_METHODS)) {
|
|
655
|
+
return true;
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
// Template literal with SQL keywords — syntax check, no name involved
|
|
659
|
+
if (this.isTemplateLiteral(node, adapter)) {
|
|
660
|
+
return this.containsSQLKeywords(adapter.getNodeText(node, sourceCode));
|
|
661
|
+
}
|
|
662
|
+
return false;
|
|
663
|
+
}
|
|
664
|
+
// Legacy fallback: name-based matching for names mode / no context
|
|
561
665
|
const nodeText = adapter.getNodeText(node, sourceCode);
|
|
562
|
-
// Call-expression with DB patterns
|
|
563
666
|
if (this.isFunctionCall(node, adapter)) {
|
|
564
667
|
const dbPatterns = ['select', 'insert', 'update', 'delete', 'from', 'where', 'execute', 'query', 'find', 'aggregate', 'count', 'distinct'];
|
|
565
668
|
if (dbPatterns.some(pattern => nodeText.toLowerCase().includes(pattern))) {
|
|
566
669
|
return true;
|
|
567
670
|
}
|
|
568
671
|
}
|
|
569
|
-
// Template literal with SQL keywords
|
|
570
672
|
if (this.isTemplateLiteral(node, adapter)) {
|
|
571
673
|
return this.containsSQLKeywords(nodeText);
|
|
572
674
|
}
|
|
@@ -653,5 +755,59 @@ export class UniversalDataAccessAnalyzer extends UniversalAnalyzer {
|
|
|
653
755
|
}
|
|
654
756
|
return '';
|
|
655
757
|
}
|
|
758
|
+
/**
|
|
759
|
+
* Walk the parent chain to find the enclosing function/method/arrow name.
|
|
760
|
+
* Returns 'top-level' if no enclosing function is found.
|
|
761
|
+
*
|
|
762
|
+
* Used for stable fingerprint symbols — the enclosing function name is
|
|
763
|
+
* immune to line drift (Spec 18 Gap 2).
|
|
764
|
+
*/
|
|
765
|
+
findEnclosingFunctionName(node, adapter) {
|
|
766
|
+
let current = adapter.getParent(node);
|
|
767
|
+
while (current) {
|
|
768
|
+
const type = adapter.getNodeType(current);
|
|
769
|
+
// Arrow functions, function declarations, function expressions
|
|
770
|
+
if (type === 'arrow_function' ||
|
|
771
|
+
type === 'function_declaration' ||
|
|
772
|
+
type === 'function_expression' ||
|
|
773
|
+
type === 'generator_function_declaration' ||
|
|
774
|
+
type === 'generator_function_expression') {
|
|
775
|
+
const name = this.getNodeName(current, adapter);
|
|
776
|
+
if (name)
|
|
777
|
+
return name;
|
|
778
|
+
}
|
|
779
|
+
// Method definitions on classes/objects
|
|
780
|
+
if (type === 'method_definition' || adapter.isMethod(current)) {
|
|
781
|
+
const name = this.getNodeName(current, adapter);
|
|
782
|
+
if (name)
|
|
783
|
+
return name;
|
|
784
|
+
}
|
|
785
|
+
current = adapter.getParent(current);
|
|
786
|
+
}
|
|
787
|
+
return 'top-level';
|
|
788
|
+
}
|
|
789
|
+
/**
|
|
790
|
+
* Extract the name/identifier from a function or method AST node.
|
|
791
|
+
*/
|
|
792
|
+
getNodeName(node, adapter) {
|
|
793
|
+
// Try named children first
|
|
794
|
+
if (node.name && typeof node.name === 'string')
|
|
795
|
+
return node.name;
|
|
796
|
+
if (node.text && typeof node.text === 'string')
|
|
797
|
+
return node.text;
|
|
798
|
+
// Walk children for identifier / property_identifier
|
|
799
|
+
if (node.children) {
|
|
800
|
+
for (const child of node.children) {
|
|
801
|
+
const childType = adapter.getNodeType(child);
|
|
802
|
+
if (childType === 'identifier' ||
|
|
803
|
+
childType === 'property_identifier') {
|
|
804
|
+
const name = this.getNodeName(child, adapter);
|
|
805
|
+
if (name)
|
|
806
|
+
return name;
|
|
807
|
+
}
|
|
808
|
+
}
|
|
809
|
+
}
|
|
810
|
+
return '';
|
|
811
|
+
}
|
|
656
812
|
}
|
|
657
813
|
//# sourceMappingURL=UniversalDataAccessAnalyzer.js.map
|