opensecurity 0.2.0 → 0.3.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.
Files changed (39) hide show
  1. package/README.md +2 -1
  2. package/dist/adapters/semgrep.js +1 -1
  3. package/dist/core/config.js +72 -0
  4. package/dist/core/scan.js +1336 -0
  5. package/dist/engines/analysis/ast.js +20 -0
  6. package/dist/engines/analysis/graphs.js +300 -0
  7. package/dist/engines/analysis/infraPatterns.js +196 -0
  8. package/dist/engines/analysis/patterns.js +237 -0
  9. package/dist/engines/analysis/rules.js +48 -0
  10. package/dist/engines/analysis/taint.js +294 -0
  11. package/dist/engines/analysis/universalPatterns.js +56 -0
  12. package/dist/engines/deps/cve.js +102 -0
  13. package/dist/engines/deps/engine.js +27 -0
  14. package/dist/engines/deps/patch.js +11 -0
  15. package/dist/engines/deps/scanners.js +114 -0
  16. package/dist/engines/deps/scoring.js +46 -0
  17. package/dist/engines/deps/simulate.js +9 -0
  18. package/dist/engines/deps/types.js +1 -0
  19. package/dist/engines/native/languages.js +222 -0
  20. package/dist/engines/native/loader.js +61 -0
  21. package/dist/engines/native/rules.js +14 -0
  22. package/dist/engines/native/taint.js +312 -0
  23. package/dist/engines/rules/defaultRules.js +177 -0
  24. package/dist/engines/rules/loadRules.js +14 -0
  25. package/dist/io/fileWalker.js +27 -0
  26. package/dist/io/login.js +583 -0
  27. package/dist/io/oauthStore.js +48 -0
  28. package/dist/io/proxy.js +93 -0
  29. package/dist/io/telemetry.js +72 -0
  30. package/dist/ui/cli.js +410 -0
  31. package/dist/ui/pr-comment.js +118 -0
  32. package/dist/ui/progress.js +150 -0
  33. package/package.json +30 -5
  34. package/rules/taint/c.json +38 -2
  35. package/rules/taint/cpp.json +38 -2
  36. package/rules/taint/go.json +16 -0
  37. package/rules/taint/kotlin.json +15 -0
  38. package/rules/taint/rust.json +16 -0
  39. package/rules/taint/swift.json +15 -0
@@ -0,0 +1,150 @@
1
+ /**
2
+ * CLI UX utilities: progress spinner, verbose logging, and formatting.
3
+ */
4
+ const SPINNER_FRAMES = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
5
+ const SPINNER_INTERVAL = 80;
6
+ export class Logger {
7
+ level;
8
+ constructor(options = {}) {
9
+ if (options.silent) {
10
+ this.level = "silent";
11
+ }
12
+ else if (options.verbose) {
13
+ this.level = "verbose";
14
+ }
15
+ else {
16
+ this.level = "normal";
17
+ }
18
+ }
19
+ info(message) {
20
+ if (this.level === "silent")
21
+ return;
22
+ console.error(`ℹ ${message}`);
23
+ }
24
+ verbose(message) {
25
+ if (this.level !== "verbose")
26
+ return;
27
+ console.error(` ${dim(message)}`);
28
+ }
29
+ success(message) {
30
+ if (this.level === "silent")
31
+ return;
32
+ console.error(`✅ ${message}`);
33
+ }
34
+ warn(message) {
35
+ if (this.level === "silent")
36
+ return;
37
+ console.error(`⚠️ ${message}`);
38
+ }
39
+ error(message) {
40
+ console.error(`❌ ${message}`);
41
+ }
42
+ }
43
+ export class Spinner {
44
+ frame = 0;
45
+ timer = null;
46
+ message;
47
+ stream;
48
+ active = false;
49
+ constructor(message) {
50
+ this.message = message;
51
+ this.stream = process.stderr;
52
+ }
53
+ start() {
54
+ if (!this.stream.isTTY)
55
+ return;
56
+ this.active = true;
57
+ this.render();
58
+ this.timer = setInterval(() => this.render(), SPINNER_INTERVAL);
59
+ }
60
+ update(message) {
61
+ this.message = message;
62
+ }
63
+ pause() {
64
+ if (this.timer) {
65
+ clearInterval(this.timer);
66
+ this.timer = null;
67
+ }
68
+ if (this.active) {
69
+ this.clearLine();
70
+ }
71
+ }
72
+ resume() {
73
+ if (!this.active || !this.stream.isTTY)
74
+ return;
75
+ if (this.timer)
76
+ return;
77
+ this.render();
78
+ this.timer = setInterval(() => this.render(), SPINNER_INTERVAL);
79
+ }
80
+ stop(finalMessage) {
81
+ if (this.timer) {
82
+ clearInterval(this.timer);
83
+ this.timer = null;
84
+ }
85
+ if (this.active) {
86
+ this.clearLine();
87
+ if (finalMessage) {
88
+ this.stream.write(`${finalMessage}\n`);
89
+ }
90
+ }
91
+ this.active = false;
92
+ }
93
+ render() {
94
+ const symbol = SPINNER_FRAMES[this.frame % SPINNER_FRAMES.length];
95
+ this.frame += 1;
96
+ this.clearLine();
97
+ this.stream.write(`${symbol} ${this.message}`);
98
+ }
99
+ clearLine() {
100
+ this.stream.write("\r\x1b[K");
101
+ }
102
+ }
103
+ /**
104
+ * Format duration in human-readable form.
105
+ */
106
+ export function formatDuration(ms) {
107
+ if (ms < 1000)
108
+ return `${Math.round(ms)}ms`;
109
+ const seconds = ms / 1000;
110
+ if (seconds < 60)
111
+ return `${seconds.toFixed(1)}s`;
112
+ const minutes = Math.floor(seconds / 60);
113
+ const remainingSeconds = Math.round(seconds % 60);
114
+ return `${minutes}m ${remainingSeconds}s`;
115
+ }
116
+ /**
117
+ * Format a count with plural suffix.
118
+ */
119
+ export function pluralize(count, singular, plural) {
120
+ return count === 1 ? `${count} ${singular}` : `${count} ${plural ?? singular + "s"}`;
121
+ }
122
+ /**
123
+ * Dim text using ANSI escape codes (stderr only).
124
+ */
125
+ function dim(text) {
126
+ return `\x1b[2m${text}\x1b[0m`;
127
+ }
128
+ /**
129
+ * Bold text using ANSI escape codes.
130
+ */
131
+ export function bold(text) {
132
+ return `\x1b[1m${text}\x1b[0m`;
133
+ }
134
+ /**
135
+ * Colored severity label.
136
+ */
137
+ export function severityColor(severity) {
138
+ switch (severity) {
139
+ case "critical":
140
+ return `\x1b[31m${severity.toUpperCase()}\x1b[0m`; // red
141
+ case "high":
142
+ return `\x1b[33m${severity.toUpperCase()}\x1b[0m`; // yellow
143
+ case "medium":
144
+ return `\x1b[36m${severity.toUpperCase()}\x1b[0m`; // cyan
145
+ case "low":
146
+ return `\x1b[34m${severity.toUpperCase()}\x1b[0m`; // blue
147
+ default:
148
+ return severity.toUpperCase();
149
+ }
150
+ }
package/package.json CHANGED
@@ -1,15 +1,40 @@
1
1
  {
2
2
  "name": "opensecurity",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "private": false,
5
+ "description": "Open-source CLI for scanning repositories for security risks across code, infra, and dependencies.",
6
+ "license": "MIT",
5
7
  "type": "module",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "https://github.com/eliophan/opensecurity.git"
11
+ },
12
+ "homepage": "https://github.com/eliophan/opensecurity",
13
+ "bugs": {
14
+ "url": "https://github.com/eliophan/opensecurity/issues"
15
+ },
16
+ "keywords": [
17
+ "security",
18
+ "sast",
19
+ "static-analysis",
20
+ "taint",
21
+ "vulnerability",
22
+ "scanner",
23
+ "devsecops",
24
+ "cli",
25
+ "dependency",
26
+ "ai"
27
+ ],
28
+ "engines": {
29
+ "node": ">=20"
30
+ },
6
31
  "bin": {
7
- "opensecurity": "dist/cli.js",
8
- "openSecurity": "dist/cli.js"
32
+ "opensecurity": "dist/ui/cli.js",
33
+ "openSecurity": "dist/ui/cli.js"
9
34
  },
10
35
  "scripts": {
11
- "dev": "tsx src/cli.ts",
12
- "proxy": "tsx src/proxy.ts",
36
+ "dev": "tsx src/ui/cli.ts",
37
+ "proxy": "tsx src/io/proxy.ts",
13
38
  "build": "tsc",
14
39
  "build-grammars": "tsx scripts/build-grammars.ts",
15
40
  "prepack": "npm run build",
@@ -8,7 +8,9 @@
8
8
  "owasp": "A03:2021 Injection",
9
9
  "kind": "taint",
10
10
  "sources": [
11
- { "id": "argv", "name": "argv", "matcher": { "calleePattern": ["argv", "getenv"] } }
11
+ { "id": "getenv", "name": "getenv", "matcher": { "callee": ["getenv"] } },
12
+ { "id": "scanf", "name": "scanf", "matcher": { "calleePattern": ["scanf", "fscanf", "sscanf"] } },
13
+ { "id": "gets", "name": "gets", "matcher": { "callee": ["gets", "fgets"] } }
12
14
  ],
13
15
  "sinks": [
14
16
  { "id": "system", "name": "system", "matcher": { "callee": ["system", "popen"] } }
@@ -21,12 +23,46 @@
21
23
  "owasp": "A01:2021 Broken Access Control",
22
24
  "kind": "taint",
23
25
  "sources": [
24
- { "id": "argv", "name": "argv", "matcher": { "calleePattern": ["argv", "getenv"] } }
26
+ { "id": "getenv", "name": "getenv", "matcher": { "callee": ["getenv"] } },
27
+ { "id": "scanf", "name": "scanf", "matcher": { "calleePattern": ["scanf", "fscanf", "sscanf"] } },
28
+ { "id": "gets", "name": "gets", "matcher": { "callee": ["gets", "fgets"] } }
25
29
  ],
26
30
  "sinks": [
27
31
  { "id": "fopen", "name": "fopen", "matcher": { "callee": ["fopen", "open", "read"] } }
28
32
  ]
29
33
  },
34
+ {
35
+ "id": "c-sqli",
36
+ "title": "SQL Injection",
37
+ "severity": "high",
38
+ "owasp": "A03:2021 Injection",
39
+ "kind": "taint",
40
+ "sources": [
41
+ { "id": "getenv", "name": "getenv", "matcher": { "callee": ["getenv"] } },
42
+ { "id": "scanf", "name": "scanf", "matcher": { "calleePattern": ["scanf", "fscanf", "sscanf"] } },
43
+ { "id": "gets", "name": "gets", "matcher": { "callee": ["gets", "fgets"] } }
44
+ ],
45
+ "sinks": [
46
+ { "id": "sqlite", "name": "sqlite3_exec", "matcher": { "callee": ["sqlite3_exec"] } },
47
+ { "id": "mysql", "name": "mysql_query", "matcher": { "callee": ["mysql_query"] } },
48
+ { "id": "pq", "name": "PQexec", "matcher": { "callee": ["PQexec"] } }
49
+ ]
50
+ },
51
+ {
52
+ "id": "c-ssrf",
53
+ "title": "Server-Side Request Forgery",
54
+ "severity": "high",
55
+ "owasp": "A10:2021 Server-Side Request Forgery",
56
+ "kind": "taint",
57
+ "sources": [
58
+ { "id": "getenv", "name": "getenv", "matcher": { "callee": ["getenv"] } },
59
+ { "id": "scanf", "name": "scanf", "matcher": { "calleePattern": ["scanf", "fscanf", "sscanf"] } },
60
+ { "id": "gets", "name": "gets", "matcher": { "callee": ["gets", "fgets"] } }
61
+ ],
62
+ "sinks": [
63
+ { "id": "curl", "name": "curl_easy_perform", "matcher": { "callee": ["curl_easy_perform"] } }
64
+ ]
65
+ },
30
66
  {
31
67
  "id": "c-weak-crypto",
32
68
  "title": "Weak Crypto",
@@ -8,7 +8,9 @@
8
8
  "owasp": "A03:2021 Injection",
9
9
  "kind": "taint",
10
10
  "sources": [
11
- { "id": "argv", "name": "argv", "matcher": { "calleePattern": ["argv", "getenv"] } }
11
+ { "id": "getenv", "name": "getenv", "matcher": { "callee": ["getenv"] } },
12
+ { "id": "scanf", "name": "scanf", "matcher": { "calleePattern": ["scanf", "fscanf", "sscanf"] } },
13
+ { "id": "gets", "name": "gets", "matcher": { "callee": ["gets", "fgets"] } }
12
14
  ],
13
15
  "sinks": [
14
16
  { "id": "system", "name": "system", "matcher": { "callee": ["system", "popen"] } }
@@ -21,12 +23,46 @@
21
23
  "owasp": "A01:2021 Broken Access Control",
22
24
  "kind": "taint",
23
25
  "sources": [
24
- { "id": "argv", "name": "argv", "matcher": { "calleePattern": ["argv", "getenv"] } }
26
+ { "id": "getenv", "name": "getenv", "matcher": { "callee": ["getenv"] } },
27
+ { "id": "scanf", "name": "scanf", "matcher": { "calleePattern": ["scanf", "fscanf", "sscanf"] } },
28
+ { "id": "gets", "name": "gets", "matcher": { "callee": ["gets", "fgets"] } }
25
29
  ],
26
30
  "sinks": [
27
31
  { "id": "fstream", "name": "fstream", "matcher": { "calleePattern": ["std::fstream", "std::ifstream", "std::ofstream", "fopen", "open"] } }
28
32
  ]
29
33
  },
34
+ {
35
+ "id": "cpp-sqli",
36
+ "title": "SQL Injection",
37
+ "severity": "high",
38
+ "owasp": "A03:2021 Injection",
39
+ "kind": "taint",
40
+ "sources": [
41
+ { "id": "getenv", "name": "getenv", "matcher": { "callee": ["getenv"] } },
42
+ { "id": "scanf", "name": "scanf", "matcher": { "calleePattern": ["scanf", "fscanf", "sscanf"] } },
43
+ { "id": "gets", "name": "gets", "matcher": { "callee": ["gets", "fgets"] } }
44
+ ],
45
+ "sinks": [
46
+ { "id": "sqlite", "name": "sqlite3_exec", "matcher": { "callee": ["sqlite3_exec"] } },
47
+ { "id": "mysql", "name": "mysql_query", "matcher": { "callee": ["mysql_query"] } },
48
+ { "id": "pq", "name": "PQexec", "matcher": { "callee": ["PQexec"] } }
49
+ ]
50
+ },
51
+ {
52
+ "id": "cpp-ssrf",
53
+ "title": "Server-Side Request Forgery",
54
+ "severity": "high",
55
+ "owasp": "A10:2021 Server-Side Request Forgery",
56
+ "kind": "taint",
57
+ "sources": [
58
+ { "id": "getenv", "name": "getenv", "matcher": { "callee": ["getenv"] } },
59
+ { "id": "scanf", "name": "scanf", "matcher": { "calleePattern": ["scanf", "fscanf", "sscanf"] } },
60
+ { "id": "gets", "name": "gets", "matcher": { "callee": ["gets", "fgets"] } }
61
+ ],
62
+ "sinks": [
63
+ { "id": "curl", "name": "curl_easy_perform", "matcher": { "callee": ["curl_easy_perform"] } }
64
+ ]
65
+ },
30
66
  {
31
67
  "id": "cpp-weak-crypto",
32
68
  "title": "Weak Crypto",
@@ -53,6 +53,22 @@
53
53
  { "id": "http", "name": "http.Get", "matcher": { "calleePattern": ["http.Get", "http.Post", "http.NewRequest"] } }
54
54
  ]
55
55
  },
56
+ {
57
+ "id": "go-deser",
58
+ "title": "Unsafe Deserialization",
59
+ "severity": "high",
60
+ "owasp": "A08:2021 Software and Data Integrity Failures",
61
+ "kind": "taint",
62
+ "sources": [
63
+ { "id": "query", "name": "r.URL.Query", "matcher": { "calleePattern": ["*.Query", "*.Query().Get", "*.FormValue"] } }
64
+ ],
65
+ "sinks": [
66
+ { "id": "json", "name": "json.Unmarshal", "matcher": { "calleePattern": ["json.Unmarshal"] } },
67
+ { "id": "xml", "name": "xml.Unmarshal", "matcher": { "calleePattern": ["xml.Unmarshal"] } },
68
+ { "id": "yaml", "name": "yaml.Unmarshal", "matcher": { "calleePattern": ["yaml.Unmarshal"] } },
69
+ { "id": "gob", "name": "gob.Decoder.Decode", "matcher": { "calleePattern": ["gob.Decoder.Decode"] } }
70
+ ]
71
+ },
56
72
  {
57
73
  "id": "go-xss-template",
58
74
  "title": "Server Template XSS",
@@ -53,6 +53,21 @@
53
53
  { "id": "url", "name": "URL.openConnection", "matcher": { "calleePattern": ["*.openConnection"] } }
54
54
  ]
55
55
  },
56
+ {
57
+ "id": "kt-deser",
58
+ "title": "Unsafe Deserialization",
59
+ "severity": "high",
60
+ "owasp": "A08:2021 Software and Data Integrity Failures",
61
+ "kind": "taint",
62
+ "sources": [
63
+ { "id": "param", "name": "request.getParameter", "matcher": { "calleePattern": ["*.getParameter", "*.getHeader"] } }
64
+ ],
65
+ "sinks": [
66
+ { "id": "java-io", "name": "ObjectInputStream.readObject", "matcher": { "calleePattern": ["ObjectInputStream.readObject", "*.readObject"] } },
67
+ { "id": "jackson", "name": "ObjectMapper.readValue", "matcher": { "calleePattern": ["ObjectMapper.readValue", "*.readValue"] } },
68
+ { "id": "gson", "name": "Gson.fromJson", "matcher": { "calleePattern": ["Gson.fromJson", "*.fromJson"] } }
69
+ ]
70
+ },
56
71
  {
57
72
  "id": "kt-xss-template",
58
73
  "title": "Server Template XSS",
@@ -53,6 +53,22 @@
53
53
  { "id": "reqwest", "name": "reqwest", "matcher": { "calleePattern": ["reqwest::get", "reqwest::Client.get", "reqwest::Client.post"] } }
54
54
  ]
55
55
  },
56
+ {
57
+ "id": "rs-deser",
58
+ "title": "Unsafe Deserialization",
59
+ "severity": "high",
60
+ "owasp": "A08:2021 Software and Data Integrity Failures",
61
+ "kind": "taint",
62
+ "sources": [
63
+ { "id": "query", "name": "web::Query", "matcher": { "calleePattern": ["Query::*", "Form::*"] } }
64
+ ],
65
+ "sinks": [
66
+ { "id": "serde-json", "name": "serde_json::from_str", "matcher": { "calleePattern": ["serde_json::from_str", "serde_json::from_slice"] } },
67
+ { "id": "serde-yaml", "name": "serde_yaml::from_str", "matcher": { "calleePattern": ["serde_yaml::from_str", "serde_yaml::from_slice"] } },
68
+ { "id": "bincode", "name": "bincode::deserialize", "matcher": { "calleePattern": ["bincode::deserialize", "bincode::deserialize_from"] } },
69
+ { "id": "rmp", "name": "rmp_serde::from_slice", "matcher": { "calleePattern": ["rmp_serde::from_slice", "rmp_serde::from_read"] } }
70
+ ]
71
+ },
56
72
  {
57
73
  "id": "rs-xss-template",
58
74
  "title": "Server Template XSS",
@@ -53,6 +53,21 @@
53
53
  { "id": "urlsession", "name": "URLSession", "matcher": { "calleePattern": ["URLSession.shared.dataTask", "URLSession.dataTask"] } }
54
54
  ]
55
55
  },
56
+ {
57
+ "id": "swift-deser",
58
+ "title": "Unsafe Deserialization",
59
+ "severity": "high",
60
+ "owasp": "A08:2021 Software and Data Integrity Failures",
61
+ "kind": "taint",
62
+ "sources": [
63
+ { "id": "param", "name": "request.query", "matcher": { "calleePattern": ["request.query*", "request.form*"] } }
64
+ ],
65
+ "sinks": [
66
+ { "id": "nskeyed", "name": "NSKeyedUnarchiver", "matcher": { "calleePattern": ["NSKeyedUnarchiver.unarchiveTopLevelObjectWithData", "NSKeyedUnarchiver.unarchiveObject"] } },
67
+ { "id": "plist", "name": "PropertyListSerialization", "matcher": { "calleePattern": ["PropertyListSerialization.propertyList"] } },
68
+ { "id": "json", "name": "JSONSerialization", "matcher": { "calleePattern": ["JSONSerialization.jsonObject"] } }
69
+ ]
70
+ },
56
71
  {
57
72
  "id": "swift-xss-template",
58
73
  "title": "Server Template XSS",