opensecurity 0.1.0 → 0.2.1
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/README.md +156 -30
- package/assets/grammars/README.md +9 -0
- package/assets/grammars/tree-sitter-c-sharp.wasm +0 -0
- package/assets/grammars/tree-sitter-c.wasm +0 -0
- package/assets/grammars/tree-sitter-cpp.wasm +0 -0
- package/assets/grammars/tree-sitter-go.wasm +0 -0
- package/assets/grammars/tree-sitter-java.wasm +0 -0
- package/assets/grammars/tree-sitter-kotlin.wasm +0 -0
- package/assets/grammars/tree-sitter-php.wasm +0 -0
- package/assets/grammars/tree-sitter-python.wasm +0 -0
- package/assets/grammars/tree-sitter-ruby.wasm +0 -0
- package/assets/grammars/tree-sitter-rust.wasm +0 -0
- package/assets/grammars/tree-sitter-swift.wasm +0 -0
- package/dist/adapters/bandit.js +41 -0
- package/dist/adapters/brakeman.js +41 -0
- package/dist/adapters/gosec.js +49 -0
- package/dist/adapters/languages.js +29 -0
- package/dist/adapters/runner.js +46 -0
- package/dist/adapters/semgrep.js +59 -0
- package/dist/adapters/types.js +1 -0
- package/dist/adapters/utils.js +52 -0
- package/dist/analysis/infraPatterns.js +196 -0
- package/dist/analysis/universalPatterns.js +56 -0
- package/dist/cli.js +15 -1
- package/dist/config.js +2 -1
- package/dist/native/languages.js +211 -0
- package/dist/native/loader.js +61 -0
- package/dist/native/rules.js +14 -0
- package/dist/native/taint.js +225 -0
- package/dist/scan.js +207 -0
- package/package.json +46 -2
- package/rules/taint/c.json +47 -0
- package/rules/taint/cpp.json +47 -0
- package/rules/taint/csharp.json +99 -0
- package/rules/taint/go.json +86 -0
- package/rules/taint/java.json +101 -0
- package/rules/taint/kotlin.json +86 -0
- package/rules/taint/php.json +100 -0
- package/rules/taint/python.json +108 -0
- package/rules/taint/ruby.json +101 -0
- package/rules/taint/rust.json +86 -0
- package/rules/taint/swift.json +86 -0
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
const RULES = [
|
|
2
|
+
{
|
|
3
|
+
id: "dockerfile-root-user",
|
|
4
|
+
title: "Container Runs as Root",
|
|
5
|
+
description: "Dockerfile runs as root user; consider using a non-root user.",
|
|
6
|
+
severity: "medium",
|
|
7
|
+
owasp: "A05:2021 Security Misconfiguration",
|
|
8
|
+
pattern: /\bUSER\s+root\b/i
|
|
9
|
+
},
|
|
10
|
+
{
|
|
11
|
+
id: "dockerfile-privileged",
|
|
12
|
+
title: "Privileged Container",
|
|
13
|
+
description: "Dockerfile enables privileged mode or sets all capabilities.",
|
|
14
|
+
severity: "high",
|
|
15
|
+
owasp: "A05:2021 Security Misconfiguration",
|
|
16
|
+
pattern: /\b(--privileged|CAP_SYS_ADMIN|cap_add\s*:\s*\[?\s*ALL\s*\]?)/i
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
id: "k8s-privileged",
|
|
20
|
+
title: "Privileged Kubernetes Pod",
|
|
21
|
+
description: "Kubernetes manifest enables privileged containers.",
|
|
22
|
+
severity: "high",
|
|
23
|
+
owasp: "A05:2021 Security Misconfiguration",
|
|
24
|
+
pattern: /\bprivileged\s*:\s*true\b/i
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
id: "k8s-host-path",
|
|
28
|
+
title: "HostPath Volume",
|
|
29
|
+
description: "Kubernetes HostPath volume may allow host access.",
|
|
30
|
+
severity: "medium",
|
|
31
|
+
owasp: "A05:2021 Security Misconfiguration",
|
|
32
|
+
pattern: /\bhostPath\s*:\b/i
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
id: "k8s-host-network",
|
|
36
|
+
title: "Host Network Enabled",
|
|
37
|
+
description: "Kubernetes manifest enables hostNetwork which reduces isolation.",
|
|
38
|
+
severity: "medium",
|
|
39
|
+
owasp: "A05:2021 Security Misconfiguration",
|
|
40
|
+
pattern: /\bhostNetwork\s*:\s*true\b/i
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
id: "k8s-host-ipc",
|
|
44
|
+
title: "Host IPC Enabled",
|
|
45
|
+
description: "Kubernetes manifest enables hostIPC which reduces isolation.",
|
|
46
|
+
severity: "medium",
|
|
47
|
+
owasp: "A05:2021 Security Misconfiguration",
|
|
48
|
+
pattern: /\bhostIPC\s*:\s*true\b/i
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
id: "k8s-host-pid",
|
|
52
|
+
title: "Host PID Namespace",
|
|
53
|
+
description: "Kubernetes manifest enables hostPID which reduces isolation.",
|
|
54
|
+
severity: "medium",
|
|
55
|
+
owasp: "A05:2021 Security Misconfiguration",
|
|
56
|
+
pattern: /\bhostPID\s*:\s*true\b/i
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
id: "k8s-allow-priv-esc",
|
|
60
|
+
title: "Privilege Escalation Enabled",
|
|
61
|
+
description: "Kubernetes manifest allows privilege escalation.",
|
|
62
|
+
severity: "high",
|
|
63
|
+
owasp: "A05:2021 Security Misconfiguration",
|
|
64
|
+
pattern: /\ballowPrivilegeEscalation\s*:\s*true\b/i
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
id: "k8s-no-readonly-fs",
|
|
68
|
+
title: "Writable Root Filesystem",
|
|
69
|
+
description: "Kubernetes manifest allows writable root filesystem.",
|
|
70
|
+
severity: "medium",
|
|
71
|
+
owasp: "A05:2021 Security Misconfiguration",
|
|
72
|
+
pattern: /\breadOnlyRootFilesystem\s*:\s*false\b/i
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
id: "k8s-run-as-root",
|
|
76
|
+
title: "Container Runs as Root",
|
|
77
|
+
description: "Kubernetes securityContext allows root user.",
|
|
78
|
+
severity: "medium",
|
|
79
|
+
owasp: "A05:2021 Security Misconfiguration",
|
|
80
|
+
pattern: /\brunAsNonRoot\s*:\s*false\b/i
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
id: "k8s-run-as-user-root",
|
|
84
|
+
title: "Container Runs as Root UID",
|
|
85
|
+
description: "Kubernetes manifest sets runAsUser to 0 (root).",
|
|
86
|
+
severity: "medium",
|
|
87
|
+
owasp: "A05:2021 Security Misconfiguration",
|
|
88
|
+
pattern: /\brunAsUser\s*:\s*0\b/i
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
id: "k8s-seccomp-unconfined",
|
|
92
|
+
title: "Seccomp Unconfined",
|
|
93
|
+
description: "Kubernetes manifest disables seccomp confinement.",
|
|
94
|
+
severity: "high",
|
|
95
|
+
owasp: "A05:2021 Security Misconfiguration",
|
|
96
|
+
pattern: /\bseccompProfile\b[\s\S]*?\btype\s*:\s*Unconfined\b/i
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
id: "terraform-public-sg",
|
|
100
|
+
title: "Public Security Group",
|
|
101
|
+
description: "Terraform security group allows ingress from all sources.",
|
|
102
|
+
severity: "high",
|
|
103
|
+
owasp: "A05:2021 Security Misconfiguration",
|
|
104
|
+
pattern: /\b(cidr_blocks|cidr_block)\s*=\s*\[?\s*\"0\.0\.0\.0\/0\"\s*\]?/i
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
id: "terraform-public-acl",
|
|
108
|
+
title: "Public Network ACL",
|
|
109
|
+
description: "Terraform network ACL allows ingress from all sources.",
|
|
110
|
+
severity: "high",
|
|
111
|
+
owasp: "A05:2021 Security Misconfiguration",
|
|
112
|
+
pattern: /\b(ingress|egress)\b[\s\S]*0\.0\.0\.0\/0/i
|
|
113
|
+
},
|
|
114
|
+
{
|
|
115
|
+
id: "terraform-open-sg-port",
|
|
116
|
+
title: "Open Security Group Port",
|
|
117
|
+
description: "Terraform security group allows public ingress on common ports.",
|
|
118
|
+
severity: "high",
|
|
119
|
+
owasp: "A05:2021 Security Misconfiguration",
|
|
120
|
+
pattern: /\b(from_port|to_port)\s*=\s*(22|3389|5432|3306|6379|9200|27017)\b[\s\S]*0\.0\.0\.0\/0/i
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
id: "terraform-public-s3-acl",
|
|
124
|
+
title: "Public S3 ACL",
|
|
125
|
+
description: "Terraform S3 ACL is public-read or public-read-write.",
|
|
126
|
+
severity: "high",
|
|
127
|
+
owasp: "A05:2021 Security Misconfiguration",
|
|
128
|
+
pattern: /\bacl\s*=\s*\"public-read(-write)?\"/i
|
|
129
|
+
},
|
|
130
|
+
{
|
|
131
|
+
id: "terraform-s3-public-block-disabled",
|
|
132
|
+
title: "S3 Public Access Block Disabled",
|
|
133
|
+
description: "Terraform disables S3 public access block.",
|
|
134
|
+
severity: "high",
|
|
135
|
+
owasp: "A05:2021 Security Misconfiguration",
|
|
136
|
+
pattern: /\b(block_public_acls|block_public_policy|ignore_public_acls|restrict_public_buckets)\s*=\s*false\b/i
|
|
137
|
+
},
|
|
138
|
+
{
|
|
139
|
+
id: "terraform-rds-public",
|
|
140
|
+
title: "Public RDS Instance",
|
|
141
|
+
description: "Terraform RDS instance is publicly accessible.",
|
|
142
|
+
severity: "high",
|
|
143
|
+
owasp: "A05:2021 Security Misconfiguration",
|
|
144
|
+
pattern: /\bpublicly_accessible\s*=\s*true\b/i
|
|
145
|
+
},
|
|
146
|
+
{
|
|
147
|
+
id: "yaml-insecure-tls",
|
|
148
|
+
title: "Insecure TLS",
|
|
149
|
+
description: "Config disables TLS verification.",
|
|
150
|
+
severity: "medium",
|
|
151
|
+
owasp: "A02:2021 Cryptographic Failures",
|
|
152
|
+
pattern: /\b(insecureSkipVerify|ssl_verify\s*:\s*false|verify_ssl\s*:\s*false)\b/i
|
|
153
|
+
}
|
|
154
|
+
];
|
|
155
|
+
export function runInfraPatterns(content, filePath) {
|
|
156
|
+
const findings = [];
|
|
157
|
+
const lineStarts = [0];
|
|
158
|
+
for (let i = 0; i < content.length; i += 1) {
|
|
159
|
+
if (content[i] === "\n")
|
|
160
|
+
lineStarts.push(i + 1);
|
|
161
|
+
}
|
|
162
|
+
const findLine = (index) => {
|
|
163
|
+
let low = 0;
|
|
164
|
+
let high = lineStarts.length - 1;
|
|
165
|
+
while (low <= high) {
|
|
166
|
+
const mid = Math.floor((low + high) / 2);
|
|
167
|
+
if (lineStarts[mid] <= index) {
|
|
168
|
+
if (mid === lineStarts.length - 1 || lineStarts[mid + 1] > index)
|
|
169
|
+
return mid + 1;
|
|
170
|
+
low = mid + 1;
|
|
171
|
+
}
|
|
172
|
+
else {
|
|
173
|
+
high = mid - 1;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
return 1;
|
|
177
|
+
};
|
|
178
|
+
for (const rule of RULES) {
|
|
179
|
+
const flags = rule.pattern.flags.includes("g") ? rule.pattern.flags : `${rule.pattern.flags}g`;
|
|
180
|
+
const regex = new RegExp(rule.pattern.source, flags);
|
|
181
|
+
let match;
|
|
182
|
+
while ((match = regex.exec(content)) !== null) {
|
|
183
|
+
const line = findLine(match.index);
|
|
184
|
+
findings.push({
|
|
185
|
+
id: rule.id,
|
|
186
|
+
severity: rule.severity,
|
|
187
|
+
owasp: rule.owasp,
|
|
188
|
+
title: rule.title,
|
|
189
|
+
description: rule.description,
|
|
190
|
+
file: filePath,
|
|
191
|
+
line
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
return findings;
|
|
196
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
const PATTERNS = [
|
|
2
|
+
{
|
|
3
|
+
id: "exec-os-system",
|
|
4
|
+
title: "Command Execution",
|
|
5
|
+
description: "Potential command execution via system call.",
|
|
6
|
+
severity: "critical",
|
|
7
|
+
owasp: "A03:2021 Injection",
|
|
8
|
+
pattern: /\b(os\.system|subprocess\.(call|run|Popen)|Runtime\.getRuntime\(\)\.exec|ProcessBuilder|exec\.Command|system\s*\(|popen\s*\(|Process\.Start|ShellExecute)\b/gi
|
|
9
|
+
},
|
|
10
|
+
{
|
|
11
|
+
id: "eval-exec",
|
|
12
|
+
title: "Dynamic Code Execution",
|
|
13
|
+
description: "Potential dynamic code execution detected.",
|
|
14
|
+
severity: "high",
|
|
15
|
+
owasp: "A03:2021 Injection",
|
|
16
|
+
pattern: /\b(eval|exec|Function|vm\.runInThisContext|loadstring)\s*\(/gi
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
id: "unsafe-deserialization",
|
|
20
|
+
title: "Unsafe Deserialization",
|
|
21
|
+
description: "Potential unsafe deserialization API usage.",
|
|
22
|
+
severity: "high",
|
|
23
|
+
owasp: "A08:2021 Software and Data Integrity Failures",
|
|
24
|
+
pattern: /\b(pickle\.loads?|yaml\.load|YAML\.load|ObjectInputStream|BinaryFormatter|XmlSerializer|Marshal\.load|PHP\s*unserialize|unserialize\s*\()\b/gi
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
id: "weak-crypto",
|
|
28
|
+
title: "Weak Crypto",
|
|
29
|
+
description: "Use of weak or deprecated crypto primitives.",
|
|
30
|
+
severity: "medium",
|
|
31
|
+
owasp: "A02:2021 Cryptographic Failures",
|
|
32
|
+
pattern: /\b(MD5|SHA1|RC4|DES|3DES|ECB)\b/gi
|
|
33
|
+
}
|
|
34
|
+
];
|
|
35
|
+
export function runUniversalPatterns(code, filePath) {
|
|
36
|
+
const findings = [];
|
|
37
|
+
const lines = code.split(/\r?\n/);
|
|
38
|
+
for (let i = 0; i < lines.length; i += 1) {
|
|
39
|
+
const line = lines[i];
|
|
40
|
+
for (const rule of PATTERNS) {
|
|
41
|
+
if (rule.pattern.test(line)) {
|
|
42
|
+
findings.push({
|
|
43
|
+
id: rule.id,
|
|
44
|
+
severity: rule.severity,
|
|
45
|
+
owasp: rule.owasp,
|
|
46
|
+
title: rule.title,
|
|
47
|
+
description: rule.description,
|
|
48
|
+
file: filePath,
|
|
49
|
+
line: i + 1
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
rule.pattern.lastIndex = 0;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return findings;
|
|
56
|
+
}
|
package/dist/cli.js
CHANGED
|
@@ -13,7 +13,7 @@ const program = new Command();
|
|
|
13
13
|
program
|
|
14
14
|
.name("opensecurity")
|
|
15
15
|
.description("openSecurity CLI")
|
|
16
|
-
.version("0.
|
|
16
|
+
.version("0.2.0");
|
|
17
17
|
program
|
|
18
18
|
.command("login")
|
|
19
19
|
.description("Store Codex Access Token in global config")
|
|
@@ -72,6 +72,14 @@ program
|
|
|
72
72
|
.option("--ai-cache", "enable AI per-file cache")
|
|
73
73
|
.option("--no-ai-cache", "disable AI per-file cache")
|
|
74
74
|
.option("--ai-cache-path <path>", "path to AI cache file")
|
|
75
|
+
.option("--native-taint", "enable native multi-language taint engine")
|
|
76
|
+
.option("--no-native-taint", "disable native multi-language taint engine")
|
|
77
|
+
.option("--native-langs <list>", "comma-separated native taint languages", (value) => value.split(",").map((item) => item.trim()).filter(Boolean))
|
|
78
|
+
.option("--native-cache", "enable native taint cache")
|
|
79
|
+
.option("--no-native-cache", "disable native taint cache")
|
|
80
|
+
.option("--native-cache-path <path>", "path to native taint cache file")
|
|
81
|
+
.option("--adapters <list>", "comma-separated external adapters (bandit,gosec,brakeman,semgrep)", (value) => value.split(",").map((item) => item.trim()).filter(Boolean))
|
|
82
|
+
.option("--disable-adapters", "disable external static adapters")
|
|
75
83
|
.option("--concurrency <n>", "parallel scan workers", (v) => Number(v))
|
|
76
84
|
.option("--dependency-only", "only run dependency/CVE scanning")
|
|
77
85
|
.option("--no-ai", "skip AI model scanning")
|
|
@@ -161,6 +169,12 @@ async function executeScan(opts) {
|
|
|
161
169
|
aiBatchDepth: opts.aiBatchDepth,
|
|
162
170
|
aiCache: opts.aiCache,
|
|
163
171
|
aiCachePath: opts.aiCachePath,
|
|
172
|
+
nativeTaint: opts.nativeTaint,
|
|
173
|
+
nativeTaintLanguages: opts.nativeLangs,
|
|
174
|
+
nativeTaintCache: opts.nativeCache,
|
|
175
|
+
nativeTaintCachePath: opts.nativeCachePath,
|
|
176
|
+
adapters: opts.adapters,
|
|
177
|
+
noAdapters: opts.disableAdapters,
|
|
164
178
|
diffOnly: opts.diffOnly,
|
|
165
179
|
diffBase: opts.diffBase,
|
|
166
180
|
concurrency: opts.concurrency
|
package/dist/config.js
CHANGED
|
@@ -10,7 +10,8 @@ export const DEFAULT_EXCLUDE = [
|
|
|
10
10
|
"**/coverage/**",
|
|
11
11
|
"**/.opensecurity.json",
|
|
12
12
|
"**/.opensecurity-cache.json",
|
|
13
|
-
"**/.opensecurity/ai-cache.json"
|
|
13
|
+
"**/.opensecurity/ai-cache.json",
|
|
14
|
+
"**/.opensecurity/native-taint-cache.json"
|
|
14
15
|
];
|
|
15
16
|
const DEFAULT_GLOBALS = {
|
|
16
17
|
baseUrl: "https://api.openai.com/v1/responses",
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
const LANGUAGES = [
|
|
2
|
+
{
|
|
3
|
+
id: "python",
|
|
4
|
+
name: "Python",
|
|
5
|
+
extensions: [".py", ".pyw"],
|
|
6
|
+
wasmFile: "tree-sitter-python.wasm",
|
|
7
|
+
nativeModule: "tree-sitter-python",
|
|
8
|
+
callNodes: ["call"],
|
|
9
|
+
callCalleeFields: ["function"],
|
|
10
|
+
callArgumentFields: ["arguments"],
|
|
11
|
+
assignmentNodes: ["assignment"],
|
|
12
|
+
assignmentLeftFields: ["left"],
|
|
13
|
+
assignmentRightFields: ["right"],
|
|
14
|
+
memberNodes: ["attribute"],
|
|
15
|
+
memberObjectFields: ["object"],
|
|
16
|
+
memberPropertyFields: ["attribute"],
|
|
17
|
+
identifierNodes: ["identifier"],
|
|
18
|
+
stringNodes: ["string", "string_literal"]
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
id: "go",
|
|
22
|
+
name: "Go",
|
|
23
|
+
extensions: [".go"],
|
|
24
|
+
wasmFile: "tree-sitter-go.wasm",
|
|
25
|
+
nativeModule: "tree-sitter-go",
|
|
26
|
+
callNodes: ["call_expression"],
|
|
27
|
+
callCalleeFields: ["function"],
|
|
28
|
+
callArgumentFields: ["arguments"],
|
|
29
|
+
assignmentNodes: ["assignment_statement"],
|
|
30
|
+
assignmentLeftFields: ["left"],
|
|
31
|
+
assignmentRightFields: ["right"],
|
|
32
|
+
memberNodes: ["selector_expression"],
|
|
33
|
+
memberObjectFields: ["operand"],
|
|
34
|
+
memberPropertyFields: ["field"],
|
|
35
|
+
identifierNodes: ["identifier"],
|
|
36
|
+
stringNodes: ["interpreted_string_literal", "raw_string_literal"]
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
id: "java",
|
|
40
|
+
name: "Java",
|
|
41
|
+
extensions: [".java"],
|
|
42
|
+
wasmFile: "tree-sitter-java.wasm",
|
|
43
|
+
nativeModule: "tree-sitter-java",
|
|
44
|
+
callNodes: ["method_invocation"],
|
|
45
|
+
callCalleeFields: ["name", "object"],
|
|
46
|
+
callArgumentFields: ["arguments"],
|
|
47
|
+
assignmentNodes: ["assignment_expression"],
|
|
48
|
+
assignmentLeftFields: ["left"],
|
|
49
|
+
assignmentRightFields: ["right"],
|
|
50
|
+
memberNodes: ["field_access"],
|
|
51
|
+
memberObjectFields: ["object"],
|
|
52
|
+
memberPropertyFields: ["field"],
|
|
53
|
+
identifierNodes: ["identifier"],
|
|
54
|
+
stringNodes: ["string_literal"]
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
id: "csharp",
|
|
58
|
+
name: "C#",
|
|
59
|
+
extensions: [".cs"],
|
|
60
|
+
wasmFile: "tree-sitter-c-sharp.wasm",
|
|
61
|
+
nativeModule: "tree-sitter-c-sharp",
|
|
62
|
+
callNodes: ["invocation_expression"],
|
|
63
|
+
callCalleeFields: ["expression"],
|
|
64
|
+
callArgumentFields: ["argument_list"],
|
|
65
|
+
assignmentNodes: ["assignment_expression"],
|
|
66
|
+
assignmentLeftFields: ["left"],
|
|
67
|
+
assignmentRightFields: ["right"],
|
|
68
|
+
memberNodes: ["member_access_expression"],
|
|
69
|
+
memberObjectFields: ["expression"],
|
|
70
|
+
memberPropertyFields: ["name"],
|
|
71
|
+
identifierNodes: ["identifier"],
|
|
72
|
+
stringNodes: ["string_literal"]
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
id: "ruby",
|
|
76
|
+
name: "Ruby",
|
|
77
|
+
extensions: [".rb"],
|
|
78
|
+
wasmFile: "tree-sitter-ruby.wasm",
|
|
79
|
+
nativeModule: "tree-sitter-ruby",
|
|
80
|
+
callNodes: ["call", "command_call"],
|
|
81
|
+
callCalleeFields: ["method", "receiver"],
|
|
82
|
+
callArgumentFields: ["arguments"],
|
|
83
|
+
assignmentNodes: ["assignment"],
|
|
84
|
+
assignmentLeftFields: ["left"],
|
|
85
|
+
assignmentRightFields: ["right"],
|
|
86
|
+
memberNodes: ["call"],
|
|
87
|
+
memberObjectFields: ["receiver"],
|
|
88
|
+
memberPropertyFields: ["method"],
|
|
89
|
+
identifierNodes: ["identifier", "constant"],
|
|
90
|
+
stringNodes: ["string", "string_literal"]
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
id: "php",
|
|
94
|
+
name: "PHP",
|
|
95
|
+
extensions: [".php", ".phtml", ".php5", ".php7", ".phps"],
|
|
96
|
+
wasmFile: "tree-sitter-php.wasm",
|
|
97
|
+
nativeModule: "tree-sitter-php",
|
|
98
|
+
callNodes: ["function_call_expression", "member_call_expression", "scoped_call_expression"],
|
|
99
|
+
callCalleeFields: ["name", "function", "member", "scope"],
|
|
100
|
+
callArgumentFields: ["arguments"],
|
|
101
|
+
assignmentNodes: ["assignment_expression"],
|
|
102
|
+
assignmentLeftFields: ["left"],
|
|
103
|
+
assignmentRightFields: ["right"],
|
|
104
|
+
memberNodes: ["member_call_expression", "scoped_call_expression"],
|
|
105
|
+
memberObjectFields: ["object", "scope"],
|
|
106
|
+
memberPropertyFields: ["name", "member"],
|
|
107
|
+
identifierNodes: ["name", "variable_name", "identifier"],
|
|
108
|
+
stringNodes: ["string", "string_literal"]
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
id: "rust",
|
|
112
|
+
name: "Rust",
|
|
113
|
+
extensions: [".rs"],
|
|
114
|
+
wasmFile: "tree-sitter-rust.wasm",
|
|
115
|
+
nativeModule: "tree-sitter-rust",
|
|
116
|
+
callNodes: ["call_expression", "macro_invocation"],
|
|
117
|
+
callCalleeFields: ["function", "macro"],
|
|
118
|
+
callArgumentFields: ["arguments", "token_tree"],
|
|
119
|
+
assignmentNodes: ["assignment_expression", "let_declaration"],
|
|
120
|
+
assignmentLeftFields: ["left", "pattern"],
|
|
121
|
+
assignmentRightFields: ["right", "value"],
|
|
122
|
+
memberNodes: ["field_expression"],
|
|
123
|
+
memberObjectFields: ["value"],
|
|
124
|
+
memberPropertyFields: ["field"],
|
|
125
|
+
identifierNodes: ["identifier", "self"],
|
|
126
|
+
stringNodes: ["string_literal", "raw_string_literal"]
|
|
127
|
+
},
|
|
128
|
+
{
|
|
129
|
+
id: "kotlin",
|
|
130
|
+
name: "Kotlin",
|
|
131
|
+
extensions: [".kt", ".kts"],
|
|
132
|
+
wasmFile: "tree-sitter-kotlin.wasm",
|
|
133
|
+
nativeModule: "tree-sitter-kotlin",
|
|
134
|
+
callNodes: ["call_expression", "primary_expression"],
|
|
135
|
+
callCalleeFields: ["callee", "reference"],
|
|
136
|
+
callArgumentFields: ["value_arguments"],
|
|
137
|
+
assignmentNodes: ["assignment"],
|
|
138
|
+
assignmentLeftFields: ["left"],
|
|
139
|
+
assignmentRightFields: ["right"],
|
|
140
|
+
memberNodes: ["navigation_expression"],
|
|
141
|
+
memberObjectFields: ["receiver"],
|
|
142
|
+
memberPropertyFields: ["selector"],
|
|
143
|
+
identifierNodes: ["identifier"],
|
|
144
|
+
stringNodes: ["string_literal"]
|
|
145
|
+
},
|
|
146
|
+
{
|
|
147
|
+
id: "swift",
|
|
148
|
+
name: "Swift",
|
|
149
|
+
extensions: [".swift"],
|
|
150
|
+
wasmFile: "tree-sitter-swift.wasm",
|
|
151
|
+
nativeModule: "tree-sitter-swift",
|
|
152
|
+
callNodes: ["function_call_expression"],
|
|
153
|
+
callCalleeFields: ["function"],
|
|
154
|
+
callArgumentFields: ["argument_clause"],
|
|
155
|
+
assignmentNodes: ["assignment_expression"],
|
|
156
|
+
assignmentLeftFields: ["left"],
|
|
157
|
+
assignmentRightFields: ["right"],
|
|
158
|
+
memberNodes: ["member_access_expression"],
|
|
159
|
+
memberObjectFields: ["object"],
|
|
160
|
+
memberPropertyFields: ["name"],
|
|
161
|
+
identifierNodes: ["identifier"],
|
|
162
|
+
stringNodes: ["string_literal"]
|
|
163
|
+
},
|
|
164
|
+
{
|
|
165
|
+
id: "c",
|
|
166
|
+
name: "C",
|
|
167
|
+
extensions: [".c", ".h"],
|
|
168
|
+
wasmFile: "tree-sitter-c.wasm",
|
|
169
|
+
nativeModule: "tree-sitter-c",
|
|
170
|
+
callNodes: ["call_expression"],
|
|
171
|
+
callCalleeFields: ["function"],
|
|
172
|
+
callArgumentFields: ["arguments"],
|
|
173
|
+
assignmentNodes: ["assignment_expression", "init_declarator"],
|
|
174
|
+
assignmentLeftFields: ["left", "declarator"],
|
|
175
|
+
assignmentRightFields: ["right", "value"],
|
|
176
|
+
memberNodes: ["field_expression"],
|
|
177
|
+
memberObjectFields: ["argument"],
|
|
178
|
+
memberPropertyFields: ["field"],
|
|
179
|
+
identifierNodes: ["identifier"],
|
|
180
|
+
stringNodes: ["string_literal"]
|
|
181
|
+
},
|
|
182
|
+
{
|
|
183
|
+
id: "cpp",
|
|
184
|
+
name: "C++",
|
|
185
|
+
extensions: [".cpp", ".hpp", ".cc", ".cxx", ".hh", ".hxx"],
|
|
186
|
+
wasmFile: "tree-sitter-cpp.wasm",
|
|
187
|
+
nativeModule: "tree-sitter-cpp",
|
|
188
|
+
callNodes: ["call_expression"],
|
|
189
|
+
callCalleeFields: ["function"],
|
|
190
|
+
callArgumentFields: ["arguments"],
|
|
191
|
+
assignmentNodes: ["assignment_expression", "init_declarator"],
|
|
192
|
+
assignmentLeftFields: ["left", "declarator"],
|
|
193
|
+
assignmentRightFields: ["right", "value"],
|
|
194
|
+
memberNodes: ["field_expression", "qualified_identifier"],
|
|
195
|
+
memberObjectFields: ["scope", "argument"],
|
|
196
|
+
memberPropertyFields: ["name", "field"],
|
|
197
|
+
identifierNodes: ["identifier"],
|
|
198
|
+
stringNodes: ["string_literal"]
|
|
199
|
+
}
|
|
200
|
+
];
|
|
201
|
+
export function getNativeLanguages() {
|
|
202
|
+
return [...LANGUAGES];
|
|
203
|
+
}
|
|
204
|
+
export function getLanguageByExtension(filePath) {
|
|
205
|
+
const lower = filePath.toLowerCase();
|
|
206
|
+
for (const lang of LANGUAGES) {
|
|
207
|
+
if (lang.extensions.some((ext) => lower.endsWith(ext)))
|
|
208
|
+
return lang;
|
|
209
|
+
}
|
|
210
|
+
return null;
|
|
211
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import fs from "node:fs/promises";
|
|
3
|
+
import { createRequire } from "node:module";
|
|
4
|
+
let treeSitter = null;
|
|
5
|
+
let treeSitterInit = null;
|
|
6
|
+
async function ensureTreeSitter() {
|
|
7
|
+
if (treeSitter)
|
|
8
|
+
return treeSitter;
|
|
9
|
+
const mod = await import("web-tree-sitter");
|
|
10
|
+
const resolved = {
|
|
11
|
+
init: mod.init?.bind(mod),
|
|
12
|
+
Language: mod.Language,
|
|
13
|
+
Parser: mod.Parser
|
|
14
|
+
};
|
|
15
|
+
treeSitter = resolved;
|
|
16
|
+
if (!treeSitterInit) {
|
|
17
|
+
treeSitterInit = resolved.init();
|
|
18
|
+
}
|
|
19
|
+
await treeSitterInit;
|
|
20
|
+
return resolved;
|
|
21
|
+
}
|
|
22
|
+
async function loadNativeLanguage(lang) {
|
|
23
|
+
try {
|
|
24
|
+
const require = createRequire(import.meta.url);
|
|
25
|
+
const Parser = require("tree-sitter");
|
|
26
|
+
const Lang = require(lang.nativeModule);
|
|
27
|
+
const parser = new Parser();
|
|
28
|
+
parser.setLanguage(Lang);
|
|
29
|
+
return { parser, type: "native" };
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
async function loadWasmLanguage(lang, baseDir) {
|
|
36
|
+
const mod = await ensureTreeSitter();
|
|
37
|
+
const wasmPath = path.join(baseDir, "assets", "grammars", lang.wasmFile);
|
|
38
|
+
try {
|
|
39
|
+
await fs.access(wasmPath);
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
const language = await mod.Language.load(wasmPath);
|
|
45
|
+
const parser = new mod.Parser();
|
|
46
|
+
parser.setLanguage(language);
|
|
47
|
+
return { parser, type: "wasm" };
|
|
48
|
+
}
|
|
49
|
+
export async function parseWithTreeSitter(source, lang, baseDir) {
|
|
50
|
+
const native = await loadNativeLanguage(lang);
|
|
51
|
+
if (native) {
|
|
52
|
+
const tree = native.parser.parse(source);
|
|
53
|
+
return { tree, source, language: lang };
|
|
54
|
+
}
|
|
55
|
+
const wasm = await loadWasmLanguage(lang, baseDir);
|
|
56
|
+
if (wasm) {
|
|
57
|
+
const tree = wasm.parser.parse(source);
|
|
58
|
+
return { tree, source, language: lang };
|
|
59
|
+
}
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import fs from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
export async function loadNativeRules(baseDir, lang) {
|
|
4
|
+
const rulePath = path.join(baseDir, "rules", "taint", `${lang}.json`);
|
|
5
|
+
try {
|
|
6
|
+
const raw = await fs.readFile(rulePath, "utf8");
|
|
7
|
+
return JSON.parse(raw);
|
|
8
|
+
}
|
|
9
|
+
catch (err) {
|
|
10
|
+
if (err?.code === "ENOENT")
|
|
11
|
+
return null;
|
|
12
|
+
throw err;
|
|
13
|
+
}
|
|
14
|
+
}
|