@anatomytool/validate 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +127 -0
- package/dist/canonical.d.ts +21 -0
- package/dist/canonical.d.ts.map +1 -0
- package/dist/canonical.js +76 -0
- package/dist/canonical.js.map +1 -0
- package/dist/checks/commands-no-test-warn.d.ts +6 -0
- package/dist/checks/commands-no-test-warn.d.ts.map +1 -0
- package/dist/checks/commands-no-test-warn.js +35 -0
- package/dist/checks/commands-no-test-warn.js.map +1 -0
- package/dist/checks/description-warn.d.ts +6 -0
- package/dist/checks/description-warn.d.ts.map +1 -0
- package/dist/checks/description-warn.js +23 -0
- package/dist/checks/description-warn.js.map +1 -0
- package/dist/checks/entry-point-alias-warn.d.ts +6 -0
- package/dist/checks/entry-point-alias-warn.d.ts.map +1 -0
- package/dist/checks/entry-point-alias-warn.js +25 -0
- package/dist/checks/entry-point-alias-warn.js.map +1 -0
- package/dist/checks/fingerprint-check.d.ts +6 -0
- package/dist/checks/fingerprint-check.d.ts.map +1 -0
- package/dist/checks/fingerprint-check.js +59 -0
- package/dist/checks/fingerprint-check.js.map +1 -0
- package/dist/checks/hash-check.d.ts +6 -0
- package/dist/checks/hash-check.d.ts.map +1 -0
- package/dist/checks/hash-check.js +36 -0
- package/dist/checks/hash-check.js.map +1 -0
- package/dist/checks/interface-form-check.d.ts +6 -0
- package/dist/checks/interface-form-check.d.ts.map +1 -0
- package/dist/checks/interface-form-check.js +66 -0
- package/dist/checks/interface-form-check.js.map +1 -0
- package/dist/checks/memory-dangling-ref-check.d.ts +6 -0
- package/dist/checks/memory-dangling-ref-check.d.ts.map +1 -0
- package/dist/checks/memory-dangling-ref-check.js +34 -0
- package/dist/checks/memory-dangling-ref-check.js.map +1 -0
- package/dist/checks/memory-fingerprint-check.d.ts +6 -0
- package/dist/checks/memory-fingerprint-check.d.ts.map +1 -0
- package/dist/checks/memory-fingerprint-check.js +20 -0
- package/dist/checks/memory-fingerprint-check.js.map +1 -0
- package/dist/checks/memory-supersession-check.d.ts +6 -0
- package/dist/checks/memory-supersession-check.d.ts.map +1 -0
- package/dist/checks/memory-supersession-check.js +61 -0
- package/dist/checks/memory-supersession-check.js.map +1 -0
- package/dist/checks/memory-verification-check.d.ts +6 -0
- package/dist/checks/memory-verification-check.d.ts.map +1 -0
- package/dist/checks/memory-verification-check.js +61 -0
- package/dist/checks/memory-verification-check.js.map +1 -0
- package/dist/checks/nested-path-escape.d.ts +6 -0
- package/dist/checks/nested-path-escape.d.ts.map +1 -0
- package/dist/checks/nested-path-escape.js +76 -0
- package/dist/checks/nested-path-escape.js.map +1 -0
- package/dist/checks/schema-check.d.ts +6 -0
- package/dist/checks/schema-check.d.ts.map +1 -0
- package/dist/checks/schema-check.js +32 -0
- package/dist/checks/schema-check.js.map +1 -0
- package/dist/checks/source-cross-check.d.ts +52 -0
- package/dist/checks/source-cross-check.d.ts.map +1 -0
- package/dist/checks/source-cross-check.js +473 -0
- package/dist/checks/source-cross-check.js.map +1 -0
- package/dist/checks/source-path-check.d.ts +6 -0
- package/dist/checks/source-path-check.d.ts.map +1 -0
- package/dist/checks/source-path-check.js +62 -0
- package/dist/checks/source-path-check.js.map +1 -0
- package/dist/checks/structure-path-check.d.ts +6 -0
- package/dist/checks/structure-path-check.d.ts.map +1 -0
- package/dist/checks/structure-path-check.js +31 -0
- package/dist/checks/structure-path-check.js.map +1 -0
- package/dist/checks/verify/ast-grep-verifier.d.ts +4 -0
- package/dist/checks/verify/ast-grep-verifier.d.ts.map +1 -0
- package/dist/checks/verify/ast-grep-verifier.js +153 -0
- package/dist/checks/verify/ast-grep-verifier.js.map +1 -0
- package/dist/checks/verify/detect-ast-grep.d.ts +9 -0
- package/dist/checks/verify/detect-ast-grep.d.ts.map +1 -0
- package/dist/checks/verify/detect-ast-grep.js +26 -0
- package/dist/checks/verify/detect-ast-grep.js.map +1 -0
- package/dist/checks/verify/detect-semgrep.d.ts +8 -0
- package/dist/checks/verify/detect-semgrep.d.ts.map +1 -0
- package/dist/checks/verify/detect-semgrep.js +36 -0
- package/dist/checks/verify/detect-semgrep.js.map +1 -0
- package/dist/checks/verify/glob-verifier.d.ts +5 -0
- package/dist/checks/verify/glob-verifier.d.ts.map +1 -0
- package/dist/checks/verify/glob-verifier.js +58 -0
- package/dist/checks/verify/glob-verifier.js.map +1 -0
- package/dist/checks/verify/semgrep-pattern-verifier.d.ts +4 -0
- package/dist/checks/verify/semgrep-pattern-verifier.d.ts.map +1 -0
- package/dist/checks/verify/semgrep-pattern-verifier.js +121 -0
- package/dist/checks/verify/semgrep-pattern-verifier.js.map +1 -0
- package/dist/checks/verify/semgrep-rule-file-verifier.d.ts +9 -0
- package/dist/checks/verify/semgrep-rule-file-verifier.d.ts.map +1 -0
- package/dist/checks/verify/semgrep-rule-file-verifier.js +151 -0
- package/dist/checks/verify/semgrep-rule-file-verifier.js.map +1 -0
- package/dist/checks/verify/types.d.ts +44 -0
- package/dist/checks/verify/types.d.ts.map +1 -0
- package/dist/checks/verify/types.js +6 -0
- package/dist/checks/verify/types.js.map +1 -0
- package/dist/checks/verify-check.d.ts +9 -0
- package/dist/checks/verify-check.d.ts.map +1 -0
- package/dist/checks/verify-check.js +67 -0
- package/dist/checks/verify-check.js.map +1 -0
- package/dist/discovery.d.ts +13 -0
- package/dist/discovery.d.ts.map +1 -0
- package/dist/discovery.js +82 -0
- package/dist/discovery.js.map +1 -0
- package/dist/errors.d.ts +38 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +5 -0
- package/dist/errors.js.map +1 -0
- package/dist/index.d.ts +43 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +81 -0
- package/dist/index.js.map +1 -0
- package/dist/memory.d.ts +19 -0
- package/dist/memory.d.ts.map +1 -0
- package/dist/memory.js +80 -0
- package/dist/memory.js.map +1 -0
- package/dist/parse.d.ts +17 -0
- package/dist/parse.d.ts.map +1 -0
- package/dist/parse.js +47 -0
- package/dist/parse.js.map +1 -0
- package/dist/schema-0.10.json +194 -0
- package/dist/schema-0.11.json +203 -0
- package/dist/schema-0.12.json +243 -0
- package/dist/schema-0.13.json +267 -0
- package/dist/schema-0.14.json +288 -0
- package/dist/schema-0.15.json +347 -0
- package/dist/schema-0.2.json +283 -0
- package/dist/schema-0.4.json +280 -0
- package/dist/schema-0.5.json +300 -0
- package/dist/schema-0.6.json +328 -0
- package/dist/schema-0.7.json +311 -0
- package/dist/schema-0.8.json +245 -0
- package/dist/schema-0.9.json +172 -0
- package/dist/schema-1.0.json +347 -0
- package/dist/schema-memory-0.1.json +43 -0
- package/dist/schema-memory-0.2.json +49 -0
- package/dist/schema-memory.d.ts +5 -0
- package/dist/schema-memory.d.ts.map +1 -0
- package/dist/schema-memory.js +16 -0
- package/dist/schema-memory.js.map +1 -0
- package/dist/schema.d.ts +6 -0
- package/dist/schema.d.ts.map +1 -0
- package/dist/schema.js +62 -0
- package/dist/schema.js.map +1 -0
- package/dist/schema.json +129 -0
- package/dist/types.d.ts +2 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.generated.d.ts +1103 -0
- package/dist/types.generated.d.ts.map +1 -0
- package/dist/types.generated.js +7 -0
- package/dist/types.generated.js.map +1 -0
- package/dist/types.js +7 -0
- package/dist/types.js.map +1 -0
- package/dist/validate-tree.d.ts +20 -0
- package/dist/validate-tree.d.ts.map +1 -0
- package/dist/validate-tree.js +64 -0
- package/dist/validate-tree.js.map +1 -0
- package/package.json +48 -0
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
// src/checks/verify/ast-grep-verifier.ts
|
|
2
|
+
// AST-based verifier for [[rules]].verify with kind="ast_pattern". Uses
|
|
3
|
+
// @ast-grep/napi as an optional dependency loaded via detect-ast-grep.ts.
|
|
4
|
+
// If napi is unavailable (package not installed, native binary missing for
|
|
5
|
+
// this platform), emits verify-ast-grep-unavailable and skips the rule —
|
|
6
|
+
// it does NOT fail validation. Malformed patterns surface as
|
|
7
|
+
// verify-invalid-pattern (author bug, not source drift); parse errors on
|
|
8
|
+
// individual source files are silently skipped (likely binary or wrong lang).
|
|
9
|
+
//
|
|
10
|
+
// API notes for @ast-grep/napi v0.42:
|
|
11
|
+
// - Lang is NOT an enum; language parsers are module-level named exports:
|
|
12
|
+
// m.ts, m.tsx, m.js, m.jsx, m.css, m.html
|
|
13
|
+
// - Python, Rust, Go, Java are NOT built-in; they require separate lang packs
|
|
14
|
+
// - Each lang object has: parse(src), parseAsync(src), findInFiles(config), kind(name), pattern(src)
|
|
15
|
+
// - parse(src) is synchronous and returns an SgRoot
|
|
16
|
+
// - SgRoot has .root() → SgNode
|
|
17
|
+
// - SgNode has .findAll({ rule: { pattern } }) → SgNode[]
|
|
18
|
+
// - SgNode has .range() → { start: { line, column, index }, end: { ... } }
|
|
19
|
+
// - SgNode has .kind() → string; ERROR nodes have kind "ERROR"
|
|
20
|
+
// - Malformed patterns don't throw on findAll — they silently return 0 matches.
|
|
21
|
+
// We detect bad patterns by parsing the pattern string as source and checking
|
|
22
|
+
// for ERROR-kind children in the parse tree.
|
|
23
|
+
import { glob, readFile } from "node:fs/promises";
|
|
24
|
+
import { join, sep as platformSep } from "node:path";
|
|
25
|
+
import { getAstGrep } from "./detect-ast-grep.js";
|
|
26
|
+
const MAX_LISTED_HITS = 5;
|
|
27
|
+
const MAX_FILE_BYTES = 256 * 1024;
|
|
28
|
+
// Map schema lang values to the napi module-level export name.
|
|
29
|
+
// Python, Rust, Go, Java are listed in the schema but lack built-in napi support
|
|
30
|
+
// without additional lang pack configuration; they are handled gracefully below.
|
|
31
|
+
const LANG_EXPORT_MAP = {
|
|
32
|
+
ts: "ts",
|
|
33
|
+
tsx: "tsx",
|
|
34
|
+
js: "js",
|
|
35
|
+
jsx: "jsx",
|
|
36
|
+
py: "py", // Not a built-in napi export; will surface as verify-invalid-pattern
|
|
37
|
+
rs: "rs", // Same
|
|
38
|
+
go: "go", // Same
|
|
39
|
+
java: "java", // Same
|
|
40
|
+
};
|
|
41
|
+
/** Normalize a platform path to POSIX (forward-slash) for display and glob matching. */
|
|
42
|
+
function toPosix(p) {
|
|
43
|
+
return platformSep === "/" ? p : p.split(platformSep).join("/");
|
|
44
|
+
}
|
|
45
|
+
/** Collect all glob matches under repoRoot, returning POSIX-style relative paths. */
|
|
46
|
+
async function collectMatches(repoRoot, pattern) {
|
|
47
|
+
const out = [];
|
|
48
|
+
for await (const entry of glob(pattern, { cwd: repoRoot })) {
|
|
49
|
+
out.push(toPosix(entry));
|
|
50
|
+
}
|
|
51
|
+
return out;
|
|
52
|
+
}
|
|
53
|
+
/** Detect whether a pattern string is syntactically malformed by parsing it as
|
|
54
|
+
* source code and checking for ERROR-kind nodes in the parse tree. */
|
|
55
|
+
function isPatternMalformed(lang, pattern) {
|
|
56
|
+
const root = lang.parse(pattern).root();
|
|
57
|
+
return root.children().some(n => n.kind() === "ERROR");
|
|
58
|
+
}
|
|
59
|
+
export async function verifyAstPattern(repoRoot, cfg, pointer) {
|
|
60
|
+
const sg = await getAstGrep();
|
|
61
|
+
if (!sg) {
|
|
62
|
+
return [{
|
|
63
|
+
code: "verify-ast-grep-unavailable",
|
|
64
|
+
message: `verify rule with kind="ast_pattern" requires @ast-grep/napi. Install it ` +
|
|
65
|
+
`('npm install --save-optional @ast-grep/napi') to enable AST-based ` +
|
|
66
|
+
`verification, or remove the verify field. Rule skipped.`,
|
|
67
|
+
pointer,
|
|
68
|
+
}];
|
|
69
|
+
}
|
|
70
|
+
const exportName = LANG_EXPORT_MAP[cfg.lang];
|
|
71
|
+
// Access the language parser from the module (e.g., sg.ts, sg.tsx, sg.js)
|
|
72
|
+
const sgModule = sg;
|
|
73
|
+
const langParser = sgModule[exportName];
|
|
74
|
+
if (!langParser || typeof langParser.parse !== "function") {
|
|
75
|
+
return [{
|
|
76
|
+
code: "verify-invalid-pattern",
|
|
77
|
+
message: `ast-grep's napi build does not include "${cfg.lang}". ` +
|
|
78
|
+
`For non-JS-family languages, use kind = "semgrep" instead — see ` +
|
|
79
|
+
`spec/0.13/prompt.md. Or use lang = ts/tsx/js/jsx for ast-grep.`,
|
|
80
|
+
pointer,
|
|
81
|
+
}];
|
|
82
|
+
}
|
|
83
|
+
// Validate the pattern before scanning files — detect syntax errors early.
|
|
84
|
+
if (isPatternMalformed(langParser, cfg.pattern)) {
|
|
85
|
+
return [{
|
|
86
|
+
code: "verify-invalid-pattern",
|
|
87
|
+
message: `ast-grep pattern "${cfg.pattern}" is malformed (parse tree contains ERROR nodes for lang=${cfg.lang}).`,
|
|
88
|
+
pointer,
|
|
89
|
+
}];
|
|
90
|
+
}
|
|
91
|
+
const targetGlob = cfg.expect_in ?? cfg.forbid_in;
|
|
92
|
+
if (!targetGlob) {
|
|
93
|
+
// Schema enforces oneOf(expect_in, forbid_in); this is a defensive guard.
|
|
94
|
+
return [];
|
|
95
|
+
}
|
|
96
|
+
const files = await collectMatches(repoRoot, targetGlob);
|
|
97
|
+
const hits = [];
|
|
98
|
+
for (const relPath of files) {
|
|
99
|
+
const absPath = join(repoRoot, relPath);
|
|
100
|
+
let source;
|
|
101
|
+
try {
|
|
102
|
+
const buf = await readFile(absPath);
|
|
103
|
+
if (buf.length > MAX_FILE_BYTES)
|
|
104
|
+
continue;
|
|
105
|
+
source = buf.toString("utf8");
|
|
106
|
+
}
|
|
107
|
+
catch {
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
let sgRoot;
|
|
111
|
+
try {
|
|
112
|
+
sgRoot = langParser.parse(source);
|
|
113
|
+
}
|
|
114
|
+
catch {
|
|
115
|
+
// Parse error on this file — skip silently (binary or wrong language).
|
|
116
|
+
continue;
|
|
117
|
+
}
|
|
118
|
+
let matches;
|
|
119
|
+
try {
|
|
120
|
+
matches = sgRoot.root().findAll({ rule: { pattern: cfg.pattern } });
|
|
121
|
+
}
|
|
122
|
+
catch {
|
|
123
|
+
// Unexpected runtime error from findAll — treat pattern as invalid.
|
|
124
|
+
continue;
|
|
125
|
+
}
|
|
126
|
+
for (const match of matches) {
|
|
127
|
+
hits.push({ file: relPath, line: match.range().start.line + 1 });
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
if (cfg.expect_in !== undefined && hits.length === 0) {
|
|
131
|
+
return [{
|
|
132
|
+
code: "verify-pattern-not-matched",
|
|
133
|
+
message: `ast-grep pattern "${cfg.pattern}" did not match any occurrence ` +
|
|
134
|
+
`in "${targetGlob}" (lang=${cfg.lang}).`,
|
|
135
|
+
pointer,
|
|
136
|
+
}];
|
|
137
|
+
}
|
|
138
|
+
if (cfg.forbid_in !== undefined && hits.length > 0) {
|
|
139
|
+
const shown = hits
|
|
140
|
+
.slice(0, MAX_LISTED_HITS)
|
|
141
|
+
.map(h => `${h.file}:${h.line}`)
|
|
142
|
+
.join(", ");
|
|
143
|
+
const suffix = hits.length > MAX_LISTED_HITS ? ", ..." : "";
|
|
144
|
+
return [{
|
|
145
|
+
code: "verify-pattern-found-where-forbidden",
|
|
146
|
+
message: `ast-grep pattern "${cfg.pattern}" matched ${hits.length} occurrence(s) ` +
|
|
147
|
+
`in forbidden glob "${targetGlob}": ${shown}${suffix}`,
|
|
148
|
+
pointer,
|
|
149
|
+
}];
|
|
150
|
+
}
|
|
151
|
+
return [];
|
|
152
|
+
}
|
|
153
|
+
//# sourceMappingURL=ast-grep-verifier.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ast-grep-verifier.js","sourceRoot":"","sources":["../../../src/checks/verify/ast-grep-verifier.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
type AstGrepModule = typeof import("@ast-grep/napi");
|
|
2
|
+
/** Returns the @ast-grep/napi module, or null if the package is unavailable
|
|
3
|
+
* (not installed, or native binary missing for this platform). Caches the
|
|
4
|
+
* outcome — at most one import attempt per process. */
|
|
5
|
+
export declare function getAstGrep(): Promise<AstGrepModule | null>;
|
|
6
|
+
/** Test-only: clear the cache so a follow-up call retries the import. */
|
|
7
|
+
export declare function _resetAstGrepCache(): void;
|
|
8
|
+
export {};
|
|
9
|
+
//# sourceMappingURL=detect-ast-grep.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"detect-ast-grep.d.ts","sourceRoot":"","sources":["../../../src/checks/verify/detect-ast-grep.ts"],"names":[],"mappings":"AAOA,KAAK,aAAa,GAAG,cAAc,gBAAgB,CAAC,CAAC;AAIrD;;wDAEwD;AACxD,wBAAsB,UAAU,IAAI,OAAO,CAAC,aAAa,GAAG,IAAI,CAAC,CAQhE;AAED,yEAAyE;AACzE,wBAAgB,kBAAkB,IAAI,IAAI,CAEzC"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
// src/checks/verify/detect-ast-grep.ts
|
|
2
|
+
// Lazy loader for the optional @ast-grep/napi dependency. ast-grep is shipped
|
|
3
|
+
// as platform-specific native binaries via optionalDependencies; on unsupported
|
|
4
|
+
// platforms or installations where it failed, the import throws. We catch the
|
|
5
|
+
// throw and surface a null, letting the ast_pattern verifier degrade gracefully
|
|
6
|
+
// with a verify-ast-grep-unavailable warning instead of failing validation.
|
|
7
|
+
let cached;
|
|
8
|
+
/** Returns the @ast-grep/napi module, or null if the package is unavailable
|
|
9
|
+
* (not installed, or native binary missing for this platform). Caches the
|
|
10
|
+
* outcome — at most one import attempt per process. */
|
|
11
|
+
export async function getAstGrep() {
|
|
12
|
+
if (cached !== undefined)
|
|
13
|
+
return cached;
|
|
14
|
+
try {
|
|
15
|
+
cached = await import("@ast-grep/napi");
|
|
16
|
+
}
|
|
17
|
+
catch {
|
|
18
|
+
cached = null;
|
|
19
|
+
}
|
|
20
|
+
return cached;
|
|
21
|
+
}
|
|
22
|
+
/** Test-only: clear the cache so a follow-up call retries the import. */
|
|
23
|
+
export function _resetAstGrepCache() {
|
|
24
|
+
cached = undefined;
|
|
25
|
+
}
|
|
26
|
+
//# sourceMappingURL=detect-ast-grep.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"detect-ast-grep.js","sourceRoot":"","sources":["../../../src/checks/verify/detect-ast-grep.ts"],"names":[],"mappings":"AAAA,uCAAuC;AACvC,8EAA8E;AAC9E,gFAAgF;AAChF,8EAA8E;AAC9E,gFAAgF;AAChF,4EAA4E;AAI5E,IAAI,MAAwC,CAAC;AAE7C;;wDAEwD;AACxD,MAAM,CAAC,KAAK,UAAU,UAAU;IAC9B,IAAI,MAAM,KAAK,SAAS;QAAE,OAAO,MAAM,CAAC;IACxC,IAAI,CAAC;QACH,MAAM,GAAG,MAAM,MAAM,CAAC,gBAAgB,CAAC,CAAC;IAC1C,CAAC;IAAC,MAAM,CAAC;QACP,MAAM,GAAG,IAAI,CAAC;IAChB,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,yEAAyE;AACzE,MAAM,UAAU,kBAAkB;IAChC,MAAM,GAAG,SAAS,CAAC;AACrB,CAAC"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export interface SemgrepDetection {
|
|
2
|
+
available: boolean;
|
|
3
|
+
version?: string;
|
|
4
|
+
}
|
|
5
|
+
export declare function getSemgrep(): SemgrepDetection;
|
|
6
|
+
/** Test-only: reset the module-level cache so each test sees a clean state. */
|
|
7
|
+
export declare function _resetSemgrepCache(): void;
|
|
8
|
+
//# sourceMappingURL=detect-semgrep.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"detect-semgrep.d.ts","sourceRoot":"","sources":["../../../src/checks/verify/detect-semgrep.ts"],"names":[],"mappings":"AAUA,MAAM,WAAW,gBAAgB;IAC/B,SAAS,EAAE,OAAO,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAID,wBAAgB,UAAU,IAAI,gBAAgB,CAqB7C;AAED,+EAA+E;AAC/E,wBAAgB,kBAAkB,IAAI,IAAI,CAEzC"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
// src/checks/verify/detect-semgrep.ts
|
|
2
|
+
// Cached one-shot detection of the `semgrep` binary on PATH. Used by both
|
|
3
|
+
// semgrep-pattern-verifier and semgrep-rule-file-verifier to decide whether
|
|
4
|
+
// to invoke Semgrep or emit verify-semgrep-unavailable.
|
|
5
|
+
//
|
|
6
|
+
// shell:true is required for .cmd shim resolution on Windows — npm/pip-installed
|
|
7
|
+
// CLIs don't resolve as plain executables on Windows without it.
|
|
8
|
+
import { spawnSync } from "node:child_process";
|
|
9
|
+
let cached;
|
|
10
|
+
export function getSemgrep() {
|
|
11
|
+
if (cached !== undefined)
|
|
12
|
+
return cached;
|
|
13
|
+
try {
|
|
14
|
+
const result = spawnSync("semgrep", ["--version"], {
|
|
15
|
+
shell: true,
|
|
16
|
+
encoding: "buffer",
|
|
17
|
+
timeout: 10_000,
|
|
18
|
+
});
|
|
19
|
+
if (result.status === 0 && !result.error) {
|
|
20
|
+
const version = result.stdout.toString("utf8").trim();
|
|
21
|
+
cached = { available: true, version };
|
|
22
|
+
}
|
|
23
|
+
else {
|
|
24
|
+
cached = { available: false };
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
cached = { available: false };
|
|
29
|
+
}
|
|
30
|
+
return cached;
|
|
31
|
+
}
|
|
32
|
+
/** Test-only: reset the module-level cache so each test sees a clean state. */
|
|
33
|
+
export function _resetSemgrepCache() {
|
|
34
|
+
cached = undefined;
|
|
35
|
+
}
|
|
36
|
+
//# sourceMappingURL=detect-semgrep.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"detect-semgrep.js","sourceRoot":"","sources":["../../../src/checks/verify/detect-semgrep.ts"],"names":[],"mappings":"AAAA,sCAAsC;AACtC,0EAA0E;AAC1E,4EAA4E;AAC5E,wDAAwD;AACxD,EAAE;AACF,iFAAiF;AACjF,iEAAiE;AAEjE,OAAO,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAO/C,IAAI,MAAoC,CAAC;AAEzC,MAAM,UAAU,UAAU;IACxB,IAAI,MAAM,KAAK,SAAS;QAAE,OAAO,MAAM,CAAC;IAExC,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,SAAS,CAAC,SAAS,EAAE,CAAC,WAAW,CAAC,EAAE;YACjD,KAAK,EAAE,IAAI;YACX,QAAQ,EAAE,QAAQ;YAClB,OAAO,EAAE,MAAM;SAChB,CAAC,CAAC;QAEH,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC;YACzC,MAAM,OAAO,GAAG,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,CAAC;YACtD,MAAM,GAAG,EAAE,SAAS,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC;QACxC,CAAC;aAAM,CAAC;YACN,MAAM,GAAG,EAAE,SAAS,EAAE,KAAK,EAAE,CAAC;QAChC,CAAC;IACH,CAAC;IAAC,MAAM,CAAC;QACP,MAAM,GAAG,EAAE,SAAS,EAAE,KAAK,EAAE,CAAC;IAChC,CAAC;IAED,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,+EAA+E;AAC/E,MAAM,UAAU,kBAAkB;IAChC,MAAM,GAAG,SAAS,CAAC;AACrB,CAAC"}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { Warning } from "../../errors.js";
|
|
2
|
+
import type { GlobExistsConfig, GlobOnlyConfig } from "./types.js";
|
|
3
|
+
export declare function verifyGlobExists(repoRoot: string, cfg: GlobExistsConfig, pointer: string): Promise<Warning[]>;
|
|
4
|
+
export declare function verifyGlobOnly(repoRoot: string, cfg: GlobOnlyConfig, pointer: string): Promise<Warning[]>;
|
|
5
|
+
//# sourceMappingURL=glob-verifier.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"glob-verifier.d.ts","sourceRoot":"","sources":["../../../src/checks/verify/glob-verifier.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,iBAAiB,CAAC;AAC/C,OAAO,KAAK,EAAE,gBAAgB,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAkBnE,wBAAsB,gBAAgB,CACpC,QAAQ,EAAE,MAAM,EAChB,GAAG,EAAE,gBAAgB,EACrB,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,OAAO,EAAE,CAAC,CAqBpB;AAED,wBAAsB,cAAc,CAClC,QAAQ,EAAE,MAAM,EAChB,GAAG,EAAE,cAAc,EACnB,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,OAAO,EAAE,CAAC,CAYpB"}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
// src/checks/verify/glob-verifier.ts
|
|
2
|
+
// Pure glob-based verifiers (no native deps). Uses Node 22+'s built-in
|
|
3
|
+
// `glob` from node:fs/promises. All globs are repo-root-relative; results
|
|
4
|
+
// are joined with the repo root for human-readable warning messages.
|
|
5
|
+
// Windows path separators are normalized to forward slash at the boundary.
|
|
6
|
+
import { glob } from "node:fs/promises";
|
|
7
|
+
import { sep as platformSep } from "node:path";
|
|
8
|
+
const MAX_LISTED_FILES = 5;
|
|
9
|
+
/** Normalize platform path to POSIX (forward slash) for stable display + glob matching. */
|
|
10
|
+
function toPosix(p) {
|
|
11
|
+
return platformSep === "/" ? p : p.split(platformSep).join("/");
|
|
12
|
+
}
|
|
13
|
+
/** Collect all matches for a glob under repoRoot, returning POSIX-style relative paths. */
|
|
14
|
+
async function collectMatches(repoRoot, pattern) {
|
|
15
|
+
const out = [];
|
|
16
|
+
for await (const entry of glob(pattern, { cwd: repoRoot })) {
|
|
17
|
+
out.push(toPosix(entry));
|
|
18
|
+
}
|
|
19
|
+
return out;
|
|
20
|
+
}
|
|
21
|
+
export async function verifyGlobExists(repoRoot, cfg, pointer) {
|
|
22
|
+
const matches = await collectMatches(repoRoot, cfg.path);
|
|
23
|
+
if (cfg.should_not === true) {
|
|
24
|
+
if (matches.length > 0) {
|
|
25
|
+
const shown = matches.slice(0, MAX_LISTED_FILES);
|
|
26
|
+
return [{
|
|
27
|
+
code: "verify-glob-unexpected-files",
|
|
28
|
+
message: `glob "${cfg.path}" should not match any files, but matched ${matches.length}: ${shown.join(", ")}${matches.length > MAX_LISTED_FILES ? ", ..." : ""}`,
|
|
29
|
+
pointer,
|
|
30
|
+
}];
|
|
31
|
+
}
|
|
32
|
+
return [];
|
|
33
|
+
}
|
|
34
|
+
if (matches.length === 0) {
|
|
35
|
+
return [{
|
|
36
|
+
code: "verify-glob-empty",
|
|
37
|
+
message: `glob "${cfg.path}" matched no files (expected ≥1).`,
|
|
38
|
+
pointer,
|
|
39
|
+
}];
|
|
40
|
+
}
|
|
41
|
+
return [];
|
|
42
|
+
}
|
|
43
|
+
export async function verifyGlobOnly(repoRoot, cfg, pointer) {
|
|
44
|
+
const matchFiles = await collectMatches(repoRoot, cfg.match);
|
|
45
|
+
if (matchFiles.length === 0)
|
|
46
|
+
return [];
|
|
47
|
+
const containerFiles = new Set(await collectMatches(repoRoot, cfg.container));
|
|
48
|
+
const violations = matchFiles.filter(f => !containerFiles.has(f));
|
|
49
|
+
if (violations.length === 0)
|
|
50
|
+
return [];
|
|
51
|
+
const shown = violations.slice(0, MAX_LISTED_FILES);
|
|
52
|
+
return [{
|
|
53
|
+
code: "verify-glob-outside-container",
|
|
54
|
+
message: `glob "${cfg.match}" matched ${violations.length} file(s) outside container "${cfg.container}": ${shown.join(", ")}${violations.length > MAX_LISTED_FILES ? ", ..." : ""}`,
|
|
55
|
+
pointer,
|
|
56
|
+
}];
|
|
57
|
+
}
|
|
58
|
+
//# sourceMappingURL=glob-verifier.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"glob-verifier.js","sourceRoot":"","sources":["../../../src/checks/verify/glob-verifier.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { Warning } from "../../errors.js";
|
|
2
|
+
import type { SemgrepPatternConfig } from "./types.js";
|
|
3
|
+
export declare function verifySemgrepPattern(repoRoot: string, cfg: SemgrepPatternConfig, pointer: string): Promise<Warning[]>;
|
|
4
|
+
//# sourceMappingURL=semgrep-pattern-verifier.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"semgrep-pattern-verifier.d.ts","sourceRoot":"","sources":["../../../src/checks/verify/semgrep-pattern-verifier.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,iBAAiB,CAAC;AAC/C,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAC;AA+BvD,wBAAsB,oBAAoB,CACxC,QAAQ,EAAE,MAAM,EAChB,GAAG,EAAE,oBAAoB,EACzB,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,OAAO,EAAE,CAAC,CA0GpB"}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
// src/checks/verify/semgrep-pattern-verifier.ts
|
|
2
|
+
// Inline-pattern verifier for [[rules]].verify with kind="semgrep" and pattern
|
|
3
|
+
// + lang fields. Mirrors ast-grep-verifier shape but shells out to the optional
|
|
4
|
+
// `semgrep` binary on PATH. Soft-fail policy: missing binary, timeout, invalid
|
|
5
|
+
// JSON, or unrecognized non-zero exit all surface as verify-semgrep-unavailable
|
|
6
|
+
// warnings — never throws.
|
|
7
|
+
import { spawnSync } from "node:child_process";
|
|
8
|
+
import { glob } from "node:fs/promises";
|
|
9
|
+
import { join, sep as platformSep } from "node:path";
|
|
10
|
+
import { getSemgrep } from "./detect-semgrep.js";
|
|
11
|
+
const MAX_LISTED_HITS = 5;
|
|
12
|
+
const TIMEOUT_MS = 60_000;
|
|
13
|
+
const MAX_BUFFER_BYTES = 10 * 1024 * 1024;
|
|
14
|
+
const PATTERN_ERROR_SIGNALS = [
|
|
15
|
+
"Invalid pattern",
|
|
16
|
+
"Syntax error",
|
|
17
|
+
"Pattern error",
|
|
18
|
+
];
|
|
19
|
+
function toPosix(p) {
|
|
20
|
+
return platformSep === "/" ? p : p.split(platformSep).join("/");
|
|
21
|
+
}
|
|
22
|
+
async function collectMatches(repoRoot, pattern) {
|
|
23
|
+
const out = [];
|
|
24
|
+
for await (const entry of glob(pattern, { cwd: repoRoot })) {
|
|
25
|
+
out.push(toPosix(entry));
|
|
26
|
+
}
|
|
27
|
+
return out;
|
|
28
|
+
}
|
|
29
|
+
export async function verifySemgrepPattern(repoRoot, cfg, pointer) {
|
|
30
|
+
const detection = getSemgrep();
|
|
31
|
+
if (!detection.available) {
|
|
32
|
+
return [{
|
|
33
|
+
code: "verify-semgrep-unavailable",
|
|
34
|
+
message: `verify rule with kind="semgrep" requires the semgrep binary on PATH. ` +
|
|
35
|
+
`Install with 'pip install semgrep' or 'brew install semgrep'. Rule skipped.`,
|
|
36
|
+
pointer,
|
|
37
|
+
}];
|
|
38
|
+
}
|
|
39
|
+
const targetGlob = cfg.expect_in ?? cfg.forbid_in;
|
|
40
|
+
if (!targetGlob)
|
|
41
|
+
return []; // Schema enforces oneOf — defensive guard.
|
|
42
|
+
const files = await collectMatches(repoRoot, targetGlob);
|
|
43
|
+
if (files.length === 0) {
|
|
44
|
+
return [{
|
|
45
|
+
code: "verify-no-files-matched",
|
|
46
|
+
message: `Glob "${targetGlob}" matched 0 files; semgrep verify skipped.`,
|
|
47
|
+
pointer,
|
|
48
|
+
}];
|
|
49
|
+
}
|
|
50
|
+
const absFiles = files.map(f => join(repoRoot, f));
|
|
51
|
+
let result;
|
|
52
|
+
try {
|
|
53
|
+
result = spawnSync("semgrep", ["--json", "--lang", cfg.lang, "--pattern", cfg.pattern, ...absFiles], {
|
|
54
|
+
shell: true,
|
|
55
|
+
encoding: "buffer",
|
|
56
|
+
timeout: TIMEOUT_MS,
|
|
57
|
+
maxBuffer: MAX_BUFFER_BYTES,
|
|
58
|
+
cwd: repoRoot,
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
catch (err) {
|
|
62
|
+
return [{
|
|
63
|
+
code: "verify-semgrep-unavailable",
|
|
64
|
+
message: `semgrep spawn failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
65
|
+
pointer,
|
|
66
|
+
}];
|
|
67
|
+
}
|
|
68
|
+
if (result.error || result.signal === "SIGTERM") {
|
|
69
|
+
return [{
|
|
70
|
+
code: "verify-semgrep-unavailable",
|
|
71
|
+
message: `semgrep invocation failed: ${result.error?.message ?? "timeout after 60s"}`,
|
|
72
|
+
pointer,
|
|
73
|
+
}];
|
|
74
|
+
}
|
|
75
|
+
const stderr = result.stderr.toString("utf8");
|
|
76
|
+
if (result.status !== 0) {
|
|
77
|
+
const parseErrLine = stderr.split("\n").find(l => PATTERN_ERROR_SIGNALS.some(s => l.includes(s)));
|
|
78
|
+
if (parseErrLine) {
|
|
79
|
+
return [{
|
|
80
|
+
code: "verify-invalid-pattern",
|
|
81
|
+
message: `semgrep rejected pattern: ${parseErrLine.trim()}`,
|
|
82
|
+
pointer,
|
|
83
|
+
}];
|
|
84
|
+
}
|
|
85
|
+
return [{
|
|
86
|
+
code: "verify-semgrep-unavailable",
|
|
87
|
+
message: `semgrep exited with status ${result.status}. stderr: ${stderr.slice(0, 200)}`,
|
|
88
|
+
pointer,
|
|
89
|
+
}];
|
|
90
|
+
}
|
|
91
|
+
let hits;
|
|
92
|
+
try {
|
|
93
|
+
const parsed = JSON.parse(result.stdout.toString("utf8"));
|
|
94
|
+
hits = parsed.results ?? [];
|
|
95
|
+
}
|
|
96
|
+
catch {
|
|
97
|
+
return [{
|
|
98
|
+
code: "verify-semgrep-unavailable",
|
|
99
|
+
message: `semgrep stdout was not valid JSON. stderr: ${stderr.slice(0, 200)}`,
|
|
100
|
+
pointer,
|
|
101
|
+
}];
|
|
102
|
+
}
|
|
103
|
+
if (cfg.expect_in !== undefined && hits.length === 0) {
|
|
104
|
+
return [{
|
|
105
|
+
code: "verify-pattern-not-matched",
|
|
106
|
+
message: `semgrep pattern "${cfg.pattern}" did not match any occurrence ` +
|
|
107
|
+
`in "${targetGlob}" (lang=${cfg.lang}).`,
|
|
108
|
+
pointer,
|
|
109
|
+
}];
|
|
110
|
+
}
|
|
111
|
+
if (cfg.forbid_in !== undefined && hits.length > 0) {
|
|
112
|
+
return hits.slice(0, MAX_LISTED_HITS).map(h => ({
|
|
113
|
+
code: "verify-pattern-found-where-forbidden",
|
|
114
|
+
message: `semgrep pattern "${cfg.pattern}" matched at ${h.path}:${h.start.line} ` +
|
|
115
|
+
`in forbidden glob "${cfg.forbid_in}".`,
|
|
116
|
+
pointer,
|
|
117
|
+
}));
|
|
118
|
+
}
|
|
119
|
+
return [];
|
|
120
|
+
}
|
|
121
|
+
//# sourceMappingURL=semgrep-pattern-verifier.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"semgrep-pattern-verifier.js","sourceRoot":"","sources":["../../../src/checks/verify/semgrep-pattern-verifier.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { Warning, ValidationError } from "../../errors.js";
|
|
2
|
+
import type { SemgrepRuleFileConfig } from "./types.js";
|
|
3
|
+
interface VerifyResult {
|
|
4
|
+
errors: ValidationError[];
|
|
5
|
+
warnings: Warning[];
|
|
6
|
+
}
|
|
7
|
+
export declare function verifySemgrepRuleFile(repoRoot: string, cfg: SemgrepRuleFileConfig, pointer: string): Promise<VerifyResult>;
|
|
8
|
+
export {};
|
|
9
|
+
//# sourceMappingURL=semgrep-rule-file-verifier.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"semgrep-rule-file-verifier.d.ts","sourceRoot":"","sources":["../../../src/checks/verify/semgrep-rule-file-verifier.ts"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAE,OAAO,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAChE,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAC;AAgCxD,UAAU,YAAY;IACpB,MAAM,EAAE,eAAe,EAAE,CAAC;IAC1B,QAAQ,EAAE,OAAO,EAAE,CAAC;CACrB;AAOD,wBAAsB,qBAAqB,CACzC,QAAQ,EAAE,MAAM,EAChB,GAAG,EAAE,qBAAqB,EAC1B,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,YAAY,CAAC,CAmIvB"}
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
// src/checks/verify/semgrep-rule-file-verifier.ts
|
|
2
|
+
// YAML-rule-file verifier for [[rules]].verify with kind="semgrep" and rule_file
|
|
3
|
+
// field. Resolves the rule file relative to repo root, refuses path-escape
|
|
4
|
+
// attempts (emits verify-rule-file-outside-repo as ERROR — the only verify code
|
|
5
|
+
// that is not a warning), then shells out to `semgrep --config <path>`.
|
|
6
|
+
//
|
|
7
|
+
// Returns { errors, warnings } separately so the path-escape ERROR can flow
|
|
8
|
+
// into the dispatcher's errors[] slot. All other verifiers return Warning[]
|
|
9
|
+
// directly because they only ever produce warnings.
|
|
10
|
+
import { spawnSync } from "node:child_process";
|
|
11
|
+
import { glob, access } from "node:fs/promises";
|
|
12
|
+
import { resolve, join, sep as platformSep } from "node:path";
|
|
13
|
+
import { getSemgrep } from "./detect-semgrep.js";
|
|
14
|
+
const MAX_LISTED_HITS = 5;
|
|
15
|
+
const TIMEOUT_MS = 60_000;
|
|
16
|
+
const MAX_BUFFER_BYTES = 10 * 1024 * 1024;
|
|
17
|
+
const RULE_FILE_ERROR_SIGNALS = [
|
|
18
|
+
"Invalid rule",
|
|
19
|
+
"Failed to parse rule",
|
|
20
|
+
"Syntax error in rule",
|
|
21
|
+
"missing required field",
|
|
22
|
+
];
|
|
23
|
+
function toPosix(p) {
|
|
24
|
+
return platformSep === "/" ? p : p.split(platformSep).join("/");
|
|
25
|
+
}
|
|
26
|
+
async function collectMatches(repoRoot, pattern) {
|
|
27
|
+
const out = [];
|
|
28
|
+
for await (const entry of glob(pattern, { cwd: repoRoot })) {
|
|
29
|
+
out.push(toPosix(entry));
|
|
30
|
+
}
|
|
31
|
+
return out;
|
|
32
|
+
}
|
|
33
|
+
const warning = (w) => ({ errors: [], warnings: [w] });
|
|
34
|
+
const warnings = (ws) => ({ errors: [], warnings: ws });
|
|
35
|
+
const error = (e) => ({ errors: [e], warnings: [] });
|
|
36
|
+
const empty = () => ({ errors: [], warnings: [] });
|
|
37
|
+
export async function verifySemgrepRuleFile(repoRoot, cfg, pointer) {
|
|
38
|
+
const detection = getSemgrep();
|
|
39
|
+
if (!detection.available) {
|
|
40
|
+
return warning({
|
|
41
|
+
code: "verify-semgrep-unavailable",
|
|
42
|
+
message: `verify rule with kind="semgrep" requires the semgrep binary on PATH. ` +
|
|
43
|
+
`Install with 'pip install semgrep' or 'brew install semgrep'. Rule skipped.`,
|
|
44
|
+
pointer,
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
// Path-escape check FIRST — refuse to even check existence outside repo.
|
|
48
|
+
const resolvedRule = resolve(repoRoot, cfg.rule_file);
|
|
49
|
+
const repoRootWithSep = repoRoot.endsWith(platformSep) ? repoRoot : repoRoot + platformSep;
|
|
50
|
+
if (!resolvedRule.startsWith(repoRootWithSep) && resolvedRule !== repoRoot) {
|
|
51
|
+
return error({
|
|
52
|
+
code: "verify-rule-file-outside-repo",
|
|
53
|
+
message: `verify rule_file "${cfg.rule_file}" resolves outside the repo root ` +
|
|
54
|
+
`(${resolvedRule}). Refusing to invoke semgrep with a non-repo rule file.`,
|
|
55
|
+
pointer,
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
// Existence check.
|
|
59
|
+
try {
|
|
60
|
+
await access(resolvedRule);
|
|
61
|
+
}
|
|
62
|
+
catch {
|
|
63
|
+
return warning({
|
|
64
|
+
code: "verify-rule-file-missing",
|
|
65
|
+
message: `verify rule_file "${cfg.rule_file}" does not exist or is not readable at ${resolvedRule}.`,
|
|
66
|
+
pointer,
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
const targetGlob = cfg.expect_in ?? cfg.forbid_in;
|
|
70
|
+
if (!targetGlob)
|
|
71
|
+
return empty();
|
|
72
|
+
const files = await collectMatches(repoRoot, targetGlob);
|
|
73
|
+
if (files.length === 0) {
|
|
74
|
+
return warning({
|
|
75
|
+
code: "verify-no-files-matched",
|
|
76
|
+
message: `Glob "${targetGlob}" matched 0 files; semgrep verify skipped.`,
|
|
77
|
+
pointer,
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
const absFiles = files.map(f => join(repoRoot, f));
|
|
81
|
+
let result;
|
|
82
|
+
try {
|
|
83
|
+
result = spawnSync("semgrep", ["--json", "--config", resolvedRule, ...absFiles], {
|
|
84
|
+
shell: true,
|
|
85
|
+
encoding: "buffer",
|
|
86
|
+
timeout: TIMEOUT_MS,
|
|
87
|
+
maxBuffer: MAX_BUFFER_BYTES,
|
|
88
|
+
cwd: repoRoot,
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
catch (err) {
|
|
92
|
+
return warning({
|
|
93
|
+
code: "verify-semgrep-unavailable",
|
|
94
|
+
message: `semgrep spawn failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
95
|
+
pointer,
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
if (result.error || result.signal === "SIGTERM") {
|
|
99
|
+
return warning({
|
|
100
|
+
code: "verify-semgrep-unavailable",
|
|
101
|
+
message: `semgrep invocation failed: ${result.error?.message ?? "timeout after 60s"}`,
|
|
102
|
+
pointer,
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
const stderr = result.stderr.toString("utf8");
|
|
106
|
+
if (result.status !== 0) {
|
|
107
|
+
const ruleErrLine = stderr.split("\n").find(l => RULE_FILE_ERROR_SIGNALS.some(s => l.includes(s)));
|
|
108
|
+
if (ruleErrLine) {
|
|
109
|
+
return warning({
|
|
110
|
+
code: "verify-invalid-rule-file",
|
|
111
|
+
message: `semgrep rejected rule_file "${cfg.rule_file}": ${ruleErrLine.trim()}`,
|
|
112
|
+
pointer,
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
return warning({
|
|
116
|
+
code: "verify-semgrep-unavailable",
|
|
117
|
+
message: `semgrep exited with status ${result.status}. stderr: ${stderr.slice(0, 200)}`,
|
|
118
|
+
pointer,
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
let hits;
|
|
122
|
+
try {
|
|
123
|
+
const parsed = JSON.parse(result.stdout.toString("utf8"));
|
|
124
|
+
hits = parsed.results ?? [];
|
|
125
|
+
}
|
|
126
|
+
catch {
|
|
127
|
+
return warning({
|
|
128
|
+
code: "verify-semgrep-unavailable",
|
|
129
|
+
message: `semgrep stdout was not valid JSON. stderr: ${stderr.slice(0, 200)}`,
|
|
130
|
+
pointer,
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
if (cfg.expect_in !== undefined && hits.length === 0) {
|
|
134
|
+
return warning({
|
|
135
|
+
code: "verify-pattern-not-matched",
|
|
136
|
+
message: `semgrep rule_file "${cfg.rule_file}" did not match any occurrence ` +
|
|
137
|
+
`in "${targetGlob}".`,
|
|
138
|
+
pointer,
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
if (cfg.forbid_in !== undefined && hits.length > 0) {
|
|
142
|
+
return warnings(hits.slice(0, MAX_LISTED_HITS).map(h => ({
|
|
143
|
+
code: "verify-pattern-found-where-forbidden",
|
|
144
|
+
message: `semgrep rule_file "${cfg.rule_file}" matched at ${h.path}:${h.start.line} ` +
|
|
145
|
+
`in forbidden glob "${cfg.forbid_in}".`,
|
|
146
|
+
pointer,
|
|
147
|
+
})));
|
|
148
|
+
}
|
|
149
|
+
return empty();
|
|
150
|
+
}
|
|
151
|
+
//# sourceMappingURL=semgrep-rule-file-verifier.js.map
|