pi-lens 3.8.21 → 3.8.23
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/CHANGELOG.md +28 -0
- package/README.md +2 -0
- package/clients/dispatch/dispatcher.ts +75 -91
- package/clients/dispatch/fact-provider-types.ts +22 -0
- package/clients/dispatch/fact-rule-runner.ts +22 -0
- package/clients/dispatch/fact-runner.ts +28 -0
- package/clients/dispatch/fact-scheduler.ts +78 -0
- package/clients/dispatch/fact-store.ts +67 -0
- package/clients/dispatch/facts/comment-facts.ts +59 -0
- package/clients/dispatch/facts/file-content.ts +20 -0
- package/clients/dispatch/facts/function-facts.ts +177 -0
- package/clients/dispatch/facts/try-catch-facts.ts +80 -0
- package/clients/dispatch/integration.ts +130 -24
- package/clients/dispatch/priorities.ts +22 -0
- package/clients/dispatch/rules/async-noise.ts +43 -0
- package/clients/dispatch/rules/error-obscuring.ts +40 -0
- package/clients/dispatch/rules/error-swallowing.ts +35 -0
- package/clients/dispatch/rules/pass-through-wrappers.ts +52 -0
- package/clients/dispatch/rules/placeholder-comments.ts +47 -0
- package/clients/dispatch/runners/architect.ts +2 -1
- package/clients/dispatch/runners/ast-grep-napi.ts +2 -1
- package/clients/dispatch/runners/biome-check.ts +40 -8
- package/clients/dispatch/runners/biome.ts +2 -1
- package/clients/dispatch/runners/eslint.ts +34 -6
- package/clients/dispatch/runners/go-vet.ts +2 -1
- package/clients/dispatch/runners/golangci-lint.ts +2 -1
- package/clients/dispatch/runners/index.ts +29 -27
- package/clients/dispatch/runners/lsp.ts +60 -4
- package/clients/dispatch/runners/oxlint.ts +2 -1
- package/clients/dispatch/runners/pyright.ts +2 -1
- package/clients/dispatch/runners/python-slop.ts +2 -1
- package/clients/dispatch/runners/rubocop.ts +2 -1
- package/clients/dispatch/runners/ruff.ts +2 -1
- package/clients/dispatch/runners/rust-clippy.ts +2 -1
- package/clients/dispatch/runners/shellcheck.ts +2 -1
- package/clients/dispatch/runners/similarity.ts +2 -1
- package/clients/dispatch/runners/spellcheck.ts +2 -1
- package/clients/dispatch/runners/sqlfluff.ts +2 -1
- package/clients/dispatch/runners/tree-sitter.ts +469 -1
- package/clients/dispatch/runners/ts-lsp.ts +2 -1
- package/clients/dispatch/runners/type-safety.ts +2 -1
- package/clients/dispatch/runners/yamllint.ts +2 -1
- package/clients/dispatch/tool-profile.ts +40 -0
- package/clients/dispatch/types.ts +3 -13
- package/clients/lsp/client.ts +366 -12
- package/clients/lsp/index.ts +374 -76
- package/clients/lsp/launch.ts +42 -2
- package/clients/lsp/server.ts +186 -12
- package/clients/pipeline.ts +2 -2
- package/clients/runtime-context.ts +2 -2
- package/clients/runtime-session.ts +43 -5
- package/clients/session-summary.ts +21 -0
- package/clients/tree-sitter-client.ts +162 -0
- package/clients/tree-sitter-logger.ts +47 -0
- package/clients/tree-sitter-query-loader.ts +13 -2
- package/index.ts +67 -17
- package/package.json +3 -1
- package/rules/rule-catalog.json +64 -0
- package/rules/tree-sitter-queries/go/go-bare-error.yml +19 -7
- package/rules/tree-sitter-queries/go/go-command-injection.yml +55 -0
- package/rules/tree-sitter-queries/go/go-direct-panic.yml +45 -0
- package/rules/tree-sitter-queries/go/go-empty-if-err.yml +47 -0
- package/rules/tree-sitter-queries/go/go-goroutine-loop-capture.yml +49 -0
- package/rules/tree-sitter-queries/go/go-ignored-call-result.yml +51 -0
- package/rules/tree-sitter-queries/go/go-insecure-random.yml +51 -0
- package/rules/tree-sitter-queries/go/go-log-fatal.yml +49 -0
- package/rules/tree-sitter-queries/go/go-path-traversal.yml +51 -0
- package/rules/tree-sitter-queries/go/go-shared-map-write-goroutine.yml +54 -0
- package/rules/tree-sitter-queries/go/go-sql-injection.yml +55 -0
- package/rules/tree-sitter-queries/go/go-weak-hash.yml +51 -0
- package/rules/tree-sitter-queries/python/python-command-injection.yml +63 -0
- package/rules/tree-sitter-queries/python/python-insecure-deserialization.yml +48 -0
- package/rules/tree-sitter-queries/python/python-insecure-random.yml +51 -0
- package/rules/tree-sitter-queries/python/python-path-traversal.yml +55 -0
- package/rules/tree-sitter-queries/python/python-sql-injection.yml +47 -0
- package/rules/tree-sitter-queries/python/python-ssrf.yml +50 -0
- package/rules/tree-sitter-queries/python/python-thread-global-write.yml +58 -0
- package/rules/tree-sitter-queries/python/python-weak-hash.yml +51 -0
- package/rules/tree-sitter-queries/ruby/ruby-command-injection.yml +56 -0
- package/rules/tree-sitter-queries/ruby/ruby-insecure-deserialization.yml +47 -0
- package/rules/tree-sitter-queries/ruby/ruby-insecure-random.yml +54 -0
- package/rules/tree-sitter-queries/ruby/ruby-weak-hash.yml +50 -0
- package/rules/tree-sitter-queries/rust/rust-lock-held-across-await.yml +59 -0
- package/rules/tree-sitter-queries/typescript/ts-command-injection.yml +60 -0
- package/rules/tree-sitter-queries/typescript/ts-detached-async-call.yml +56 -0
- package/rules/tree-sitter-queries/typescript/ts-insecure-random.yml +54 -0
- package/rules/tree-sitter-queries/typescript/ts-ssrf.yml +53 -0
- package/rules/tree-sitter-queries/typescript/ts-weak-hash.yml +54 -0
- package/scripts/validate-rule-catalog.mjs +227 -0
- package/skills/lsp-navigation/SKILL.md +15 -3
- package/tools/lsp-navigation.js +466 -79
- package/tools/lsp-navigation.ts +587 -85
|
@@ -32,6 +32,9 @@ export interface TreeSitterQuery {
|
|
|
32
32
|
value: string | string[];
|
|
33
33
|
}>;
|
|
34
34
|
tags?: string[];
|
|
35
|
+
cwe?: string[];
|
|
36
|
+
owasp?: string[];
|
|
37
|
+
confidence?: "low" | "medium" | "high";
|
|
35
38
|
defect_class?: string;
|
|
36
39
|
inline_tier?: "blocking" | "warning" | "review";
|
|
37
40
|
has_fix: boolean;
|
|
@@ -172,6 +175,13 @@ export class TreeSitterQueryLoader {
|
|
|
172
175
|
}))
|
|
173
176
|
: undefined,
|
|
174
177
|
tags: Array.isArray(parsed.tags) ? parsed.tags.map(String) : undefined,
|
|
178
|
+
cwe: Array.isArray(parsed.cwe) ? parsed.cwe.map(String) : undefined,
|
|
179
|
+
owasp: Array.isArray(parsed.owasp)
|
|
180
|
+
? parsed.owasp.map(String)
|
|
181
|
+
: undefined,
|
|
182
|
+
confidence: parsed.confidence
|
|
183
|
+
? (String(parsed.confidence) as "low" | "medium" | "high")
|
|
184
|
+
: undefined,
|
|
175
185
|
has_fix: parsed.has_fix === true || parsed.has_fix === "true",
|
|
176
186
|
fix_action: parsed.fix_action ? String(parsed.fix_action) : undefined,
|
|
177
187
|
filePath,
|
|
@@ -298,8 +308,9 @@ export class TreeSitterQueryLoader {
|
|
|
298
308
|
break;
|
|
299
309
|
}
|
|
300
310
|
|
|
301
|
-
// Skip comment lines
|
|
302
|
-
|
|
311
|
+
// Skip YAML comment lines for most keys, but preserve native
|
|
312
|
+
// tree-sitter predicate lines in query blocks (#eq?, #match?, ...).
|
|
313
|
+
if (trimmed.startsWith("#") && key !== "query") continue;
|
|
303
314
|
|
|
304
315
|
// This is part of the multiline value
|
|
305
316
|
valueLines.push(line.slice(startIndent));
|
package/index.ts
CHANGED
|
@@ -13,6 +13,7 @@ import { ComplexityClient } from "./clients/complexity-client.js";
|
|
|
13
13
|
import { DependencyChecker } from "./clients/dependency-checker.js";
|
|
14
14
|
import { getDiagnosticTracker } from "./clients/diagnostic-tracker.js";
|
|
15
15
|
import {
|
|
16
|
+
getDispatchSlopScoreLine,
|
|
16
17
|
getLatencyReports,
|
|
17
18
|
resetDispatchBaselines,
|
|
18
19
|
} from "./clients/dispatch/integration.js";
|
|
@@ -378,6 +379,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
378
379
|
`Pipeline crashes (session): ${totalCrashes}`,
|
|
379
380
|
`Files affected: ${crashEntries.length}`,
|
|
380
381
|
];
|
|
382
|
+
const slopScoreLine = getDispatchSlopScoreLine();
|
|
381
383
|
|
|
382
384
|
if (crashEntries.length > 0) {
|
|
383
385
|
lines.push("", "Top crash files:");
|
|
@@ -432,6 +434,10 @@ export default function (pi: ExtensionAPI) {
|
|
|
432
434
|
}
|
|
433
435
|
}
|
|
434
436
|
|
|
437
|
+
if (slopScoreLine) {
|
|
438
|
+
lines.push("", slopScoreLine);
|
|
439
|
+
}
|
|
440
|
+
|
|
435
441
|
ctx.ui.notify(lines.join("\n"), "info");
|
|
436
442
|
},
|
|
437
443
|
});
|
|
@@ -525,10 +531,20 @@ pi.on("tool_call", async (event, ctx) => {
|
|
|
525
531
|
}
|
|
526
532
|
}
|
|
527
533
|
|
|
528
|
-
const
|
|
534
|
+
const inputObj = (event.input ?? {}) as Record<string, unknown>;
|
|
535
|
+
const rawFilePath =
|
|
529
536
|
isToolCallEventType("write", event) || isToolCallEventType("edit", event)
|
|
530
537
|
? (event.input as { path: string }).path
|
|
531
|
-
:
|
|
538
|
+
: toolName === "read" || toolName === "lsp_navigation"
|
|
539
|
+
? typeof inputObj.filePath === "string"
|
|
540
|
+
? inputObj.filePath
|
|
541
|
+
: undefined
|
|
542
|
+
: undefined;
|
|
543
|
+
const filePath = rawFilePath
|
|
544
|
+
? path.isAbsolute(rawFilePath)
|
|
545
|
+
? rawFilePath
|
|
546
|
+
: path.resolve(ctx.cwd ?? runtime.projectRoot, rawFilePath)
|
|
547
|
+
: undefined;
|
|
532
548
|
|
|
533
549
|
if (!filePath) return;
|
|
534
550
|
|
|
@@ -537,6 +553,24 @@ pi.on("tool_call", async (event, ctx) => {
|
|
|
537
553
|
);
|
|
538
554
|
if (!nodeFs.existsSync(filePath)) return;
|
|
539
555
|
|
|
556
|
+
const shouldAutoTouch =
|
|
557
|
+
(toolName === "read" ||
|
|
558
|
+
toolName === "write" ||
|
|
559
|
+
toolName === "edit" ||
|
|
560
|
+
toolName === "lsp_navigation") &&
|
|
561
|
+
!!pi.getFlag("lens-lsp") &&
|
|
562
|
+
!pi.getFlag("no-lsp");
|
|
563
|
+
if (shouldAutoTouch) {
|
|
564
|
+
try {
|
|
565
|
+
const fileContent = nodeFs.readFileSync(filePath, "utf-8");
|
|
566
|
+
void getLSPService()
|
|
567
|
+
.touchFile(filePath, fileContent, false, `tool_call:${toolName}`)
|
|
568
|
+
.catch((err) => dbg(`lsp auto-touch failed for ${filePath}: ${err}`));
|
|
569
|
+
} catch {
|
|
570
|
+
// Best effort only; never block tool calls.
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
|
|
540
574
|
// Record complexity baseline for historical tracking (booboo/tdi).
|
|
541
575
|
// Not shown inline — just captured for delta analysis.
|
|
542
576
|
if (
|
|
@@ -734,20 +768,36 @@ pi.on("turn_end", async (_event, ctx) => {
|
|
|
734
768
|
// --- Inject turn-end findings into next agent turn ---
|
|
735
769
|
// jscpd, madge, and turn-end delta results are cached at turn_end and consumed here
|
|
736
770
|
// via the context event, which fires before each provider request.
|
|
771
|
+
// Important: context handlers must APPEND to the existing message list, not replace it.
|
|
772
|
+
// Replacing `event.messages` can drop the user's first prompt entirely, which causes
|
|
773
|
+
// OpenAI Responses requests to fail with: "One of input/previous_response_id/prompt/conversation_id must be provided."
|
|
737
774
|
// biome-ignore lint/suspicious/noExplicitAny: pi.on("context") overload has TS resolution bug
|
|
738
|
-
(pi as any).on(
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
775
|
+
(pi as any).on(
|
|
776
|
+
"context",
|
|
777
|
+
async (
|
|
778
|
+
event: { messages?: Array<{ role: string; content: unknown }> } | unknown,
|
|
779
|
+
ctx: { cwd?: string },
|
|
780
|
+
) => {
|
|
781
|
+
try {
|
|
782
|
+
const cwd = ctx.cwd ?? process.cwd();
|
|
783
|
+
const turnEndFindings = consumeTurnEndFindings(cacheManager, cwd);
|
|
784
|
+
const sessionGuidance = consumeSessionStartGuidance(cacheManager, cwd);
|
|
785
|
+
const injectedMessages = [
|
|
786
|
+
...(sessionGuidance?.messages ?? []),
|
|
787
|
+
...(turnEndFindings?.messages ?? []),
|
|
788
|
+
];
|
|
789
|
+
if (injectedMessages.length === 0) return;
|
|
790
|
+
|
|
791
|
+
const existingMessages =
|
|
792
|
+
(event as { messages?: Array<{ role: string; content: unknown }> })
|
|
793
|
+
?.messages ?? [];
|
|
794
|
+
|
|
795
|
+
return {
|
|
796
|
+
messages: [...existingMessages, ...injectedMessages],
|
|
797
|
+
};
|
|
798
|
+
} catch (err) {
|
|
799
|
+
dbg(`context event error: ${err}`);
|
|
800
|
+
}
|
|
801
|
+
},
|
|
802
|
+
);
|
|
753
803
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-lens",
|
|
3
|
-
"version": "3.8.
|
|
3
|
+
"version": "3.8.23",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Real-time code feedback for pi — LSP, linters, formatters, type-checking, structural analysis & booboo",
|
|
6
6
|
"repository": {
|
|
@@ -22,6 +22,7 @@
|
|
|
22
22
|
"rust:build:debug": "cargo build --manifest-path rust/Cargo.toml",
|
|
23
23
|
"check": "node scripts/check-extensions.mjs",
|
|
24
24
|
"audit:tree-sitter": "node scripts/audit-tree-sitter-rules.mjs",
|
|
25
|
+
"audit:rule-catalog": "node scripts/validate-rule-catalog.mjs",
|
|
25
26
|
"download-grammars": "node scripts/download-grammars.js",
|
|
26
27
|
"postinstall": "node scripts/download-grammars.js"
|
|
27
28
|
},
|
|
@@ -53,6 +54,7 @@
|
|
|
53
54
|
"scripts/download-grammars.js",
|
|
54
55
|
"scripts/check-extensions.mjs",
|
|
55
56
|
"scripts/audit-tree-sitter-rules.mjs",
|
|
57
|
+
"scripts/validate-rule-catalog.mjs",
|
|
56
58
|
"rust/src/",
|
|
57
59
|
"rust/Cargo.toml",
|
|
58
60
|
"default-architect.yaml",
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 1,
|
|
3
|
+
"entries": [
|
|
4
|
+
{ "rule_id": "go-command-injection", "engine": "tree-sitter", "language": "go", "family": "security", "scope": "file", "canonical_concept": "command-injection-sink", "severity_default": "warning", "confidence": "medium", "status": "experimental" },
|
|
5
|
+
{ "rule_id": "go-hardcoded-secrets", "engine": "tree-sitter", "language": "go", "family": "security", "scope": "file", "canonical_concept": "hardcoded-secrets", "severity_default": "error", "confidence": "high", "status": "active" },
|
|
6
|
+
{ "rule_id": "go-insecure-random", "engine": "tree-sitter", "language": "go", "family": "security", "scope": "file", "canonical_concept": "insecure-randomness", "severity_default": "warning", "confidence": "medium", "status": "experimental" },
|
|
7
|
+
{ "rule_id": "go-path-traversal", "engine": "tree-sitter", "language": "go", "family": "security", "scope": "file", "canonical_concept": "path-traversal-sink", "severity_default": "warning", "confidence": "medium", "status": "experimental" },
|
|
8
|
+
{ "rule_id": "go-sql-injection", "engine": "tree-sitter", "language": "go", "family": "security", "scope": "file", "canonical_concept": "sql-injection-sink", "severity_default": "warning", "confidence": "medium", "status": "experimental" },
|
|
9
|
+
{ "rule_id": "go-weak-hash", "engine": "tree-sitter", "language": "go", "family": "security", "scope": "file", "canonical_concept": "weak-hash-primitive", "severity_default": "warning", "confidence": "high", "status": "experimental" },
|
|
10
|
+
{ "rule_id": "python-command-injection", "engine": "tree-sitter", "language": "python", "family": "security", "scope": "file", "canonical_concept": "command-injection-sink", "severity_default": "warning", "confidence": "medium", "status": "experimental" },
|
|
11
|
+
{ "rule_id": "python-hardcoded-secrets", "engine": "tree-sitter", "language": "python", "family": "security", "scope": "file", "canonical_concept": "hardcoded-secrets", "severity_default": "error", "confidence": "high", "status": "active" },
|
|
12
|
+
{ "rule_id": "eval-exec", "engine": "tree-sitter", "language": "python", "family": "security", "scope": "file", "canonical_concept": "dynamic-code-execution", "severity_default": "error", "confidence": "high", "status": "active" },
|
|
13
|
+
{ "rule_id": "python-insecure-deserialization", "engine": "tree-sitter", "language": "python", "family": "security", "scope": "file", "canonical_concept": "insecure-deserialization-sink", "severity_default": "warning", "confidence": "medium", "status": "experimental" },
|
|
14
|
+
{ "rule_id": "python-insecure-random", "engine": "tree-sitter", "language": "python", "family": "security", "scope": "file", "canonical_concept": "insecure-randomness", "severity_default": "warning", "confidence": "medium", "status": "experimental" },
|
|
15
|
+
{ "rule_id": "python-path-traversal", "engine": "tree-sitter", "language": "python", "family": "security", "scope": "file", "canonical_concept": "path-traversal-sink", "severity_default": "warning", "confidence": "medium", "status": "experimental" },
|
|
16
|
+
{ "rule_id": "python-sql-injection", "engine": "tree-sitter", "language": "python", "family": "security", "scope": "file", "canonical_concept": "sql-injection-sink", "severity_default": "warning", "confidence": "medium", "status": "experimental" },
|
|
17
|
+
{ "rule_id": "python-ssrf", "engine": "tree-sitter", "language": "python", "family": "security", "scope": "file", "canonical_concept": "ssrf-sink", "severity_default": "warning", "confidence": "medium", "status": "experimental" },
|
|
18
|
+
{ "rule_id": "python-unsafe-regex", "engine": "tree-sitter", "language": "python", "family": "security", "scope": "file", "canonical_concept": "unsafe-regex", "severity_default": "error", "confidence": "high", "status": "active" },
|
|
19
|
+
{ "rule_id": "python-weak-hash", "engine": "tree-sitter", "language": "python", "family": "security", "scope": "file", "canonical_concept": "weak-hash-primitive", "severity_default": "warning", "confidence": "high", "status": "experimental" },
|
|
20
|
+
{ "rule_id": "ruby-command-injection", "engine": "tree-sitter", "language": "ruby", "family": "security", "scope": "file", "canonical_concept": "command-injection-sink", "severity_default": "warning", "confidence": "low", "status": "experimental" },
|
|
21
|
+
{ "rule_id": "ruby-eval", "engine": "tree-sitter", "language": "ruby", "family": "security", "scope": "file", "canonical_concept": "dynamic-code-execution", "severity_default": "error", "confidence": "high", "status": "active" },
|
|
22
|
+
{ "rule_id": "ruby-hardcoded-secrets", "engine": "tree-sitter", "language": "ruby", "family": "security", "scope": "file", "canonical_concept": "hardcoded-secrets", "severity_default": "error", "confidence": "high", "status": "active" },
|
|
23
|
+
{ "rule_id": "ruby-insecure-deserialization", "engine": "tree-sitter", "language": "ruby", "family": "security", "scope": "file", "canonical_concept": "insecure-deserialization-sink", "severity_default": "warning", "confidence": "medium", "status": "experimental" },
|
|
24
|
+
{ "rule_id": "ruby-insecure-random", "engine": "tree-sitter", "language": "ruby", "family": "security", "scope": "file", "canonical_concept": "insecure-randomness", "severity_default": "warning", "confidence": "medium", "status": "experimental" },
|
|
25
|
+
{ "rule_id": "ruby-unsafe-regex", "engine": "tree-sitter", "language": "ruby", "family": "security", "scope": "file", "canonical_concept": "unsafe-regex", "severity_default": "error", "confidence": "high", "status": "active" },
|
|
26
|
+
{ "rule_id": "ruby-weak-hash", "engine": "tree-sitter", "language": "ruby", "family": "security", "scope": "file", "canonical_concept": "weak-hash-primitive", "severity_default": "warning", "confidence": "high", "status": "experimental" },
|
|
27
|
+
{ "rule_id": "dangerously-set-inner-html", "engine": "tree-sitter", "language": "tsx", "family": "security", "scope": "file", "canonical_concept": "dom-xss-dangerous-inner-html", "severity_default": "error", "confidence": "high", "status": "active" },
|
|
28
|
+
{ "rule_id": "no-eval", "engine": "tree-sitter", "language": "typescript", "family": "security", "scope": "file", "canonical_concept": "dynamic-code-execution", "severity_default": "error", "confidence": "high", "status": "active" },
|
|
29
|
+
{ "rule_id": "hardcoded-secrets", "engine": "tree-sitter", "language": "typescript", "family": "security", "scope": "file", "canonical_concept": "hardcoded-secrets", "severity_default": "error", "confidence": "high", "status": "active" },
|
|
30
|
+
{ "rule_id": "sql-injection", "engine": "tree-sitter", "language": "typescript", "family": "security", "scope": "file", "canonical_concept": "sql-injection-sink", "severity_default": "error", "confidence": "medium", "status": "active" },
|
|
31
|
+
{ "rule_id": "ts-command-injection", "engine": "tree-sitter", "language": "typescript", "family": "security", "scope": "file", "canonical_concept": "command-injection-sink", "severity_default": "warning", "confidence": "medium", "status": "experimental" },
|
|
32
|
+
{ "rule_id": "ts-insecure-random", "engine": "tree-sitter", "language": "typescript", "family": "security", "scope": "file", "canonical_concept": "insecure-randomness", "severity_default": "warning", "confidence": "medium", "status": "experimental" },
|
|
33
|
+
{ "rule_id": "ts-ssrf", "engine": "tree-sitter", "language": "typescript", "family": "security", "scope": "file", "canonical_concept": "ssrf-sink", "severity_default": "warning", "confidence": "medium", "status": "experimental" },
|
|
34
|
+
{ "rule_id": "ts-weak-hash", "engine": "tree-sitter", "language": "typescript", "family": "security", "scope": "file", "canonical_concept": "weak-hash-primitive", "severity_default": "warning", "confidence": "high", "status": "experimental" },
|
|
35
|
+
{ "rule_id": "unsafe-regex", "engine": "tree-sitter", "language": "typescript", "family": "security", "scope": "file", "canonical_concept": "unsafe-regex", "severity_default": "error", "confidence": "high", "status": "active" },
|
|
36
|
+
{ "rule_id": "go-goroutine-loop-capture", "engine": "tree-sitter", "language": "go", "family": "concurrency", "scope": "file", "canonical_concept": "goroutine-loop-capture", "severity_default": "warning", "confidence": "low", "status": "experimental" },
|
|
37
|
+
{ "rule_id": "go-shared-map-write-goroutine", "engine": "tree-sitter", "language": "go", "family": "concurrency", "scope": "file", "canonical_concept": "shared-map-write-in-goroutine", "severity_default": "warning", "confidence": "medium", "status": "experimental" },
|
|
38
|
+
{ "rule_id": "python-thread-global-write", "engine": "tree-sitter", "language": "python", "family": "concurrency", "scope": "file", "canonical_concept": "threaded-shared-state-write", "severity_default": "warning", "confidence": "medium", "status": "experimental" },
|
|
39
|
+
{ "rule_id": "rust-lock-held-across-await", "engine": "tree-sitter", "language": "rust", "family": "concurrency", "scope": "file", "canonical_concept": "lock-held-across-await", "severity_default": "warning", "confidence": "low", "status": "experimental" },
|
|
40
|
+
{ "rule_id": "ts-detached-async-call", "engine": "tree-sitter", "language": "typescript", "family": "concurrency", "scope": "file", "canonical_concept": "detached-async-call", "severity_default": "warning", "confidence": "medium", "status": "experimental" },
|
|
41
|
+
|
|
42
|
+
{ "rule_id": "no-sql-in-code", "engine": "ast-grep", "language": "typescript", "family": "security", "scope": "file", "canonical_concept": "sql-in-code-literal", "severity_default": "warning", "confidence": "medium", "status": "active" },
|
|
43
|
+
{ "rule_id": "no-sql-in-code-js", "engine": "ast-grep", "language": "javascript", "family": "security", "scope": "file", "canonical_concept": "sql-in-code-literal", "severity_default": "warning", "confidence": "medium", "status": "active" },
|
|
44
|
+
{ "rule_id": "no-open-redirect", "engine": "ast-grep", "language": "typescript", "family": "security", "scope": "file", "canonical_concept": "open-redirect", "severity_default": "warning", "confidence": "medium", "status": "active" },
|
|
45
|
+
{ "rule_id": "no-open-redirect-js", "engine": "ast-grep", "language": "javascript", "family": "security", "scope": "file", "canonical_concept": "open-redirect", "severity_default": "warning", "confidence": "medium", "status": "active" },
|
|
46
|
+
{ "rule_id": "no-javascript-url", "engine": "ast-grep", "language": "typescript", "family": "security", "scope": "file", "canonical_concept": "javascript-url-scheme", "severity_default": "warning", "confidence": "high", "status": "active" },
|
|
47
|
+
{ "rule_id": "no-javascript-url-js", "engine": "ast-grep", "language": "javascript", "family": "security", "scope": "file", "canonical_concept": "javascript-url-scheme", "severity_default": "warning", "confidence": "high", "status": "active" },
|
|
48
|
+
{ "rule_id": "no-insecure-randomness", "engine": "ast-grep", "language": "typescript", "family": "security", "scope": "file", "canonical_concept": "insecure-randomness", "severity_default": "warning", "confidence": "medium", "status": "active" },
|
|
49
|
+
{ "rule_id": "no-insecure-randomness-js", "engine": "ast-grep", "language": "javascript", "family": "security", "scope": "file", "canonical_concept": "insecure-randomness", "severity_default": "warning", "confidence": "medium", "status": "active" },
|
|
50
|
+
{ "rule_id": "no-implied-eval", "engine": "ast-grep", "language": "typescript", "family": "security", "scope": "file", "canonical_concept": "dynamic-code-execution", "severity_default": "error", "confidence": "high", "status": "active", "allow_overlap": true },
|
|
51
|
+
{ "rule_id": "no-implied-eval-js", "engine": "ast-grep", "language": "javascript", "family": "security", "scope": "file", "canonical_concept": "dynamic-code-execution", "severity_default": "error", "confidence": "high", "status": "active" },
|
|
52
|
+
{ "rule_id": "no-hardcoded-secrets", "engine": "ast-grep", "language": "typescript", "family": "security", "scope": "file", "canonical_concept": "hardcoded-secrets", "severity_default": "error", "confidence": "high", "status": "active", "allow_overlap": true },
|
|
53
|
+
{ "rule_id": "no-hardcoded-secrets-js", "engine": "ast-grep", "language": "javascript", "family": "security", "scope": "file", "canonical_concept": "hardcoded-secrets", "severity_default": "error", "confidence": "high", "status": "active" },
|
|
54
|
+
{ "rule_id": "no-global-eval-js", "engine": "ast-grep", "language": "javascript", "family": "security", "scope": "file", "canonical_concept": "dynamic-code-execution", "severity_default": "error", "confidence": "high", "status": "active", "allow_overlap": true },
|
|
55
|
+
{ "rule_id": "jwt-no-verify", "engine": "ast-grep", "language": "typescript", "family": "security", "scope": "file", "canonical_concept": "jwt-signature-bypass", "severity_default": "error", "confidence": "high", "status": "active" },
|
|
56
|
+
{ "rule_id": "jwt-no-verify-js", "engine": "ast-grep", "language": "javascript", "family": "security", "scope": "file", "canonical_concept": "jwt-signature-bypass", "severity_default": "error", "confidence": "high", "status": "active" },
|
|
57
|
+
{ "rule_id": "toctou", "engine": "ast-grep", "language": "typescript", "family": "concurrency", "scope": "file", "canonical_concept": "toctou-file-race", "severity_default": "error", "confidence": "high", "status": "active" },
|
|
58
|
+
{ "rule_id": "toctou-js", "engine": "ast-grep", "language": "javascript", "family": "concurrency", "scope": "file", "canonical_concept": "toctou-file-race", "severity_default": "error", "confidence": "high", "status": "active" },
|
|
59
|
+
{ "rule_id": "missed-concurrency", "engine": "ast-grep", "language": "typescript", "family": "concurrency", "scope": "function", "canonical_concept": "sequential-awaits-parallelizable", "severity_default": "warning", "confidence": "medium", "status": "active" },
|
|
60
|
+
{ "rule_id": "missed-concurrency-js", "engine": "ast-grep", "language": "javascript", "family": "concurrency", "scope": "function", "canonical_concept": "sequential-awaits-parallelizable", "severity_default": "warning", "confidence": "medium", "status": "active" },
|
|
61
|
+
{ "rule_id": "no-await-in-loop", "engine": "ast-grep", "language": "typescript", "family": "concurrency", "scope": "function", "canonical_concept": "await-inside-loop", "severity_default": "warning", "confidence": "medium", "status": "active" },
|
|
62
|
+
{ "rule_id": "no-await-in-loop-js", "engine": "ast-grep", "language": "javascript", "family": "concurrency", "scope": "function", "canonical_concept": "await-inside-loop", "severity_default": "warning", "confidence": "medium", "status": "active" }
|
|
63
|
+
]
|
|
64
|
+
}
|
|
@@ -16,17 +16,29 @@ description: |
|
|
|
16
16
|
✅ FIX: Handle the error before returning, or document why it's safe.
|
|
17
17
|
|
|
18
18
|
query: |
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
(
|
|
22
|
-
|
|
23
|
-
|
|
19
|
+
[
|
|
20
|
+
(function_declaration
|
|
21
|
+
result: (type_identifier) @RET
|
|
22
|
+
body: (block
|
|
23
|
+
(return_statement
|
|
24
|
+
(expression_list
|
|
25
|
+
(call_expression) @CALL)))
|
|
26
|
+
(#eq? @RET "error"))
|
|
27
|
+
(function_declaration
|
|
28
|
+
result: (parameter_list
|
|
29
|
+
(parameter_declaration
|
|
30
|
+
type: (type_identifier) @RET))
|
|
31
|
+
body: (block
|
|
32
|
+
(return_statement
|
|
33
|
+
(expression_list
|
|
34
|
+
(call_expression) @CALL)))
|
|
35
|
+
(#eq? @RET "error"))
|
|
36
|
+
]
|
|
24
37
|
|
|
25
38
|
metavars:
|
|
39
|
+
- RET
|
|
26
40
|
- CALL
|
|
27
41
|
|
|
28
|
-
post_filter: returns_error
|
|
29
|
-
|
|
30
42
|
has_fix: false
|
|
31
43
|
|
|
32
44
|
tags:
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# Go Security
|
|
2
|
+
# Detects shell command execution via exec.Command(..., "-c"|"/c", ...).
|
|
3
|
+
id: go-command-injection
|
|
4
|
+
name: Command Injection Sink
|
|
5
|
+
severity: warning
|
|
6
|
+
category: security
|
|
7
|
+
defect_class: injection
|
|
8
|
+
inline_tier: warning
|
|
9
|
+
language: go
|
|
10
|
+
|
|
11
|
+
message: "Potential command injection sink — avoid shell command composition with user input"
|
|
12
|
+
|
|
13
|
+
description: |
|
|
14
|
+
Using exec.Command with a shell (`sh -c`, `bash -c`, `cmd /c`) executes command strings.
|
|
15
|
+
|
|
16
|
+
✅ FIX: invoke binaries directly with explicit argument arrays and strict allowlists.
|
|
17
|
+
|
|
18
|
+
query: |
|
|
19
|
+
(call_expression
|
|
20
|
+
function: (selector_expression
|
|
21
|
+
operand: (identifier) @PKG
|
|
22
|
+
field: (field_identifier) @FN)
|
|
23
|
+
arguments: (argument_list
|
|
24
|
+
(interpreted_string_literal) @SHELL
|
|
25
|
+
(interpreted_string_literal) @FLAG
|
|
26
|
+
(_) @CMD))
|
|
27
|
+
(#eq? @PKG "exec")
|
|
28
|
+
(#match? @FN "^(Command|CommandContext)$")
|
|
29
|
+
(#match? @SHELL "^\"(sh|bash|zsh|cmd|powershell|pwsh)\"$")
|
|
30
|
+
(#match? @FLAG "^\"(-c|/c)\"$")
|
|
31
|
+
|
|
32
|
+
metavars:
|
|
33
|
+
- PKG
|
|
34
|
+
- FN
|
|
35
|
+
- SHELL
|
|
36
|
+
- FLAG
|
|
37
|
+
- CMD
|
|
38
|
+
|
|
39
|
+
post_filter: go_command_injection_sink
|
|
40
|
+
|
|
41
|
+
has_fix: false
|
|
42
|
+
|
|
43
|
+
tags:
|
|
44
|
+
- go
|
|
45
|
+
- security
|
|
46
|
+
- command-injection
|
|
47
|
+
- cwe-78
|
|
48
|
+
- owasp-a03
|
|
49
|
+
|
|
50
|
+
examples:
|
|
51
|
+
bad: |
|
|
52
|
+
exec.Command("sh", "-c", userInput)
|
|
53
|
+
|
|
54
|
+
good: |
|
|
55
|
+
exec.Command("git", "status")
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# Go Reliability
|
|
2
|
+
# Detects direct panic calls in application/library code.
|
|
3
|
+
id: go-direct-panic
|
|
4
|
+
name: Direct panic() Call
|
|
5
|
+
severity: error
|
|
6
|
+
category: reliability
|
|
7
|
+
defect_class: safety
|
|
8
|
+
inline_tier: blocking
|
|
9
|
+
language: go
|
|
10
|
+
|
|
11
|
+
message: "Direct panic() call can crash the process — return or propagate an error instead"
|
|
12
|
+
|
|
13
|
+
description: |
|
|
14
|
+
Calling `panic(...)` for ordinary error paths can terminate the process and bypass
|
|
15
|
+
normal recovery/cleanup flow.
|
|
16
|
+
|
|
17
|
+
✅ FIX: return an error, wrap it, or handle it at the boundary layer.
|
|
18
|
+
|
|
19
|
+
query: |
|
|
20
|
+
(call_expression
|
|
21
|
+
function: (identifier) @FN
|
|
22
|
+
arguments: (argument_list) @ARGS)
|
|
23
|
+
(#eq? @FN "panic")
|
|
24
|
+
|
|
25
|
+
metavars:
|
|
26
|
+
- FN
|
|
27
|
+
- ARGS
|
|
28
|
+
|
|
29
|
+
has_fix: false
|
|
30
|
+
|
|
31
|
+
tags:
|
|
32
|
+
- go
|
|
33
|
+
- reliability
|
|
34
|
+
- safety
|
|
35
|
+
|
|
36
|
+
examples:
|
|
37
|
+
bad: |
|
|
38
|
+
if err != nil {
|
|
39
|
+
panic(err)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
good: |
|
|
43
|
+
if err != nil {
|
|
44
|
+
return fmt.Errorf("failed: %w", err)
|
|
45
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# Go Error Handling
|
|
2
|
+
# Detects empty `if err != nil` handlers
|
|
3
|
+
id: go-empty-if-err
|
|
4
|
+
name: Empty Error Handler
|
|
5
|
+
severity: error
|
|
6
|
+
category: error-handling
|
|
7
|
+
defect_class: silent-error
|
|
8
|
+
inline_tier: blocking
|
|
9
|
+
language: go
|
|
10
|
+
|
|
11
|
+
message: "Empty error handler — handle err or return it"
|
|
12
|
+
|
|
13
|
+
description: |
|
|
14
|
+
An `if err != nil {}` block silently ignores failures.
|
|
15
|
+
|
|
16
|
+
✅ FIX: return, wrap, or log the error with context.
|
|
17
|
+
|
|
18
|
+
query: |
|
|
19
|
+
(if_statement
|
|
20
|
+
condition: (binary_expression
|
|
21
|
+
left: (identifier) @ERR
|
|
22
|
+
right: (nil))
|
|
23
|
+
consequence: (block) @BODY)
|
|
24
|
+
(#eq? @ERR "err")
|
|
25
|
+
|
|
26
|
+
metavars:
|
|
27
|
+
- ERR
|
|
28
|
+
- BODY
|
|
29
|
+
|
|
30
|
+
post_filter: empty_body
|
|
31
|
+
|
|
32
|
+
has_fix: false
|
|
33
|
+
|
|
34
|
+
tags:
|
|
35
|
+
- go
|
|
36
|
+
- error-handling
|
|
37
|
+
- reliability
|
|
38
|
+
|
|
39
|
+
examples:
|
|
40
|
+
bad: |
|
|
41
|
+
if err != nil {
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
good: |
|
|
45
|
+
if err != nil {
|
|
46
|
+
return fmt.Errorf("load config: %w", err)
|
|
47
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# Go Concurrency
|
|
2
|
+
# Detects goroutines started inside loops with parameterless func literals.
|
|
3
|
+
id: go-goroutine-loop-capture
|
|
4
|
+
name: Goroutine Loop Capture Risk
|
|
5
|
+
severity: warning
|
|
6
|
+
category: concurrency
|
|
7
|
+
defect_class: async-misuse
|
|
8
|
+
inline_tier: warning
|
|
9
|
+
language: go
|
|
10
|
+
|
|
11
|
+
message: "Goroutine launched in loop with captured variables — pass loop values as parameters"
|
|
12
|
+
|
|
13
|
+
description: |
|
|
14
|
+
Launching `go func(){...}()` inside loops can capture changing loop variables.
|
|
15
|
+
|
|
16
|
+
✅ FIX: pass loop variables as explicit function parameters.
|
|
17
|
+
|
|
18
|
+
query: |
|
|
19
|
+
(for_statement
|
|
20
|
+
body: (block
|
|
21
|
+
(go_statement) @GO)
|
|
22
|
+
|
|
23
|
+
metavars:
|
|
24
|
+
- GO
|
|
25
|
+
|
|
26
|
+
cwe:
|
|
27
|
+
- CWE-362
|
|
28
|
+
owasp:
|
|
29
|
+
- A09
|
|
30
|
+
confidence: medium
|
|
31
|
+
|
|
32
|
+
has_fix: false
|
|
33
|
+
|
|
34
|
+
tags:
|
|
35
|
+
- go
|
|
36
|
+
- concurrency
|
|
37
|
+
- goroutine
|
|
38
|
+
- loop-capture
|
|
39
|
+
|
|
40
|
+
examples:
|
|
41
|
+
bad: |
|
|
42
|
+
for _, item := range items {
|
|
43
|
+
go func() { use(item) }()
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
good: |
|
|
47
|
+
for _, item := range items {
|
|
48
|
+
go func(v string) { use(v) }(item)
|
|
49
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# Go Reliability
|
|
2
|
+
# Detects discarded call results using blank identifier assignment.
|
|
3
|
+
id: go-ignored-call-result
|
|
4
|
+
name: Ignored Call Result
|
|
5
|
+
severity: warning
|
|
6
|
+
category: error-handling
|
|
7
|
+
defect_class: silent-error
|
|
8
|
+
inline_tier: warning
|
|
9
|
+
language: go
|
|
10
|
+
|
|
11
|
+
message: "Call result assigned to '_' — verify this discard is intentional"
|
|
12
|
+
|
|
13
|
+
description: |
|
|
14
|
+
Assigning a call result to `_` can hide failures or important return values.
|
|
15
|
+
|
|
16
|
+
✅ FIX: handle the returned value/error or document intentional discard.
|
|
17
|
+
|
|
18
|
+
query: |
|
|
19
|
+
[
|
|
20
|
+
(short_var_declaration
|
|
21
|
+
left: (expression_list
|
|
22
|
+
(identifier) @VAR)
|
|
23
|
+
right: (expression_list
|
|
24
|
+
(call_expression) @CALL))
|
|
25
|
+
(assignment_statement
|
|
26
|
+
left: (expression_list
|
|
27
|
+
(identifier) @VAR)
|
|
28
|
+
right: (expression_list
|
|
29
|
+
(call_expression) @CALL))
|
|
30
|
+
]
|
|
31
|
+
(#eq? @VAR "_")
|
|
32
|
+
|
|
33
|
+
metavars:
|
|
34
|
+
- VAR
|
|
35
|
+
- CALL
|
|
36
|
+
|
|
37
|
+
has_fix: false
|
|
38
|
+
|
|
39
|
+
tags:
|
|
40
|
+
- go
|
|
41
|
+
- reliability
|
|
42
|
+
- code-smell
|
|
43
|
+
|
|
44
|
+
examples:
|
|
45
|
+
bad: |
|
|
46
|
+
_ = doWork()
|
|
47
|
+
|
|
48
|
+
good: |
|
|
49
|
+
if err := doWork(); err != nil {
|
|
50
|
+
return err
|
|
51
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# Go Security
|
|
2
|
+
# Detects use of math/rand in potentially security-sensitive contexts.
|
|
3
|
+
id: go-insecure-random
|
|
4
|
+
name: Insecure Randomness
|
|
5
|
+
severity: warning
|
|
6
|
+
category: security
|
|
7
|
+
defect_class: injection
|
|
8
|
+
inline_tier: warning
|
|
9
|
+
language: go
|
|
10
|
+
|
|
11
|
+
message: "Insecure randomness source detected — use crypto/rand for security-sensitive values"
|
|
12
|
+
|
|
13
|
+
description: |
|
|
14
|
+
math/rand is deterministic and not suitable for security-sensitive randomness.
|
|
15
|
+
|
|
16
|
+
✅ FIX: use crypto/rand for tokens, keys, nonces, and secrets.
|
|
17
|
+
|
|
18
|
+
query: |
|
|
19
|
+
(call_expression
|
|
20
|
+
function: (selector_expression
|
|
21
|
+
operand: (identifier) @PKG
|
|
22
|
+
field: (field_identifier) @FN)
|
|
23
|
+
arguments: (argument_list) @ARGS)
|
|
24
|
+
(#eq? @PKG "rand")
|
|
25
|
+
(#match? @FN "^(Int|Intn|Int63|Uint32|Uint64|Read|Float64)$")
|
|
26
|
+
|
|
27
|
+
metavars:
|
|
28
|
+
- PKG
|
|
29
|
+
- FN
|
|
30
|
+
- ARGS
|
|
31
|
+
|
|
32
|
+
cwe:
|
|
33
|
+
- CWE-330
|
|
34
|
+
owasp:
|
|
35
|
+
- A02
|
|
36
|
+
confidence: medium
|
|
37
|
+
|
|
38
|
+
has_fix: false
|
|
39
|
+
|
|
40
|
+
tags:
|
|
41
|
+
- go
|
|
42
|
+
- security
|
|
43
|
+
- randomness
|
|
44
|
+
- weak-prng
|
|
45
|
+
|
|
46
|
+
examples:
|
|
47
|
+
bad: |
|
|
48
|
+
code := rand.Intn(1000000)
|
|
49
|
+
|
|
50
|
+
good: |
|
|
51
|
+
_, _ = rand.Read(buf) // from crypto/rand
|