code-auditor-mcp 4.0.4 → 4.1.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 +96 -0
- package/README.md +5 -5
- package/configs/hhra-compat.json +2 -2
- package/dist/analyzers/applicability.d.ts +25 -2
- package/dist/analyzers/applicability.d.ts.map +1 -1
- package/dist/analyzers/applicability.js +44 -50
- package/dist/analyzers/applicability.js.map +1 -1
- package/dist/analyzers/cross-language/APIContractAnalyzer.d.ts +18 -49
- package/dist/analyzers/cross-language/APIContractAnalyzer.d.ts.map +1 -1
- package/dist/analyzers/cross-language/APIContractAnalyzer.js +22 -240
- package/dist/analyzers/cross-language/APIContractAnalyzer.js.map +1 -1
- package/dist/analyzers/cross-language/SchemaValidator.d.ts +2 -14
- package/dist/analyzers/cross-language/SchemaValidator.d.ts.map +1 -1
- package/dist/analyzers/cross-language/SchemaValidator.js +0 -104
- package/dist/analyzers/cross-language/SchemaValidator.js.map +1 -1
- package/dist/analyzers/orgFilterTiers.d.ts +89 -0
- package/dist/analyzers/orgFilterTiers.d.ts.map +1 -0
- package/dist/analyzers/orgFilterTiers.js +96 -0
- package/dist/analyzers/orgFilterTiers.js.map +1 -0
- package/dist/analyzers/ruleRegistry.d.ts +15 -0
- package/dist/analyzers/ruleRegistry.d.ts.map +1 -1
- package/dist/analyzers/ruleRegistry.js +139 -185
- package/dist/analyzers/ruleRegistry.js.map +1 -1
- package/dist/analyzers/universal/UniversalDataAccessAnalyzer.d.ts +39 -0
- package/dist/analyzers/universal/UniversalDataAccessAnalyzer.d.ts.map +1 -1
- package/dist/analyzers/universal/UniversalDataAccessAnalyzer.js +62 -41
- package/dist/analyzers/universal/UniversalDataAccessAnalyzer.js.map +1 -1
- package/dist/analyzers/universal/UniversalSchemaAnalyzer.d.ts +2 -2
- package/dist/analyzers/universal/UniversalSchemaAnalyzer.d.ts.map +1 -1
- package/dist/analyzers/universal/UniversalSchemaAnalyzer.js +2 -2
- package/dist/analyzers/universal/UniversalSchemaAnalyzer.js.map +1 -1
- package/dist/analyzers/universal/UniversalSecurityAnalyzer.d.ts +86 -0
- package/dist/analyzers/universal/UniversalSecurityAnalyzer.d.ts.map +1 -0
- package/dist/analyzers/universal/UniversalSecurityAnalyzer.js +606 -0
- package/dist/analyzers/universal/UniversalSecurityAnalyzer.js.map +1 -0
- package/dist/analyzers/universal/schema/codeAnalysis.d.ts +14 -0
- package/dist/analyzers/universal/schema/codeAnalysis.d.ts.map +1 -1
- package/dist/analyzers/universal/schema/codeAnalysis.js +35 -18
- package/dist/analyzers/universal/schema/codeAnalysis.js.map +1 -1
- package/dist/analyzers/universal/schema/migrations.d.ts +14 -1
- package/dist/analyzers/universal/schema/migrations.d.ts.map +1 -1
- package/dist/analyzers/universal/schema/migrations.js +58 -14
- package/dist/analyzers/universal/schema/migrations.js.map +1 -1
- package/dist/auditRunner.d.ts.map +1 -1
- package/dist/auditRunner.js +70 -13
- package/dist/auditRunner.js.map +1 -1
- package/dist/churn/churnExtractor.js +5 -5
- package/dist/churn/churnExtractor.js.map +1 -1
- package/dist/cli.js +21 -9
- package/dist/cli.js.map +1 -1
- package/dist/codeIndexDB.d.ts +6 -0
- package/dist/codeIndexDB.d.ts.map +1 -1
- package/dist/codeIndexDB.js +33 -1
- package/dist/codeIndexDB.js.map +1 -1
- package/dist/config/configGeneratePath.d.ts +19 -0
- package/dist/config/configGeneratePath.d.ts.map +1 -0
- package/dist/config/configGeneratePath.js +29 -0
- package/dist/config/configGeneratePath.js.map +1 -0
- package/dist/config/configLoader.d.ts +36 -4
- package/dist/config/configLoader.d.ts.map +1 -1
- package/dist/config/configLoader.js +136 -15
- package/dist/config/configLoader.js.map +1 -1
- package/dist/config/lintConfigReader.d.ts +4 -0
- package/dist/config/lintConfigReader.d.ts.map +1 -1
- package/dist/config/lintConfigReader.js +35 -36
- package/dist/config/lintConfigReader.js.map +1 -1
- package/dist/config/staticObjectExtract.d.ts +35 -0
- package/dist/config/staticObjectExtract.d.ts.map +1 -0
- package/dist/config/staticObjectExtract.js +312 -0
- package/dist/config/staticObjectExtract.js.map +1 -0
- package/dist/constants.d.ts +1 -2
- package/dist/constants.d.ts.map +1 -1
- package/dist/constants.js +0 -2
- package/dist/constants.js.map +1 -1
- package/dist/daemon/core.js +1 -1
- package/dist/daemon/core.js.map +1 -1
- package/dist/daemon/main.js +1 -1
- package/dist/daemon/main.js.map +1 -1
- package/dist/dataPaths.d.ts.map +1 -1
- package/dist/dataPaths.js +11 -2
- package/dist/dataPaths.js.map +1 -1
- package/dist/generators/BaseConfigGenerator.d.ts +0 -4
- package/dist/generators/BaseConfigGenerator.d.ts.map +1 -1
- package/dist/generators/BaseConfigGenerator.js +1 -7
- package/dist/generators/BaseConfigGenerator.js.map +1 -1
- package/dist/installConfig.d.ts +3 -1
- package/dist/installConfig.d.ts.map +1 -1
- package/dist/installConfig.js +15 -2
- package/dist/installConfig.js.map +1 -1
- package/dist/installer.d.ts +10 -0
- package/dist/installer.d.ts.map +1 -1
- package/dist/installer.js +14 -2
- package/dist/installer.js.map +1 -1
- package/dist/languages/LanguageOrchestrator.js +6 -6
- package/dist/languages/LanguageOrchestrator.js.map +1 -1
- package/dist/languages/RuntimeManager.js +13 -10
- package/dist/languages/RuntimeManager.js.map +1 -1
- package/dist/mcp-tools-shared.d.ts.map +1 -1
- package/dist/mcp-tools-shared.js +7 -2
- package/dist/mcp-tools-shared.js.map +1 -1
- package/dist/mcp-ui-simple.d.ts +5 -1
- package/dist/mcp-ui-simple.d.ts.map +1 -1
- package/dist/mcp-ui-simple.js +81 -29
- package/dist/mcp-ui-simple.js.map +1 -1
- package/dist/mcp.js +19 -4
- package/dist/mcp.js.map +1 -1
- package/dist/mcpAuditJobs.d.ts.map +1 -1
- package/dist/mcpAuditJobs.js +1 -0
- package/dist/mcpAuditJobs.js.map +1 -1
- package/dist/nextFileIncremental.js +1 -1
- package/dist/nextFileIncremental.js.map +1 -1
- package/dist/pipeline.d.ts.map +1 -1
- package/dist/pipeline.js +13 -2
- package/dist/pipeline.js.map +1 -1
- package/dist/pipelineAdapters.d.ts +15 -0
- package/dist/pipelineAdapters.d.ts.map +1 -1
- package/dist/pipelineAdapters.js +140 -6
- package/dist/pipelineAdapters.js.map +1 -1
- package/dist/reporting/htmlReportGenerator.js +2 -2
- package/dist/reporting/htmlReportGenerator.js.map +1 -1
- package/dist/ruleAliases.d.ts.map +1 -1
- package/dist/ruleAliases.js +45 -0
- package/dist/ruleAliases.js.map +1 -1
- package/dist/styles/tailwindConfigLoader.d.ts +4 -0
- package/dist/styles/tailwindConfigLoader.d.ts.map +1 -1
- package/dist/styles/tailwindConfigLoader.js +54 -36
- package/dist/styles/tailwindConfigLoader.js.map +1 -1
- package/dist/types.d.ts +55 -2
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +15 -0
- package/dist/types.js.map +1 -1
- package/dist/version.generated.d.ts +1 -1
- package/dist/version.generated.js +1 -1
- package/examples/.auditrc.example.json +2 -2
- package/examples/.auditrc.json +3 -3
- package/examples/nextjs.auditrc.json +1 -1
- package/examples/node-api.auditrc.json +1 -1
- package/examples/react.auditrc.json +2 -2
- package/examples/tolerant-solid.auditrc.json +1 -1
- package/package.json +2 -1
- package/plugin/.claude-plugin/plugin.json +1 -1
- package/plugin/scripts/hook-common.sh +27 -27
- package/plugin/skills/code-auditor/SKILL.md +1 -1
- package/server.json +2 -2
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tenant-scoping tier resolution (Spec 62 Amendment B).
|
|
3
|
+
*
|
|
4
|
+
* The single source of truth for "does a table require an org/tenant filter?"
|
|
5
|
+
* and "is the missing-org-filter rule applicable to this corpus?". Both the
|
|
6
|
+
* firing predicate (the Stage-4 missing-org-filter reducer) and the
|
|
7
|
+
* applicability predicate (pipeline.ts → applicability.ts) are derived from
|
|
8
|
+
* this one function, so the two can never drift to different tier sets.
|
|
9
|
+
*
|
|
10
|
+
* Three tiers, all read through {@link buildOrgFilterTierSet}:
|
|
11
|
+
*
|
|
12
|
+
* Tier 1 (config-primary) — `orgFilterTables`: the user's explicit list of
|
|
13
|
+
* multi-tenant tables. Tenancy is policy; this is the declaration of record.
|
|
14
|
+
*
|
|
15
|
+
* Tier 2 (schema-inference) — a configured `schemas` table that carries a
|
|
16
|
+
* column matching `orgFilterColumns` (default org_id/tenant_id/
|
|
17
|
+
* organization_id/workspace_id). Makes non-English table names detectable
|
|
18
|
+
* with zero explicit declaration.
|
|
19
|
+
*
|
|
20
|
+
* Tier 3 (DDL-discovery) — a table whose columns, extracted from SQL DDL
|
|
21
|
+
* (CREATE TABLE / ALTER TABLE … ADD COLUMN), carry a tenant-scoping column.
|
|
22
|
+
* This is the tier that was missing from the old firing predicate, which is
|
|
23
|
+
* what made `missing-org-filter` report a DDL-declared multi-tenant codebase
|
|
24
|
+
* `clean` (applicability un-suppressed the rule via Tier 3, but firing only
|
|
25
|
+
* read Tiers 1–2, so nothing fired).
|
|
26
|
+
*
|
|
27
|
+
* The firing predicate and the applicability predicate are NOT separate
|
|
28
|
+
* implementations that "agree by inspection" — they are two consumers of the
|
|
29
|
+
* same {@link OrgFilterTierSet}:
|
|
30
|
+
*
|
|
31
|
+
* - firing: `tableRequiresOrgFilter(queryTables, tierSet)`
|
|
32
|
+
* - applicability: `hasDeclaredTenancy(tierSet)`
|
|
33
|
+
*
|
|
34
|
+
* This module is deliberately free of analyzer and pipeline imports: it is
|
|
35
|
+
* imported statically by applicability.ts (which pipeline.ts imports
|
|
36
|
+
* statically) AND by the Stage-4 reducer in pipelineAdapters.ts (which reaches
|
|
37
|
+
* analyzers only through dynamic import()). Neither import path may pull in
|
|
38
|
+
* pipeline.js or an analyzer class.
|
|
39
|
+
*/
|
|
40
|
+
/** Default tenant-scoping column names (lowercased by the consumer). */
|
|
41
|
+
export declare const DEFAULT_ORG_FILTER_COLUMNS: string[];
|
|
42
|
+
/** The loose config shape the tier resolver reads. Compatible with the
|
|
43
|
+
* data-access analyzer's `DataAccessAnalyzerConfig` and with the bare
|
|
44
|
+
* `Record<string, unknown>` the pipeline passes to applicability. */
|
|
45
|
+
export interface OrgFilterConfig {
|
|
46
|
+
orgFilterTables?: string[];
|
|
47
|
+
orgFilterColumns?: string[];
|
|
48
|
+
schemas?: Array<{
|
|
49
|
+
name?: string;
|
|
50
|
+
tables?: Array<{
|
|
51
|
+
name?: string;
|
|
52
|
+
columns?: Array<{
|
|
53
|
+
name?: unknown;
|
|
54
|
+
type?: unknown;
|
|
55
|
+
}>;
|
|
56
|
+
}>;
|
|
57
|
+
}>;
|
|
58
|
+
}
|
|
59
|
+
/** The resolved tenant-scoping picture across all three tiers. */
|
|
60
|
+
export interface OrgFilterTierSet {
|
|
61
|
+
/** Tier 1 — explicit tenant table names, lowercased. */
|
|
62
|
+
orgFilterTables: Set<string>;
|
|
63
|
+
/** Tier 2 — configured-schema tables carrying a tenant column, lowercased. */
|
|
64
|
+
schemaOrgTables: Set<string>;
|
|
65
|
+
/** Tier 3 — DDL-discovered tables carrying a tenant column, lowercased. */
|
|
66
|
+
ddlOrgTables: Set<string>;
|
|
67
|
+
/** The tenant-scoping column names this config treats as evidence, lowercased. */
|
|
68
|
+
tenantColumns: string[];
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Build the full tenant-scoping tier set from config + DDL-discovered
|
|
72
|
+
* per-table columns. `ddlTableColumns` is the corpus-wide `Record<table,
|
|
73
|
+
* string[]>` folded by the schema reducer; it is undefined when the schema
|
|
74
|
+
* analyzer was not part of the run (Tier 3 is then empty — the rule can still
|
|
75
|
+
* fire on Tiers 1–2, which come from config alone).
|
|
76
|
+
*/
|
|
77
|
+
export declare function buildOrgFilterTierSet(config: OrgFilterConfig | undefined, ddlTableColumns: Record<string, string[]> | undefined): OrgFilterTierSet;
|
|
78
|
+
/**
|
|
79
|
+
* Firing predicate — true when any of the query's referenced tables requires an
|
|
80
|
+
* org/tenant filter, across all three tiers.
|
|
81
|
+
*/
|
|
82
|
+
export declare function tableRequiresOrgFilter(tables: string[], tierSet: OrgFilterTierSet): boolean;
|
|
83
|
+
/**
|
|
84
|
+
* Applicability predicate — true when any tier declares tenancy. When false,
|
|
85
|
+
* the rule is `notApplicable` (this corpus has no tenant-scoping input), never
|
|
86
|
+
* `clean` (the strongest claim the tool makes).
|
|
87
|
+
*/
|
|
88
|
+
export declare function hasDeclaredTenancy(tierSet: OrgFilterTierSet): boolean;
|
|
89
|
+
//# sourceMappingURL=orgFilterTiers.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"orgFilterTiers.d.ts","sourceRoot":"","sources":["../../src/analyzers/orgFilterTiers.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AAEH,wEAAwE;AACxE,eAAO,MAAM,0BAA0B,UAKtC,CAAC;AAEF;;sEAEsE;AACtE,MAAM,WAAW,eAAe;IAC9B,eAAe,CAAC,EAAE,MAAM,EAAE,CAAC;IAC3B,gBAAgB,CAAC,EAAE,MAAM,EAAE,CAAC;IAC5B,OAAO,CAAC,EAAE,KAAK,CAAC;QACd,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,MAAM,CAAC,EAAE,KAAK,CAAC;YACb,IAAI,CAAC,EAAE,MAAM,CAAC;YACd,OAAO,CAAC,EAAE,KAAK,CAAC;gBAAE,IAAI,CAAC,EAAE,OAAO,CAAC;gBAAC,IAAI,CAAC,EAAE,OAAO,CAAA;aAAE,CAAC,CAAC;SACrD,CAAC,CAAC;KACJ,CAAC,CAAC;CACJ;AAED,kEAAkE;AAClE,MAAM,WAAW,gBAAgB;IAC/B,wDAAwD;IACxD,eAAe,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IAC7B,8EAA8E;IAC9E,eAAe,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IAC7B,2EAA2E;IAC3E,YAAY,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IAC1B,kFAAkF;IAClF,aAAa,EAAE,MAAM,EAAE,CAAC;CACzB;AAED;;;;;;GAMG;AACH,wBAAgB,qBAAqB,CACnC,MAAM,EAAE,eAAe,GAAG,SAAS,EACnC,eAAe,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,GAAG,SAAS,GACpD,gBAAgB,CA6BlB;AAED;;;GAGG;AACH,wBAAgB,sBAAsB,CACpC,MAAM,EAAE,MAAM,EAAE,EAChB,OAAO,EAAE,gBAAgB,GACxB,OAAO,CAST;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,gBAAgB,GAAG,OAAO,CAMrE"}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tenant-scoping tier resolution (Spec 62 Amendment B).
|
|
3
|
+
*
|
|
4
|
+
* The single source of truth for "does a table require an org/tenant filter?"
|
|
5
|
+
* and "is the missing-org-filter rule applicable to this corpus?". Both the
|
|
6
|
+
* firing predicate (the Stage-4 missing-org-filter reducer) and the
|
|
7
|
+
* applicability predicate (pipeline.ts → applicability.ts) are derived from
|
|
8
|
+
* this one function, so the two can never drift to different tier sets.
|
|
9
|
+
*
|
|
10
|
+
* Three tiers, all read through {@link buildOrgFilterTierSet}:
|
|
11
|
+
*
|
|
12
|
+
* Tier 1 (config-primary) — `orgFilterTables`: the user's explicit list of
|
|
13
|
+
* multi-tenant tables. Tenancy is policy; this is the declaration of record.
|
|
14
|
+
*
|
|
15
|
+
* Tier 2 (schema-inference) — a configured `schemas` table that carries a
|
|
16
|
+
* column matching `orgFilterColumns` (default org_id/tenant_id/
|
|
17
|
+
* organization_id/workspace_id). Makes non-English table names detectable
|
|
18
|
+
* with zero explicit declaration.
|
|
19
|
+
*
|
|
20
|
+
* Tier 3 (DDL-discovery) — a table whose columns, extracted from SQL DDL
|
|
21
|
+
* (CREATE TABLE / ALTER TABLE … ADD COLUMN), carry a tenant-scoping column.
|
|
22
|
+
* This is the tier that was missing from the old firing predicate, which is
|
|
23
|
+
* what made `missing-org-filter` report a DDL-declared multi-tenant codebase
|
|
24
|
+
* `clean` (applicability un-suppressed the rule via Tier 3, but firing only
|
|
25
|
+
* read Tiers 1–2, so nothing fired).
|
|
26
|
+
*
|
|
27
|
+
* The firing predicate and the applicability predicate are NOT separate
|
|
28
|
+
* implementations that "agree by inspection" — they are two consumers of the
|
|
29
|
+
* same {@link OrgFilterTierSet}:
|
|
30
|
+
*
|
|
31
|
+
* - firing: `tableRequiresOrgFilter(queryTables, tierSet)`
|
|
32
|
+
* - applicability: `hasDeclaredTenancy(tierSet)`
|
|
33
|
+
*
|
|
34
|
+
* This module is deliberately free of analyzer and pipeline imports: it is
|
|
35
|
+
* imported statically by applicability.ts (which pipeline.ts imports
|
|
36
|
+
* statically) AND by the Stage-4 reducer in pipelineAdapters.ts (which reaches
|
|
37
|
+
* analyzers only through dynamic import()). Neither import path may pull in
|
|
38
|
+
* pipeline.js or an analyzer class.
|
|
39
|
+
*/
|
|
40
|
+
/** Default tenant-scoping column names (lowercased by the consumer). */
|
|
41
|
+
export const DEFAULT_ORG_FILTER_COLUMNS = [
|
|
42
|
+
'org_id',
|
|
43
|
+
'tenant_id',
|
|
44
|
+
'organization_id',
|
|
45
|
+
'workspace_id',
|
|
46
|
+
];
|
|
47
|
+
/**
|
|
48
|
+
* Build the full tenant-scoping tier set from config + DDL-discovered
|
|
49
|
+
* per-table columns. `ddlTableColumns` is the corpus-wide `Record<table,
|
|
50
|
+
* string[]>` folded by the schema reducer; it is undefined when the schema
|
|
51
|
+
* analyzer was not part of the run (Tier 3 is then empty — the rule can still
|
|
52
|
+
* fire on Tiers 1–2, which come from config alone).
|
|
53
|
+
*/
|
|
54
|
+
export function buildOrgFilterTierSet(config, ddlTableColumns) {
|
|
55
|
+
const tenantColumns = (config?.orgFilterColumns ?? DEFAULT_ORG_FILTER_COLUMNS).map((c) => String(c).toLowerCase());
|
|
56
|
+
const tenantSet = new Set(tenantColumns);
|
|
57
|
+
const orgFilterTables = new Set((config?.orgFilterTables ?? []).map((t) => String(t).toLowerCase()));
|
|
58
|
+
const schemaOrgTables = new Set();
|
|
59
|
+
for (const schema of config?.schemas ?? []) {
|
|
60
|
+
for (const table of schema.tables ?? []) {
|
|
61
|
+
if ((table.columns ?? []).some((c) => tenantSet.has(String(c.name).toLowerCase()))) {
|
|
62
|
+
schemaOrgTables.add(String(table.name).toLowerCase());
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
const ddlOrgTables = new Set();
|
|
67
|
+
for (const [table, columns] of Object.entries(ddlTableColumns ?? {})) {
|
|
68
|
+
if ((columns ?? []).some((c) => tenantSet.has(String(c).toLowerCase()))) {
|
|
69
|
+
ddlOrgTables.add(String(table).toLowerCase());
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return { orgFilterTables, schemaOrgTables, ddlOrgTables, tenantColumns };
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Firing predicate — true when any of the query's referenced tables requires an
|
|
76
|
+
* org/tenant filter, across all three tiers.
|
|
77
|
+
*/
|
|
78
|
+
export function tableRequiresOrgFilter(tables, tierSet) {
|
|
79
|
+
return tables.some((t) => {
|
|
80
|
+
const lt = String(t).toLowerCase();
|
|
81
|
+
return (tierSet.orgFilterTables.has(lt) ||
|
|
82
|
+
tierSet.schemaOrgTables.has(lt) ||
|
|
83
|
+
tierSet.ddlOrgTables.has(lt));
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Applicability predicate — true when any tier declares tenancy. When false,
|
|
88
|
+
* the rule is `notApplicable` (this corpus has no tenant-scoping input), never
|
|
89
|
+
* `clean` (the strongest claim the tool makes).
|
|
90
|
+
*/
|
|
91
|
+
export function hasDeclaredTenancy(tierSet) {
|
|
92
|
+
return (tierSet.orgFilterTables.size > 0 ||
|
|
93
|
+
tierSet.schemaOrgTables.size > 0 ||
|
|
94
|
+
tierSet.ddlOrgTables.size > 0);
|
|
95
|
+
}
|
|
96
|
+
//# sourceMappingURL=orgFilterTiers.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"orgFilterTiers.js","sourceRoot":"","sources":["../../src/analyzers/orgFilterTiers.ts"],"names":[],"mappings":"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"}
|
|
@@ -155,6 +155,21 @@ export declare const ALL_ANALYZERS: readonly string[];
|
|
|
155
155
|
* Referenced here so mcp.ts and mcp-tools-shared.ts don't each re-type the list.
|
|
156
156
|
*/
|
|
157
157
|
export declare const MCP_DEFAULT_ANALYZERS: readonly string[];
|
|
158
|
+
/**
|
|
159
|
+
* Analyzers the pipeline can run that emit *no* registry rule. Their rules come
|
|
160
|
+
* from a runtime source other than {@link RULE_REGISTRY}: `invariants` reads its
|
|
161
|
+
* rules from the `.codeauditor.json` `rules` array (the seven invariant rule
|
|
162
|
+
* kinds), not from registry rows, so it is absent from {@link ALL_ANALYZERS}.
|
|
163
|
+
*/
|
|
164
|
+
export declare const PIPELINE_ONLY_ANALYZERS: readonly string[];
|
|
165
|
+
/**
|
|
166
|
+
* Every analyzer `enabledAnalyzers` may legitimately name — {@link ALL_ANALYZERS}
|
|
167
|
+
* (registry rule emitters) plus {@link PIPELINE_ONLY_ANALYZERS}. This is the
|
|
168
|
+
* single source of truth for "can this analyzer actually run?" — used by config
|
|
169
|
+
* validation, which must not reject `invariants` (a valid pipeline analyzer that
|
|
170
|
+
* happens to carry no registry rule).
|
|
171
|
+
*/
|
|
172
|
+
export declare const RUNNABLE_ANALYZERS: readonly string[];
|
|
158
173
|
/**
|
|
159
174
|
* A violation in the loose shape the gate and baseline code handle (a Violation
|
|
160
175
|
* with the transitional analyzer/type/violationType/etc. fields still present).
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ruleRegistry.d.ts","sourceRoot":"","sources":["../../src/analyzers/ruleRegistry.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAE9C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AAEH,MAAM,WAAW,iBAAiB;IAChC,4CAA4C;IAC5C,QAAQ,EAAE,MAAM,CAAC;IACjB,4DAA4D;IAC5D,KAAK,EAAE,MAAM,GAAG,WAAW,GAAG,eAAe,GAAG,MAAM,GAAG,cAAc,GAAG,QAAQ,GAAG,SAAS,CAAC;IAC/F;;;;;;OAMG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;;;;;;;;;;;;;OAcG;IACH,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;IACjB;;;;;;OAMG;IACH,UAAU,EAAE,OAAO,CAAC;IACpB;;;;OAIG;IACH,OAAO,EAAE,MAAM,CAAC;IAChB;;;;OAIG;IACH,IAAI,EAAE,MAAM,CAAC;IACb;;;;;OAKG;IACH,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB;;;;;;OAMG;IACH,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B;;;;;OAKG;IACH,OAAO,EAAE,WAAW,CAAC;CACtB;AAED;;;;;;;;;GASG;AACH,MAAM,WAAW,UAAU;IACzB,+FAA+F;IAC/F,IAAI,EAAE,MAAM,CAAC;IACb,kDAAkD;IAClD,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,4FAA4F;IAC5F,UAAU,CAAC,EAAE,UAAU,CAAC;CACzB;AAED,8DAA8D;AAC9D,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,UAAU,EAAE,CAAC;IACpB,OAAO,EAAE,UAAU,EAAE,CAAC;CACvB;AAED;;;;;;;GAOG;AACH,eAAO,MAAM,aAAa,EAAE,MAAM,CAAC,MAAM,EAAE,QAAQ,CAAC,iBAAiB,CAAC,
|
|
1
|
+
{"version":3,"file":"ruleRegistry.d.ts","sourceRoot":"","sources":["../../src/analyzers/ruleRegistry.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAE9C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AAEH,MAAM,WAAW,iBAAiB;IAChC,4CAA4C;IAC5C,QAAQ,EAAE,MAAM,CAAC;IACjB,4DAA4D;IAC5D,KAAK,EAAE,MAAM,GAAG,WAAW,GAAG,eAAe,GAAG,MAAM,GAAG,cAAc,GAAG,QAAQ,GAAG,SAAS,CAAC;IAC/F;;;;;;OAMG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;;;;;;;;;;;;;OAcG;IACH,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;IACjB;;;;;;OAMG;IACH,UAAU,EAAE,OAAO,CAAC;IACpB;;;;OAIG;IACH,OAAO,EAAE,MAAM,CAAC;IAChB;;;;OAIG;IACH,IAAI,EAAE,MAAM,CAAC;IACb;;;;;OAKG;IACH,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB;;;;;;OAMG;IACH,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B;;;;;OAKG;IACH,OAAO,EAAE,WAAW,CAAC;CACtB;AAED;;;;;;;;;GASG;AACH,MAAM,WAAW,UAAU;IACzB,+FAA+F;IAC/F,IAAI,EAAE,MAAM,CAAC;IACb,kDAAkD;IAClD,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,4FAA4F;IAC5F,UAAU,CAAC,EAAE,UAAU,CAAC;CACzB;AAED,8DAA8D;AAC9D,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,UAAU,EAAE,CAAC;IACpB,OAAO,EAAE,UAAU,EAAE,CAAC;CACvB;AAED;;;;;;;GAOG;AACH,eAAO,MAAM,aAAa,EAAE,MAAM,CAAC,MAAM,EAAE,QAAQ,CAAC,iBAAiB,CAAC,CA26DrE,CAAC;AAEF;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,aAAa,EAAE,SAAS,MAAM,EAEnC,CAAC;AAET;;;;;GAKG;AACH,eAAO,MAAM,qBAAqB,EAAE,SAAS,MAAM,EAOlD,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,uBAAuB,EAAE,SAAS,MAAM,EAAmB,CAAC;AAEzE;;;;;;GAMG;AACH,eAAO,MAAM,kBAAkB,EAAE,SAAS,MAAM,EAExC,CAAC;AAET;;;GAGG;AACH,UAAU,aAAa;IACrB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,qBAAqB,CAAC,CAAC,EAAE,aAAa,GAAG,QAAQ,CAAC,iBAAiB,CAAC,GAAG,SAAS,CAQ/F"}
|
|
@@ -43,7 +43,7 @@ export const RULE_REGISTRY = {
|
|
|
43
43
|
{ code: 'function simple(x) {\n if (x > 0) return x;\n return -x;\n}', nearMiss: true },
|
|
44
44
|
],
|
|
45
45
|
invalid: [
|
|
46
|
-
{ code: 'function complex(x) {\n if (
|
|
46
|
+
{ code: 'function complex(x) {\n if (x && x && x && x && x && x && x && x && x && x && x && x && x && x && x && x && x && x && x && x && x && x && x && x && x && x && x && x && x && x && x && x && x && x && x && x && x && x && x && x && x && x && x && x && x && x && x && x && x && x && x && x && x && x && x) return 1;\n return 0;\n}' },
|
|
47
47
|
],
|
|
48
48
|
},
|
|
49
49
|
},
|
|
@@ -142,7 +142,7 @@ export const RULE_REGISTRY = {
|
|
|
142
142
|
{ code: 'interface Printer {\n print(doc) { return doc; }\n}', nearMiss: true },
|
|
143
143
|
],
|
|
144
144
|
invalid: [
|
|
145
|
-
{ code: 'interface Machine {\n op1()
|
|
145
|
+
{ code: 'interface Machine {\n op1(): void;\n op2(): void;\n op3(): void;\n op4(): void;\n op5(): void;\n op6(): void;\n op7(): void;\n op8(): void;\n op9(): void;\n op10(): void;\n op11(): void;\n op12(): void;\n op13(): void;\n op14(): void;\n op15(): void;\n op16(): void;\n op17(): void;\n op18(): void;\n op19(): void;\n op20(): void;\n op21(): void;\n op22(): void;\n op23(): void;\n op24(): void;\n op25(): void;\n op26(): void;\n}' },
|
|
146
146
|
],
|
|
147
147
|
},
|
|
148
148
|
},
|
|
@@ -395,8 +395,13 @@ export const RULE_REGISTRY = {
|
|
|
395
395
|
],
|
|
396
396
|
invalid: [
|
|
397
397
|
{
|
|
398
|
-
|
|
399
|
-
|
|
398
|
+
// Two IDENTICAL ≥15-line for-loops — `dry/duplicate` is an *exact*
|
|
399
|
+
// token match (normalizeCode keeps identifiers), so the two blocks must
|
|
400
|
+
// be byte-identical, not merely same-shape. The old sample (`function a`
|
|
401
|
+
// / `function b` with identical bodies) was doubly dead: its names differ
|
|
402
|
+
// (no exact hash match) and it was under minLineThreshold.
|
|
403
|
+
code: 'function process(rows) {\n for (const row of rows) {\n const id = row.id;\n const name = row.name;\n const value = row.value;\n const category = row.category;\n const tags = row.tags;\n const meta = row.meta;\n const score = computeScore(row);\n const rank = computeRank(score);\n const label = formatLabel(name);\n const bucket = assignBucket(rank);\n const flags = extractFlags(row);\n const audit = buildAudit(flags);\n const record = { id, name, value, category, tags, meta, score, rank, label, bucket, audit };\n push(record);\n notify(record.id);\n }\n for (const row of rows) {\n const id = row.id;\n const name = row.name;\n const value = row.value;\n const category = row.category;\n const tags = row.tags;\n const meta = row.meta;\n const score = computeScore(row);\n const rank = computeRank(score);\n const label = formatLabel(name);\n const bucket = assignBucket(rank);\n const flags = extractFlags(row);\n const audit = buildAudit(flags);\n const record = { id, name, value, category, tags, meta, score, rank, label, bucket, audit };\n push(record);\n notify(record.id);\n }\n}',
|
|
404
|
+
resolution: { action: 'extract-shared', summary: 'Extract the duplicated block into a shared helper and call it from both sites.', symbols: ['process'] },
|
|
400
405
|
},
|
|
401
406
|
],
|
|
402
407
|
},
|
|
@@ -415,7 +420,14 @@ export const RULE_REGISTRY = {
|
|
|
415
420
|
{ code: 'function a(x) {\n return x + 1;\n}\nfunction b(x) {\n return x * 2;\n}', nearMiss: true },
|
|
416
421
|
],
|
|
417
422
|
invalid: [
|
|
418
|
-
{
|
|
423
|
+
{
|
|
424
|
+
// Two structurally-identical ≥15-line functions with different
|
|
425
|
+
// identifiers/literals. `dry/structural-similarity` compares the
|
|
426
|
+
// token-kind skeleton (normalizeCodeForStructure), so the two must share
|
|
427
|
+
// structure but differ in names — the old two-line `fetch().then()`
|
|
428
|
+
// pair was under minLineThreshold and never emitted.
|
|
429
|
+
code: 'function computeA(rows) {\n for (const item of rows) {\n const id = item.id;\n const name = item.name;\n const value = item.value;\n const category = item.category;\n const tags = item.tags;\n const meta = item.meta;\n const score = scoreA(item);\n const rank = rankA(score);\n const label = labelA(name);\n const bucket = bucketA(rank);\n const flags = flagsA(item);\n const audit = auditA(flags);\n const record = { id, name, value, category, tags, meta, score, rank, label, bucket, audit };\n appendA(record);\n emitA(record.id);\n }\n}\nfunction computeB(rows) {\n for (const entry of rows) {\n const id = entry.id;\n const name = entry.name;\n const value = entry.value;\n const category = entry.category;\n const tags = entry.tags;\n const meta = entry.meta;\n const score = scoreB(entry);\n const rank = rankB(score);\n const label = labelB(name);\n const bucket = bucketB(rank);\n const flags = flagsB(entry);\n const audit = auditB(flags);\n const record = { id, name, value, category, tags, meta, score, rank, label, bucket, audit };\n appendB(record);\n emitB(record.id);\n }\n}',
|
|
430
|
+
},
|
|
419
431
|
],
|
|
420
432
|
},
|
|
421
433
|
},
|
|
@@ -536,11 +548,16 @@ export const RULE_REGISTRY = {
|
|
|
536
548
|
},
|
|
537
549
|
},
|
|
538
550
|
'missing-org-filter': {
|
|
539
|
-
analyzer: 'data-access',
|
|
551
|
+
analyzer: 'data-access-org-filter',
|
|
540
552
|
field: 'rule',
|
|
541
|
-
input: ['schema'],
|
|
542
|
-
resolvable:
|
|
543
|
-
|
|
553
|
+
input: ['data-access', 'schema'],
|
|
554
|
+
resolvable: true,
|
|
555
|
+
// The claim is "no organization/tenant *predicate*", NOT "no filter". A
|
|
556
|
+
// query scoped by primary key (`WHERE id = $1`) still fires, because it is
|
|
557
|
+
// scoped by id but not by tenant — an IDOR surface if the id is
|
|
558
|
+
// request-reachable. The fix is to add the tenant column to the predicate,
|
|
559
|
+
// not to add a filter (one already exists in the PK-scoped case).
|
|
560
|
+
message: 'Query on {tables} has no organization/tenant predicate.',
|
|
544
561
|
docs: 'missing-org-filter',
|
|
545
562
|
thresholds: [],
|
|
546
563
|
samples: {
|
|
@@ -548,7 +565,14 @@ export const RULE_REGISTRY = {
|
|
|
548
565
|
{ code: 'db.query("SELECT * FROM projects WHERE org_id = ?", [orgId])', nearMiss: true },
|
|
549
566
|
],
|
|
550
567
|
invalid: [
|
|
551
|
-
{
|
|
568
|
+
{
|
|
569
|
+
code: 'db.query("SELECT * FROM projects WHERE id = ?", [id])',
|
|
570
|
+
resolution: {
|
|
571
|
+
action: 'add-tenant-predicate',
|
|
572
|
+
summary: 'Add the tenant column (organization_id / org_id) to the WHERE predicate so the query is scoped to the current organization, not just by primary key.',
|
|
573
|
+
symbols: ['db', 'query'],
|
|
574
|
+
},
|
|
575
|
+
},
|
|
552
576
|
],
|
|
553
577
|
},
|
|
554
578
|
},
|
|
@@ -575,7 +599,7 @@ export const RULE_REGISTRY = {
|
|
|
575
599
|
field: 'rule',
|
|
576
600
|
input: ['files'],
|
|
577
601
|
resolvable: false,
|
|
578
|
-
message: 'Unfiltered write on {tables} has no WHERE/HAVING/LIMIT.',
|
|
602
|
+
message: 'Unfiltered write or tenant-scoped read on {tables} has no WHERE/HAVING/LIMIT.',
|
|
579
603
|
docs: 'unfiltered-query',
|
|
580
604
|
thresholds: [],
|
|
581
605
|
samples: {
|
|
@@ -720,10 +744,21 @@ export const RULE_REGISTRY = {
|
|
|
720
744
|
thresholds: [],
|
|
721
745
|
samples: {
|
|
722
746
|
valid: [
|
|
723
|
-
{
|
|
747
|
+
{
|
|
748
|
+
// return-documentation only applies to a typed, non-void return
|
|
749
|
+
// (`func.returnType && !== 'void'`). The old sample returned nothing,
|
|
750
|
+
// so the guard was vacuous; this one returns `number` and documents it.
|
|
751
|
+
code: '/**\n * Computes the result.\n * @returns the result.\n */\nfunction compute(): number { return 1; }',
|
|
752
|
+
nearMiss: true,
|
|
753
|
+
},
|
|
724
754
|
],
|
|
725
755
|
invalid: [
|
|
726
|
-
{
|
|
756
|
+
{
|
|
757
|
+
// Missing @returns on a typed, non-void function. The old sample
|
|
758
|
+
// (`function compute() {}`) had no return type, so the return-doc guard
|
|
759
|
+
// never applied.
|
|
760
|
+
code: '/** Computes the result. */\nfunction compute(): number { return 1; }',
|
|
761
|
+
},
|
|
727
762
|
],
|
|
728
763
|
},
|
|
729
764
|
},
|
|
@@ -848,23 +883,6 @@ export const RULE_REGISTRY = {
|
|
|
848
883
|
],
|
|
849
884
|
},
|
|
850
885
|
},
|
|
851
|
-
'file-error': {
|
|
852
|
-
analyzer: 'schema',
|
|
853
|
-
field: 'rule',
|
|
854
|
-
input: ['schema-json'],
|
|
855
|
-
resolvable: false,
|
|
856
|
-
message: 'Error processing schema file: {error}.',
|
|
857
|
-
docs: 'file-error',
|
|
858
|
-
thresholds: [],
|
|
859
|
-
samples: {
|
|
860
|
-
valid: [
|
|
861
|
-
{ code: '{"valid": true}', nearMiss: true },
|
|
862
|
-
],
|
|
863
|
-
invalid: [
|
|
864
|
-
{ code: 'not a readable schema' },
|
|
865
|
-
],
|
|
866
|
-
},
|
|
867
|
-
},
|
|
868
886
|
'type-mismatch': {
|
|
869
887
|
analyzer: 'schema',
|
|
870
888
|
field: 'rule',
|
|
@@ -1214,7 +1232,13 @@ export const RULE_REGISTRY = {
|
|
|
1214
1232
|
{ code: 'function Simple({ x }) {\n return <div>{x}</div>;\n}', nearMiss: true },
|
|
1215
1233
|
],
|
|
1216
1234
|
invalid: [
|
|
1217
|
-
{
|
|
1235
|
+
{
|
|
1236
|
+
// 22 `if` statements — component complexity (base 1 + one per branch)
|
|
1237
|
+
// must exceed maxComponentComplexity (20). The old sample had 6
|
|
1238
|
+
// branches (complexity 7), under the recalibrated ceiling, so it never
|
|
1239
|
+
// fired.
|
|
1240
|
+
code: 'function Complex(props) {\n if (props.x === 1) return <C1 />;\n if (props.x === 2) return <C2 />;\n if (props.x === 3) return <C3 />;\n if (props.x === 4) return <C4 />;\n if (props.x === 5) return <C5 />;\n if (props.x === 6) return <C6 />;\n if (props.x === 7) return <C7 />;\n if (props.x === 8) return <C8 />;\n if (props.x === 9) return <C9 />;\n if (props.x === 10) return <C10 />;\n if (props.x === 11) return <C11 />;\n if (props.x === 12) return <C12 />;\n if (props.x === 13) return <C13 />;\n if (props.x === 14) return <C14 />;\n if (props.x === 15) return <C15 />;\n if (props.x === 16) return <C16 />;\n if (props.x === 17) return <C17 />;\n if (props.x === 18) return <C18 />;\n if (props.x === 19) return <C19 />;\n if (props.x === 20) return <C20 />;\n if (props.x === 21) return <C21 />;\n if (props.x === 22) return <C22 />;\n return <Z />;\n}',
|
|
1241
|
+
},
|
|
1218
1242
|
],
|
|
1219
1243
|
},
|
|
1220
1244
|
},
|
|
@@ -1318,23 +1342,6 @@ export const RULE_REGISTRY = {
|
|
|
1318
1342
|
// `clean` (which would assert the tool checked something it no longer checks
|
|
1319
1343
|
// as a finding).
|
|
1320
1344
|
// ── schema-validator (SchemaValidator) ──────────────────────────────────
|
|
1321
|
-
'field-mismatch': {
|
|
1322
|
-
analyzer: 'schema-validator',
|
|
1323
|
-
field: 'rule',
|
|
1324
|
-
input: ['cross-language-entities'],
|
|
1325
|
-
resolvable: false,
|
|
1326
|
-
message: 'Field mismatch: {detail}.',
|
|
1327
|
-
docs: 'field-mismatch',
|
|
1328
|
-
thresholds: [],
|
|
1329
|
-
samples: {
|
|
1330
|
-
valid: [
|
|
1331
|
-
{ code: 'model User { id Int @id }', nearMiss: true },
|
|
1332
|
-
],
|
|
1333
|
-
invalid: [
|
|
1334
|
-
{ code: 'model User { id String @id }' },
|
|
1335
|
-
],
|
|
1336
|
-
},
|
|
1337
|
-
},
|
|
1338
1345
|
'schema-field-mismatch': {
|
|
1339
1346
|
analyzer: 'schema-validator',
|
|
1340
1347
|
field: 'rule',
|
|
@@ -1386,143 +1393,6 @@ export const RULE_REGISTRY = {
|
|
|
1386
1393
|
],
|
|
1387
1394
|
},
|
|
1388
1395
|
},
|
|
1389
|
-
'constraint-mismatch': {
|
|
1390
|
-
analyzer: 'schema-validator',
|
|
1391
|
-
field: 'rule',
|
|
1392
|
-
input: ['cross-language-entities'],
|
|
1393
|
-
resolvable: false,
|
|
1394
|
-
message: 'Constraint mismatch: {detail}.',
|
|
1395
|
-
docs: 'constraint-mismatch',
|
|
1396
|
-
thresholds: [],
|
|
1397
|
-
samples: {
|
|
1398
|
-
valid: [
|
|
1399
|
-
{ code: 'model User { id Int @id @default(autoincrement()) }', nearMiss: true },
|
|
1400
|
-
],
|
|
1401
|
-
invalid: [
|
|
1402
|
-
{ code: 'model User { id Int @id }' },
|
|
1403
|
-
],
|
|
1404
|
-
},
|
|
1405
|
-
},
|
|
1406
|
-
'version-mismatch': {
|
|
1407
|
-
analyzer: 'schema-validator',
|
|
1408
|
-
field: 'rule',
|
|
1409
|
-
input: ['cross-language-entities'],
|
|
1410
|
-
resolvable: false,
|
|
1411
|
-
message: 'Version mismatch: {detail}.',
|
|
1412
|
-
docs: 'version-mismatch',
|
|
1413
|
-
thresholds: [],
|
|
1414
|
-
samples: {
|
|
1415
|
-
valid: [
|
|
1416
|
-
{ code: 'export const version = "1.2.3";', nearMiss: true },
|
|
1417
|
-
],
|
|
1418
|
-
invalid: [
|
|
1419
|
-
{ code: 'export const version = "0.0.1";' },
|
|
1420
|
-
],
|
|
1421
|
-
},
|
|
1422
|
-
},
|
|
1423
|
-
// ── api-contract (APIContractAnalyzer) ──────────────────────────────────
|
|
1424
|
-
'api-type-mismatch': {
|
|
1425
|
-
analyzer: 'api-contract',
|
|
1426
|
-
field: 'rule',
|
|
1427
|
-
input: ['cross-language-entities'],
|
|
1428
|
-
resolvable: false,
|
|
1429
|
-
message: 'API type mismatch: {detail}.',
|
|
1430
|
-
docs: 'api-type-mismatch',
|
|
1431
|
-
thresholds: [],
|
|
1432
|
-
samples: {
|
|
1433
|
-
valid: [
|
|
1434
|
-
{ code: 'function getUser(id: number): User { return {} as User; }', nearMiss: true },
|
|
1435
|
-
],
|
|
1436
|
-
invalid: [
|
|
1437
|
-
{ code: 'function getUser(id: number): string { return ""; }' },
|
|
1438
|
-
],
|
|
1439
|
-
},
|
|
1440
|
-
},
|
|
1441
|
-
'missing-endpoint': {
|
|
1442
|
-
analyzer: 'api-contract',
|
|
1443
|
-
field: 'rule',
|
|
1444
|
-
input: ['cross-language-entities'],
|
|
1445
|
-
resolvable: false,
|
|
1446
|
-
message: 'Missing API endpoint: {endpoint}.',
|
|
1447
|
-
docs: 'missing-endpoint',
|
|
1448
|
-
thresholds: [],
|
|
1449
|
-
samples: {
|
|
1450
|
-
valid: [
|
|
1451
|
-
{ code: 'app.get("/users", handler);', nearMiss: true },
|
|
1452
|
-
],
|
|
1453
|
-
invalid: [
|
|
1454
|
-
{ code: 'app.get("/unlisted", handler);' },
|
|
1455
|
-
],
|
|
1456
|
-
},
|
|
1457
|
-
},
|
|
1458
|
-
'api-extra-field': {
|
|
1459
|
-
analyzer: 'api-contract',
|
|
1460
|
-
field: 'rule',
|
|
1461
|
-
input: ['cross-language-entities'],
|
|
1462
|
-
resolvable: false,
|
|
1463
|
-
message: 'Extra field in API response: {field}.',
|
|
1464
|
-
docs: 'api-extra-field',
|
|
1465
|
-
thresholds: [],
|
|
1466
|
-
samples: {
|
|
1467
|
-
valid: [
|
|
1468
|
-
{ code: 'return res.json({ id, name });', nearMiss: true },
|
|
1469
|
-
],
|
|
1470
|
-
invalid: [
|
|
1471
|
-
{ code: 'return res.json({ id, name, internal });' },
|
|
1472
|
-
],
|
|
1473
|
-
},
|
|
1474
|
-
},
|
|
1475
|
-
'api-missing-field': {
|
|
1476
|
-
analyzer: 'api-contract',
|
|
1477
|
-
field: 'rule',
|
|
1478
|
-
input: ['cross-language-entities'],
|
|
1479
|
-
resolvable: false,
|
|
1480
|
-
message: 'Missing field in API response: {field}.',
|
|
1481
|
-
docs: 'api-missing-field',
|
|
1482
|
-
thresholds: [],
|
|
1483
|
-
samples: {
|
|
1484
|
-
valid: [
|
|
1485
|
-
{ code: 'return res.json({ id, name });', nearMiss: true },
|
|
1486
|
-
],
|
|
1487
|
-
invalid: [
|
|
1488
|
-
{ code: 'return res.json({ id });' },
|
|
1489
|
-
],
|
|
1490
|
-
},
|
|
1491
|
-
},
|
|
1492
|
-
'method-mismatch': {
|
|
1493
|
-
analyzer: 'api-contract',
|
|
1494
|
-
field: 'rule',
|
|
1495
|
-
input: ['cross-language-entities'],
|
|
1496
|
-
resolvable: false,
|
|
1497
|
-
message: 'HTTP method mismatch: {detail}.',
|
|
1498
|
-
docs: 'method-mismatch',
|
|
1499
|
-
thresholds: [],
|
|
1500
|
-
samples: {
|
|
1501
|
-
valid: [
|
|
1502
|
-
{ code: 'app.post("/users", createUser);', nearMiss: true },
|
|
1503
|
-
],
|
|
1504
|
-
invalid: [
|
|
1505
|
-
{ code: 'app.get("/users", createUser);' },
|
|
1506
|
-
],
|
|
1507
|
-
},
|
|
1508
|
-
},
|
|
1509
|
-
'auth-mismatch': {
|
|
1510
|
-
analyzer: 'api-contract',
|
|
1511
|
-
field: 'rule',
|
|
1512
|
-
input: ['cross-language-entities'],
|
|
1513
|
-
resolvable: false,
|
|
1514
|
-
message: 'Authentication mismatch: {detail}.',
|
|
1515
|
-
docs: 'auth-mismatch',
|
|
1516
|
-
thresholds: [],
|
|
1517
|
-
samples: {
|
|
1518
|
-
valid: [
|
|
1519
|
-
{ code: 'app.get("/users", auth, handler);', nearMiss: true },
|
|
1520
|
-
],
|
|
1521
|
-
invalid: [
|
|
1522
|
-
{ code: 'app.get("/users", handler);' },
|
|
1523
|
-
],
|
|
1524
|
-
},
|
|
1525
|
-
},
|
|
1526
1396
|
// ── dependency-graph (DependencyGraphBuilder) ───────────────────────────
|
|
1527
1397
|
'circular-dependency': {
|
|
1528
1398
|
analyzer: 'dependency-graph',
|
|
@@ -2021,6 +1891,72 @@ export const RULE_REGISTRY = {
|
|
|
2021
1891
|
],
|
|
2022
1892
|
},
|
|
2023
1893
|
},
|
|
1894
|
+
// ── security (UniversalSecurityAnalyzer) — Spec 61 R6 ─────────────────────
|
|
1895
|
+
'command-injection-risk': {
|
|
1896
|
+
analyzer: 'security',
|
|
1897
|
+
field: 'rule',
|
|
1898
|
+
input: ['files'],
|
|
1899
|
+
resolvable: true,
|
|
1900
|
+
message: 'Unsafe process invocation: {method} is passed a command built by interpolation/concatenation.',
|
|
1901
|
+
docs: 'command-injection-risk',
|
|
1902
|
+
thresholds: [],
|
|
1903
|
+
samples: {
|
|
1904
|
+
valid: [
|
|
1905
|
+
{ code: "execFileSync('git', ['diff', '--name-only', ref])", nearMiss: true },
|
|
1906
|
+
],
|
|
1907
|
+
invalid: [
|
|
1908
|
+
{ code: "execSync(`git diff --name-only ${ref}`)", resolution: { action: 'use-argv-array', summary: 'Replace execSync with execFileSync/spawn whose command is a string literal and whose arguments are separate array elements, so no shell interprets them.', symbols: ['execSync'] } },
|
|
1909
|
+
],
|
|
1910
|
+
},
|
|
1911
|
+
},
|
|
1912
|
+
'dynamic-require-of-project-path': {
|
|
1913
|
+
analyzer: 'security',
|
|
1914
|
+
field: 'rule',
|
|
1915
|
+
input: ['files'],
|
|
1916
|
+
resolvable: true,
|
|
1917
|
+
message: 'Dynamic require/import of a project config path: {path}.',
|
|
1918
|
+
docs: 'dynamic-require-of-project-path',
|
|
1919
|
+
thresholds: [],
|
|
1920
|
+
samples: {
|
|
1921
|
+
valid: [
|
|
1922
|
+
{ code: "import('./builtin-helper.js')", nearMiss: true },
|
|
1923
|
+
{ code: "require('tailwindcss')", nearMiss: true },
|
|
1924
|
+
],
|
|
1925
|
+
invalid: [
|
|
1926
|
+
{ code: "require(configPath)", resolution: { action: 'static-config-extraction', summary: 'Read the config without executing it (static extraction) rather than require()/import() a path discovered from the project tree.', symbols: ['require'] } },
|
|
1927
|
+
],
|
|
1928
|
+
},
|
|
1929
|
+
},
|
|
1930
|
+
'unescaped-html-interpolation': {
|
|
1931
|
+
analyzer: 'security',
|
|
1932
|
+
field: 'rule',
|
|
1933
|
+
input: ['files'],
|
|
1934
|
+
resolvable: true,
|
|
1935
|
+
message: 'Unescaped HTML interpolation: {field} is inserted into an HTML template without an escaping call.',
|
|
1936
|
+
docs: 'unescaped-html-interpolation',
|
|
1937
|
+
thresholds: [],
|
|
1938
|
+
samples: {
|
|
1939
|
+
valid: [
|
|
1940
|
+
{
|
|
1941
|
+
// Same sink (`.innerHTML`) + same member-access interpolation, but the
|
|
1942
|
+
// value is escape-wrapped — the fix, not the finding. The old near-miss
|
|
1943
|
+
// (`const html = ...` with no sink) "passed" only because there was no
|
|
1944
|
+
// sink to read.
|
|
1945
|
+
code: 'el.innerHTML = `<p>${escapeHtml(user.name)}</p>`;',
|
|
1946
|
+
nearMiss: true,
|
|
1947
|
+
},
|
|
1948
|
+
],
|
|
1949
|
+
invalid: [
|
|
1950
|
+
{
|
|
1951
|
+
// Needs BOTH a sink (`.innerHTML` assignment) and a member-expression
|
|
1952
|
+
// interpolation (`user.name`, not a bare identifier). The old sample
|
|
1953
|
+
// (`const html = `${userName}``) had neither, so it never emitted.
|
|
1954
|
+
code: 'el.innerHTML = `<p>${user.name}</p>`;',
|
|
1955
|
+
resolution: { action: 'escape-html-interpolation', summary: 'Wrap the interpolation in an escaping call (e.g. ${escapeHtml(user.name)}) before it reaches the HTML template.', symbols: ['user.name'] },
|
|
1956
|
+
},
|
|
1957
|
+
],
|
|
1958
|
+
},
|
|
1959
|
+
},
|
|
2024
1960
|
};
|
|
2025
1961
|
/**
|
|
2026
1962
|
* Canonical set of every analyzer ID that emits at least one rule in
|
|
@@ -2050,7 +1986,25 @@ export const MCP_DEFAULT_ANALYZERS = [
|
|
|
2050
1986
|
'documentation',
|
|
2051
1987
|
'react',
|
|
2052
1988
|
'data-access',
|
|
1989
|
+
'data-access-org-filter',
|
|
2053
1990
|
];
|
|
1991
|
+
/**
|
|
1992
|
+
* Analyzers the pipeline can run that emit *no* registry rule. Their rules come
|
|
1993
|
+
* from a runtime source other than {@link RULE_REGISTRY}: `invariants` reads its
|
|
1994
|
+
* rules from the `.codeauditor.json` `rules` array (the seven invariant rule
|
|
1995
|
+
* kinds), not from registry rows, so it is absent from {@link ALL_ANALYZERS}.
|
|
1996
|
+
*/
|
|
1997
|
+
export const PIPELINE_ONLY_ANALYZERS = ['invariants'];
|
|
1998
|
+
/**
|
|
1999
|
+
* Every analyzer `enabledAnalyzers` may legitimately name — {@link ALL_ANALYZERS}
|
|
2000
|
+
* (registry rule emitters) plus {@link PIPELINE_ONLY_ANALYZERS}. This is the
|
|
2001
|
+
* single source of truth for "can this analyzer actually run?" — used by config
|
|
2002
|
+
* validation, which must not reject `invariants` (a valid pipeline analyzer that
|
|
2003
|
+
* happens to carry no registry rule).
|
|
2004
|
+
*/
|
|
2005
|
+
export const RUNNABLE_ANALYZERS = [
|
|
2006
|
+
...new Set([...ALL_ANALYZERS, ...PIPELINE_ONLY_ANALYZERS]),
|
|
2007
|
+
].sort();
|
|
2054
2008
|
/**
|
|
2055
2009
|
* Resolve a violation to its canonical {@link RuleRegistryEntry}, if any.
|
|
2056
2010
|
*
|