insa-check-core 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +24 -0
- package/dist/annex-b.d.ts +6 -0
- package/dist/annex-b.d.ts.map +1 -0
- package/dist/annex-b.js +152 -0
- package/dist/annex-b.js.map +1 -0
- package/dist/checks/content/page.d.ts +7 -0
- package/dist/checks/content/page.d.ts.map +1 -0
- package/dist/checks/content/page.js +133 -0
- package/dist/checks/content/page.js.map +1 -0
- package/dist/checks/cookies/cookie-flags.d.ts +3 -0
- package/dist/checks/cookies/cookie-flags.d.ts.map +1 -0
- package/dist/checks/cookies/cookie-flags.js +41 -0
- package/dist/checks/cookies/cookie-flags.js.map +1 -0
- package/dist/checks/cookies/cookie-scope.d.ts +6 -0
- package/dist/checks/cookies/cookie-scope.d.ts.map +1 -0
- package/dist/checks/cookies/cookie-scope.js +60 -0
- package/dist/checks/cookies/cookie-scope.js.map +1 -0
- package/dist/checks/deployment/server-banner.d.ts +4 -0
- package/dist/checks/deployment/server-banner.d.ts.map +1 -0
- package/dist/checks/deployment/server-banner.js +60 -0
- package/dist/checks/deployment/server-banner.js.map +1 -0
- package/dist/checks/deployment/trace.d.ts +3 -0
- package/dist/checks/deployment/trace.d.ts.map +1 -0
- package/dist/checks/deployment/trace.js +30 -0
- package/dist/checks/deployment/trace.js.map +1 -0
- package/dist/checks/headers/clickjacking.d.ts +5 -0
- package/dist/checks/headers/clickjacking.d.ts.map +1 -0
- package/dist/checks/headers/clickjacking.js +57 -0
- package/dist/checks/headers/clickjacking.js.map +1 -0
- package/dist/checks/headers/content-type-options.d.ts +3 -0
- package/dist/checks/headers/content-type-options.d.ts.map +1 -0
- package/dist/checks/headers/content-type-options.js +17 -0
- package/dist/checks/headers/content-type-options.js.map +1 -0
- package/dist/checks/headers/cors.d.ts +4 -0
- package/dist/checks/headers/cors.d.ts.map +1 -0
- package/dist/checks/headers/cors.js +38 -0
- package/dist/checks/headers/cors.js.map +1 -0
- package/dist/checks/html.d.ts +11 -0
- package/dist/checks/html.d.ts.map +1 -0
- package/dist/checks/html.js +30 -0
- package/dist/checks/html.js.map +1 -0
- package/dist/checks/index.d.ts +7 -0
- package/dist/checks/index.d.ts.map +1 -0
- package/dist/checks/index.js +48 -0
- package/dist/checks/index.js.map +1 -0
- package/dist/checks/info-disclosure/directory-listing.d.ts +6 -0
- package/dist/checks/info-disclosure/directory-listing.d.ts.map +1 -0
- package/dist/checks/info-disclosure/directory-listing.js +60 -0
- package/dist/checks/info-disclosure/directory-listing.js.map +1 -0
- package/dist/checks/info-disclosure/exposed-files.d.ts +4 -0
- package/dist/checks/info-disclosure/exposed-files.d.ts.map +1 -0
- package/dist/checks/info-disclosure/exposed-files.js +56 -0
- package/dist/checks/info-disclosure/exposed-files.js.map +1 -0
- package/dist/checks/info-disclosure/more.d.ts +8 -0
- package/dist/checks/info-disclosure/more.d.ts.map +1 -0
- package/dist/checks/info-disclosure/more.js +186 -0
- package/dist/checks/info-disclosure/more.js.map +1 -0
- package/dist/checks/tls/certificate.d.ts +3 -0
- package/dist/checks/tls/certificate.d.ts.map +1 -0
- package/dist/checks/tls/certificate.js +49 -0
- package/dist/checks/tls/certificate.js.map +1 -0
- package/dist/checks/tls/hsts.d.ts +5 -0
- package/dist/checks/tls/hsts.d.ts.map +1 -0
- package/dist/checks/tls/hsts.js +35 -0
- package/dist/checks/tls/hsts.js.map +1 -0
- package/dist/checks/tls/https-redirect.d.ts +3 -0
- package/dist/checks/tls/https-redirect.d.ts.map +1 -0
- package/dist/checks/tls/https-redirect.js +46 -0
- package/dist/checks/tls/https-redirect.js.map +1 -0
- package/dist/checks/tls/legacy-protocols.d.ts +3 -0
- package/dist/checks/tls/legacy-protocols.d.ts.map +1 -0
- package/dist/checks/tls/legacy-protocols.js +27 -0
- package/dist/checks/tls/legacy-protocols.js.map +1 -0
- package/dist/checks/util.d.ts +22 -0
- package/dist/checks/util.d.ts.map +1 -0
- package/dist/checks/util.js +63 -0
- package/dist/checks/util.js.map +1 -0
- package/dist/code/checks/crypto.d.ts +5 -0
- package/dist/code/checks/crypto.d.ts.map +1 -0
- package/dist/code/checks/crypto.js +100 -0
- package/dist/code/checks/crypto.js.map +1 -0
- package/dist/code/checks/django.d.ts +14 -0
- package/dist/code/checks/django.d.ts.map +1 -0
- package/dist/code/checks/django.js +313 -0
- package/dist/code/checks/django.js.map +1 -0
- package/dist/code/checks/flask.d.ts +6 -0
- package/dist/code/checks/flask.d.ts.map +1 -0
- package/dist/code/checks/flask.js +100 -0
- package/dist/code/checks/flask.js.map +1 -0
- package/dist/code/checks/index.d.ts +9 -0
- package/dist/code/checks/index.d.ts.map +1 -0
- package/dist/code/checks/index.js +126 -0
- package/dist/code/checks/index.js.map +1 -0
- package/dist/code/checks/injection.d.ts +27 -0
- package/dist/code/checks/injection.d.ts.map +1 -0
- package/dist/code/checks/injection.js +189 -0
- package/dist/code/checks/injection.js.map +1 -0
- package/dist/code/checks/javascript-config.d.ts +14 -0
- package/dist/code/checks/javascript-config.d.ts.map +1 -0
- package/dist/code/checks/javascript-config.js +251 -0
- package/dist/code/checks/javascript-config.js.map +1 -0
- package/dist/code/checks/javascript.d.ts +34 -0
- package/dist/code/checks/javascript.d.ts.map +1 -0
- package/dist/code/checks/javascript.js +333 -0
- package/dist/code/checks/javascript.js.map +1 -0
- package/dist/code/checks/php.d.ts +27 -0
- package/dist/code/checks/php.d.ts.map +1 -0
- package/dist/code/checks/php.js +433 -0
- package/dist/code/checks/php.js.map +1 -0
- package/dist/code/checks/secrets.d.ts +10 -0
- package/dist/code/checks/secrets.d.ts.map +1 -0
- package/dist/code/checks/secrets.js +140 -0
- package/dist/code/checks/secrets.js.map +1 -0
- package/dist/code/checks/shared.d.ts +25 -0
- package/dist/code/checks/shared.d.ts.map +1 -0
- package/dist/code/checks/shared.js +57 -0
- package/dist/code/checks/shared.js.map +1 -0
- package/dist/code/checks/treesitter.d.ts +15 -0
- package/dist/code/checks/treesitter.d.ts.map +1 -0
- package/dist/code/checks/treesitter.js +322 -0
- package/dist/code/checks/treesitter.js.map +1 -0
- package/dist/code/checks/xml.d.ts +9 -0
- package/dist/code/checks/xml.d.ts.map +1 -0
- package/dist/code/checks/xml.js +78 -0
- package/dist/code/checks/xml.js.map +1 -0
- package/dist/code/context.d.ts +6 -0
- package/dist/code/context.d.ts.map +1 -0
- package/dist/code/context.js +92 -0
- package/dist/code/context.js.map +1 -0
- package/dist/code/detect.d.ts +3 -0
- package/dist/code/detect.d.ts.map +1 -0
- package/dist/code/detect.js +118 -0
- package/dist/code/detect.js.map +1 -0
- package/dist/code/django/settings.d.ts +46 -0
- package/dist/code/django/settings.d.ts.map +1 -0
- package/dist/code/django/settings.js +154 -0
- package/dist/code/django/settings.js.map +1 -0
- package/dist/code/findings.d.ts +18 -0
- package/dist/code/findings.d.ts.map +1 -0
- package/dist/code/findings.js +63 -0
- package/dist/code/findings.js.map +1 -0
- package/dist/code/javascript/ast.d.ts +46 -0
- package/dist/code/javascript/ast.d.ts.map +1 -0
- package/dist/code/javascript/ast.js +166 -0
- package/dist/code/javascript/ast.js.map +1 -0
- package/dist/code/javascript/index.d.ts +3 -0
- package/dist/code/javascript/index.d.ts.map +1 -0
- package/dist/code/javascript/index.js +3 -0
- package/dist/code/javascript/index.js.map +1 -0
- package/dist/code/javascript/module.d.ts +59 -0
- package/dist/code/javascript/module.d.ts.map +1 -0
- package/dist/code/javascript/module.js +250 -0
- package/dist/code/javascript/module.js.map +1 -0
- package/dist/code/php/index.d.ts +92 -0
- package/dist/code/php/index.d.ts.map +1 -0
- package/dist/code/php/index.js +316 -0
- package/dist/code/php/index.js.map +1 -0
- package/dist/code/project.d.ts +13 -0
- package/dist/code/project.d.ts.map +1 -0
- package/dist/code/project.js +69 -0
- package/dist/code/project.js.map +1 -0
- package/dist/code/python/ast.d.ts +47 -0
- package/dist/code/python/ast.d.ts.map +1 -0
- package/dist/code/python/ast.js +193 -0
- package/dist/code/python/ast.js.map +1 -0
- package/dist/code/python/index.d.ts +3 -0
- package/dist/code/python/index.d.ts.map +1 -0
- package/dist/code/python/index.js +3 -0
- package/dist/code/python/index.js.map +1 -0
- package/dist/code/python/module.d.ts +55 -0
- package/dist/code/python/module.d.ts.map +1 -0
- package/dist/code/python/module.js +215 -0
- package/dist/code/python/module.js.map +1 -0
- package/dist/code/scan.d.ts +24 -0
- package/dist/code/scan.d.ts.map +1 -0
- package/dist/code/scan.js +159 -0
- package/dist/code/scan.js.map +1 -0
- package/dist/code/treesitter/index.d.ts +83 -0
- package/dist/code/treesitter/index.d.ts.map +1 -0
- package/dist/code/treesitter/index.js +243 -0
- package/dist/code/treesitter/index.js.map +1 -0
- package/dist/code/types.d.ts +48 -0
- package/dist/code/types.d.ts.map +1 -0
- package/dist/code/types.js +2 -0
- package/dist/code/types.js.map +1 -0
- package/dist/content/en.d.ts +3 -0
- package/dist/content/en.d.ts.map +1 -0
- package/dist/content/en.js +722 -0
- package/dist/content/en.js.map +1 -0
- package/dist/content/index.d.ts +15 -0
- package/dist/content/index.d.ts.map +1 -0
- package/dist/content/index.js +21 -0
- package/dist/content/index.js.map +1 -0
- package/dist/content/types.d.ts +87 -0
- package/dist/content/types.d.ts.map +1 -0
- package/dist/content/types.js +2 -0
- package/dist/content/types.js.map +1 -0
- package/dist/engine.d.ts +52 -0
- package/dist/engine.d.ts.map +1 -0
- package/dist/engine.js +120 -0
- package/dist/engine.js.map +1 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +11 -0
- package/dist/index.js.map +1 -0
- package/dist/meta.d.ts +4 -0
- package/dist/meta.d.ts.map +1 -0
- package/dist/meta.js +4 -0
- package/dist/meta.js.map +1 -0
- package/dist/net/context.d.ts +9 -0
- package/dist/net/context.d.ts.map +1 -0
- package/dist/net/context.js +62 -0
- package/dist/net/context.js.map +1 -0
- package/dist/net/guard.d.ts +16 -0
- package/dist/net/guard.d.ts.map +1 -0
- package/dist/net/guard.js +70 -0
- package/dist/net/guard.js.map +1 -0
- package/dist/net/http.d.ts +22 -0
- package/dist/net/http.d.ts.map +1 -0
- package/dist/net/http.js +114 -0
- package/dist/net/http.js.map +1 -0
- package/dist/net/tls.d.ts +15 -0
- package/dist/net/tls.d.ts.map +1 -0
- package/dist/net/tls.js +103 -0
- package/dist/net/tls.js.map +1 -0
- package/dist/report/html.d.ts +14 -0
- package/dist/report/html.d.ts.map +1 -0
- package/dist/report/html.js +210 -0
- package/dist/report/html.js.map +1 -0
- package/dist/report/index.d.ts +10 -0
- package/dist/report/index.d.ts.map +1 -0
- package/dist/report/index.js +10 -0
- package/dist/report/index.js.map +1 -0
- package/dist/report/manual.d.ts +62 -0
- package/dist/report/manual.d.ts.map +1 -0
- package/dist/report/manual.js +190 -0
- package/dist/report/manual.js.map +1 -0
- package/dist/report/merge.d.ts +8 -0
- package/dist/report/merge.d.ts.map +1 -0
- package/dist/report/merge.js +135 -0
- package/dist/report/merge.js.map +1 -0
- package/dist/report/summary.d.ts +10 -0
- package/dist/report/summary.d.ts.map +1 -0
- package/dist/report/summary.js +27 -0
- package/dist/report/summary.js.map +1 -0
- package/dist/report/validate.d.ts +3 -0
- package/dist/report/validate.d.ts.map +1 -0
- package/dist/report/validate.js +78 -0
- package/dist/report/validate.js.map +1 -0
- package/dist/types.d.ts +244 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/validate.d.ts +4 -0
- package/dist/validate.d.ts.map +1 -0
- package/dist/validate.js +24 -0
- package/dist/validate.js.map +1 -0
- package/package.json +40 -0
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import { djangoSettings } from "../django/settings.js";
|
|
2
|
+
import { NO_PATTERN, pyFinding } from "../findings.js";
|
|
3
|
+
import { callArgs, dotted, identifiers, strConst, walk } from "../python/index.js";
|
|
4
|
+
import { bases, classDefs, PASSWORD_IDENT, patternCheck, SECRET_IDENT, walkStatements } from "./shared.js";
|
|
5
|
+
export const SECRET_ASSIGN = /(password|passwd|secret|token|api_?key|apikey|private_?key|access_?key|secret_?key)$/i;
|
|
6
|
+
export const SECRET_DICT_KEY = /^(password|passwd|secret|secret_key|api_key|apikey|token|access_key|private_key)$/i;
|
|
7
|
+
/** Values that are clearly not secrets: placeholders and templates. */
|
|
8
|
+
const PLACEHOLDER = /^(|.*\{.*\}.*|.*%\(?\w*\)?s.*|<.*>|\*+|x+|changeme|password|none|null)$/i;
|
|
9
|
+
/** An environment variable name (case-sensitive: "DB_PASSWORD" is a name, "Sup3rPass" is not). */
|
|
10
|
+
const ENV_NAME = /^[A-Z][A-Z0-9_]+$/;
|
|
11
|
+
export const looksSecret = (value) => value !== undefined && value.length >= 6 && !PLACEHOLDER.test(value) && !ENV_NAME.test(value);
|
|
12
|
+
/** #6 Hard-coded secrets (Django SECRET_KEY, credentials in code). Snippets are redacted. */
|
|
13
|
+
export const hardcodedSecrets = patternCheck("py-hardcoded-secrets", ["python"], (index, ctx) => {
|
|
14
|
+
const out = [];
|
|
15
|
+
const settings = ctx.project.frameworks.includes("django") ? djangoSettings(index) : undefined;
|
|
16
|
+
const settingsPaths = new Set(settings?.modules.map((m) => m.path));
|
|
17
|
+
for (const module of index.modules) {
|
|
18
|
+
for (const n of walk(module.tree)) {
|
|
19
|
+
if (n._t === "Assign" || n._t === "AnnAssign") {
|
|
20
|
+
const targets = n._t === "Assign" ? n["targets"] : [n["target"]];
|
|
21
|
+
const value = strConst(n["value"]);
|
|
22
|
+
for (const t of targets) {
|
|
23
|
+
const name = t._t === "Name" ? t["id"] : t._t === "Attribute" ? t["attr"] : undefined;
|
|
24
|
+
if (!name || !SECRET_ASSIGN.test(name) || !looksSecret(value))
|
|
25
|
+
continue;
|
|
26
|
+
if (name === "SECRET_KEY" && settingsPaths.has(module.path))
|
|
27
|
+
continue; // handled below with dev/prod context
|
|
28
|
+
out.push(pyFinding(module, n, "py-hardcoded-secret", "medium", "Credentials in source code leak through repositories, backups and builds; load them from the environment or a secret store."));
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
else if (n._t === "Dict") {
|
|
32
|
+
const keys = n["keys"];
|
|
33
|
+
const values = n["values"];
|
|
34
|
+
keys.forEach((k, i) => {
|
|
35
|
+
if (SECRET_DICT_KEY.test(strConst(k) ?? "") && looksSecret(strConst(values[i]))) {
|
|
36
|
+
out.push(pyFinding(module, values[i], "py-hardcoded-secret", "medium", "Credentials in source code leak through repositories, backups and builds; load them from the environment or a secret store."));
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
else if (n._t === "keyword" && typeof n["arg"] === "string" && SECRET_ASSIGN.test(n["arg"]) && looksSecret(strConst(n["value"]))) {
|
|
41
|
+
out.push(pyFinding(module, n["value"], "py-hardcoded-secret", "medium", "Credentials passed as literals are stored in source code."));
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
if (settings) {
|
|
46
|
+
for (const a of settings.all("SECRET_KEY")) {
|
|
47
|
+
if (typeof a.value !== "string" || a.value.length === 0)
|
|
48
|
+
continue;
|
|
49
|
+
out.push(settings.findingAt(a, "django-hardcoded-secret-key", a.kind === "dev" ? "low" : "high", a.kind === "dev"
|
|
50
|
+
? "SECRET_KEY is hard-coded in development settings; make sure production never uses it."
|
|
51
|
+
: "A hard-coded SECRET_KEY lets anyone with the source forge sessions, password-reset tokens and signed data."));
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return out;
|
|
55
|
+
}, (n) => NO_PATTERN("hard-coded credentials or secret keys", n));
|
|
56
|
+
const HASHING_CALL = /(make_password|set_password|hash|bcrypt|argon2|pbkdf2|scrypt|encrypt)/i;
|
|
57
|
+
const MAIL_OR_RESPONSE = /^(django\.core\.mail\.)?(send_mail|send_mass_mail|mail_admins|mail_managers|EmailMessage|EmailMultiAlternatives)$|(^|\.)(HttpResponse|JsonResponse|Response)$/;
|
|
58
|
+
/** #34 Plaintext password storage or retrieval. */
|
|
59
|
+
export const plaintextPasswords = patternCheck("py-plaintext-passwords", ["python"], (index) => {
|
|
60
|
+
const out = [];
|
|
61
|
+
// user.password = <not hashed>
|
|
62
|
+
for (const module of index.modules) {
|
|
63
|
+
for (const n of walk(module.tree)) {
|
|
64
|
+
if (n._t !== "Assign")
|
|
65
|
+
continue;
|
|
66
|
+
const target = n["targets"][0];
|
|
67
|
+
if (target?._t !== "Attribute" || !PASSWORD_IDENT.test(target["attr"]))
|
|
68
|
+
continue;
|
|
69
|
+
const value = n["value"];
|
|
70
|
+
if (value._t === "Constant")
|
|
71
|
+
continue;
|
|
72
|
+
const text = module.text(value);
|
|
73
|
+
if (HASHING_CALL.test(text))
|
|
74
|
+
continue;
|
|
75
|
+
out.push(pyFinding(module, n, "py-plaintext-password-assign", "high", "Assigning a raw value to a password attribute stores it unhashed; use set_password()/make_password()."));
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
// Model fields named password on non-auth models.
|
|
79
|
+
for (const { module, node } of classDefs(index)) {
|
|
80
|
+
const b = bases(module, node);
|
|
81
|
+
if (!b.some((x) => /(^|\.)Model$/.test(x)) || b.some((x) => /AbstractBaseUser|AbstractUser/.test(x)))
|
|
82
|
+
continue;
|
|
83
|
+
for (const stmt of walkStatements(node)) {
|
|
84
|
+
if (stmt._t !== "Assign")
|
|
85
|
+
continue;
|
|
86
|
+
const t = stmt["targets"][0];
|
|
87
|
+
const v = stmt["value"];
|
|
88
|
+
const field = module.qualify(dotted(v["func"])) ?? "";
|
|
89
|
+
const fieldName = t?._t === "Name" ? t["id"] : "";
|
|
90
|
+
if (/hash|digest|salt/i.test(fieldName))
|
|
91
|
+
continue; // storing a hash is the correct design
|
|
92
|
+
if (t?._t === "Name" && PASSWORD_IDENT.test(fieldName) && v._t === "Call" && /(CharField|TextField)$/.test(field)) {
|
|
93
|
+
out.push(pyFinding(module, stmt, "py-password-model-field", "medium", "A password stored in a plain text model field is readable by anyone with database access."));
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
// Passwords sent by e-mail or returned in responses.
|
|
98
|
+
for (const site of index.calls()) {
|
|
99
|
+
if (!MAIL_OR_RESPONSE.test(site.name ?? ""))
|
|
100
|
+
continue;
|
|
101
|
+
const args = [...callArgs(site.node), ...((site.node["keywords"] ?? []).map((k) => k["value"]))];
|
|
102
|
+
if (args.some((a) => a._t !== "Constant" && identifiers(a).some((i) => PASSWORD_IDENT.test(i)))) {
|
|
103
|
+
out.push(pyFinding(site.module, site.node, "py-password-disclosed", "medium", "Sending or returning a password in plaintext implies it is stored or handled unhashed."));
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
return out;
|
|
107
|
+
}, (n) => NO_PATTERN("plaintext password storage or retrieval", n));
|
|
108
|
+
const LOG_METHOD = new Set(["debug", "info", "warning", "warn", "error", "exception", "critical", "log"]);
|
|
109
|
+
const LOGGER_RECEIVER = /(^|\.)(_?log(ger)?|LOG(GER)?|logging)$/i;
|
|
110
|
+
const REQUEST_DUMP = /^(self\.)?request\.(POST|data|body|META|headers|COOKIES|session)$/;
|
|
111
|
+
/** #77 Secrets/tokens written to logs. */
|
|
112
|
+
export const secretsInLogs = patternCheck("py-secrets-in-logs", ["python"], (index) => {
|
|
113
|
+
const out = [];
|
|
114
|
+
for (const site of index.calls()) {
|
|
115
|
+
const func = site.node["func"];
|
|
116
|
+
const receiver = func._t === "Attribute" ? (dotted(func["value"]) ?? "") : "";
|
|
117
|
+
const isLogger = func._t === "Attribute" && LOG_METHOD.has(site.method ?? "") && (LOGGER_RECEIVER.test(receiver) || (site.name ?? "").startsWith("logging."));
|
|
118
|
+
const isPrint = site.name === "print";
|
|
119
|
+
if (!isLogger && !isPrint)
|
|
120
|
+
continue;
|
|
121
|
+
const args = [...callArgs(site.node), ...((site.node["keywords"] ?? []).map((k) => k["value"]))];
|
|
122
|
+
for (const arg of args) {
|
|
123
|
+
if (arg._t === "Constant")
|
|
124
|
+
continue;
|
|
125
|
+
const secret = identifiers(arg).find((i) => SECRET_IDENT.test(i));
|
|
126
|
+
// A dump is the request data itself, wrapped in a call (dict(request.POST)) or an f-string placeholder,
|
|
127
|
+
// not an expression that merely indexes into it (request.POST["username"]).
|
|
128
|
+
const candidates = arg._t === "JoinedStr"
|
|
129
|
+
? (arg["values"] ?? []).filter((v) => v._t === "FormattedValue").map((v) => v["value"])
|
|
130
|
+
: [arg._t === "Call" && callArgs(arg)[0] ? callArgs(arg)[0] : arg];
|
|
131
|
+
const dump = candidates.some((x) => REQUEST_DUMP.test(dotted(x) ?? ""));
|
|
132
|
+
if (secret || dump) {
|
|
133
|
+
out.push(pyFinding(site.module, site.node, "py-secret-logged", isPrint ? "low" : "medium", dump ? "Logging raw request data can write passwords, tokens and session IDs to logs." : `Logs a value named "${secret}", which may be a credential or token.`));
|
|
134
|
+
break;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
return out;
|
|
139
|
+
}, (n) => NO_PATTERN("logging of passwords, tokens or raw request data", n));
|
|
140
|
+
//# sourceMappingURL=secrets.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"secrets.js","sourceRoot":"","sources":["../../../src/code/checks/secrets.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { CheckOutcome, CodeFinding } from "../../types.js";
|
|
2
|
+
import { type PyModule, type PyNode, type PythonIndex } from "../python/index.js";
|
|
3
|
+
import type { CodeCheck, CodeContext, FrameworkId, Language } from "../types.js";
|
|
4
|
+
export declare function codeCheck(key: string, appliesTo: readonly (Language | FrameworkId)[], run: (ctx: CodeContext) => Promise<CheckOutcome>): CodeCheck;
|
|
5
|
+
/** A check whose verdict is the standard findings rule (see outcomeFromFindings). */
|
|
6
|
+
export declare function patternCheck(key: string, appliesTo: readonly (Language | FrameworkId)[], find: (index: PythonIndex, ctx: CodeContext) => CodeFinding[] | Promise<CodeFinding[]>, passDetail: (files: number, index: PythonIndex) => string): CodeCheck;
|
|
7
|
+
/** Names that suggest a security-relevant value. Used to raise or lower confidence, never as the sole match. */
|
|
8
|
+
export declare const SECURITY_CONTEXT: RegExp;
|
|
9
|
+
export declare const PASSWORD_IDENT: RegExp;
|
|
10
|
+
export declare const SECRET_IDENT: RegExp;
|
|
11
|
+
/** Qualified names of a function's decorators (calls unwrapped). */
|
|
12
|
+
export declare function decorators(module: PyModule, fn: PyNode): string[];
|
|
13
|
+
export declare function classDefs(index: PythonIndex): Generator<{
|
|
14
|
+
module: PyModule;
|
|
15
|
+
node: PyNode;
|
|
16
|
+
}>;
|
|
17
|
+
export declare function functionDefs(index: PythonIndex): Generator<{
|
|
18
|
+
module: PyModule;
|
|
19
|
+
node: PyNode;
|
|
20
|
+
}>;
|
|
21
|
+
/** All statements at any depth (bodies of classes, functions, if/try/with blocks). */
|
|
22
|
+
export declare function walkStatements(node: PyNode): Generator<PyNode>;
|
|
23
|
+
/** Base class names of a ClassDef, qualified. */
|
|
24
|
+
export declare function bases(module: PyModule, cls: PyNode): string[];
|
|
25
|
+
//# sourceMappingURL=shared.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shared.d.ts","sourceRoot":"","sources":["../../../src/code/checks/shared.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAEhE,OAAO,EAAkB,KAAK,QAAQ,EAAE,KAAK,MAAM,EAAE,KAAK,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAClG,OAAO,KAAK,EAAE,SAAS,EAAE,WAAW,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAEjF,wBAAgB,SAAS,CACvB,GAAG,EAAE,MAAM,EACX,SAAS,EAAE,SAAS,CAAC,QAAQ,GAAG,WAAW,CAAC,EAAE,EAC9C,GAAG,EAAE,CAAC,GAAG,EAAE,WAAW,KAAK,OAAO,CAAC,YAAY,CAAC,GAC/C,SAAS,CAEX;AAED,qFAAqF;AACrF,wBAAgB,YAAY,CAC1B,GAAG,EAAE,MAAM,EACX,SAAS,EAAE,SAAS,CAAC,QAAQ,GAAG,WAAW,CAAC,EAAE,EAC9C,IAAI,EAAE,CAAC,KAAK,EAAE,WAAW,EAAE,GAAG,EAAE,WAAW,KAAK,WAAW,EAAE,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC,EACtF,UAAU,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,WAAW,KAAK,MAAM,GACxD,SAAS,CAKX;AAED,gHAAgH;AAChH,eAAO,MAAM,gBAAgB,QAAsG,CAAC;AACpI,eAAO,MAAM,cAAc,QAAoD,CAAC;AAChF,eAAO,MAAM,YAAY,QAAsL,CAAC;AAEhN,oEAAoE;AACpE,wBAAgB,UAAU,CAAC,MAAM,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,GAAG,MAAM,EAAE,CAKjE;AAED,wBAAiB,SAAS,CAAC,KAAK,EAAE,WAAW,GAAG,SAAS,CAAC;IAAE,MAAM,EAAE,QAAQ,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC,CAI5F;AAED,wBAAiB,YAAY,CAAC,KAAK,EAAE,WAAW,GAAG,SAAS,CAAC;IAAE,MAAM,EAAE,QAAQ,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC,CAM/F;AAED,sFAAsF;AACtF,wBAAiB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,CAAC,MAAM,CAAC,CAU/D;AAED,iDAAiD;AACjD,wBAAgB,KAAK,CAAC,MAAM,EAAE,QAAQ,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,EAAE,CAE7D"}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { outcomeFromFindings } from "../findings.js";
|
|
2
|
+
import { dotted, isNode } from "../python/index.js";
|
|
3
|
+
export function codeCheck(key, appliesTo, run) {
|
|
4
|
+
return { kind: "code", key, appliesTo, run };
|
|
5
|
+
}
|
|
6
|
+
/** A check whose verdict is the standard findings rule (see outcomeFromFindings). */
|
|
7
|
+
export function patternCheck(key, appliesTo, find, passDetail) {
|
|
8
|
+
return codeCheck(key, appliesTo, async (ctx) => {
|
|
9
|
+
const index = await ctx.python();
|
|
10
|
+
return outcomeFromFindings(await find(index, ctx), passDetail(index.modules.length, index));
|
|
11
|
+
});
|
|
12
|
+
}
|
|
13
|
+
/** Names that suggest a security-relevant value. Used to raise or lower confidence, never as the sole match. */
|
|
14
|
+
export const SECURITY_CONTEXT = /pass(word|wd)?\b|pwd|secret|token|otp|nonce|salt|signature|sign\b|auth|api_?key|credential|reset/i;
|
|
15
|
+
export const PASSWORD_IDENT = /^(.*_)?(password|passwd|pwd|passphrase)(_.*)?$/i;
|
|
16
|
+
export const SECRET_IDENT = /^(.*_)?(password|passwd|pwd|secret|token|api_?key|apikey|secret_?key|private_?key|access_?key|session_?key|sessionid|authorization|jwt|otp|cvv|card_?number|credit_?card)(_.*)?$/i;
|
|
17
|
+
/** Qualified names of a function's decorators (calls unwrapped). */
|
|
18
|
+
export function decorators(module, fn) {
|
|
19
|
+
return (fn["decorator_list"] ?? []).map((d) => {
|
|
20
|
+
const target = d._t === "Call" ? d["func"] : d;
|
|
21
|
+
return module.qualify(dotted(target)) ?? "";
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
export function* classDefs(index) {
|
|
25
|
+
for (const module of index.modules) {
|
|
26
|
+
for (const stmt of walkStatements(module.tree))
|
|
27
|
+
if (stmt._t === "ClassDef")
|
|
28
|
+
yield { module, node: stmt };
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
export function* functionDefs(index) {
|
|
32
|
+
for (const module of index.modules) {
|
|
33
|
+
for (const stmt of walkStatements(module.tree)) {
|
|
34
|
+
if (stmt._t === "FunctionDef" || stmt._t === "AsyncFunctionDef")
|
|
35
|
+
yield { module, node: stmt };
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
/** All statements at any depth (bodies of classes, functions, if/try/with blocks). */
|
|
40
|
+
export function* walkStatements(node) {
|
|
41
|
+
for (const key of ["body", "orelse", "finalbody", "handlers"]) {
|
|
42
|
+
const list = node[key];
|
|
43
|
+
if (!Array.isArray(list))
|
|
44
|
+
continue;
|
|
45
|
+
for (const stmt of list) {
|
|
46
|
+
if (!isNode(stmt))
|
|
47
|
+
continue;
|
|
48
|
+
yield stmt;
|
|
49
|
+
yield* walkStatements(stmt);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
/** Base class names of a ClassDef, qualified. */
|
|
54
|
+
export function bases(module, cls) {
|
|
55
|
+
return (cls["bases"] ?? []).map((b) => module.qualify(dotted(b)) ?? "");
|
|
56
|
+
}
|
|
57
|
+
//# sourceMappingURL=shared.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shared.js","sourceRoot":"","sources":["../../../src/code/checks/shared.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { CodeCheck } from "../types.js";
|
|
2
|
+
export declare const tsHardcodedSecrets: CodeCheck;
|
|
3
|
+
export declare const tsWeakCrypto: CodeCheck;
|
|
4
|
+
export declare const tsCryptoMisuse: CodeCheck;
|
|
5
|
+
export declare const tsSqlInjection: CodeCheck;
|
|
6
|
+
export declare const tsCommandInjection: CodeCheck;
|
|
7
|
+
export declare const tsCodeInjection: CodeCheck;
|
|
8
|
+
export declare const tsPathTraversal: CodeCheck;
|
|
9
|
+
export declare const tsXss: CodeCheck;
|
|
10
|
+
export declare const tsOpenRedirect: CodeCheck;
|
|
11
|
+
export declare const tsSecretsInLogs: CodeCheck;
|
|
12
|
+
export declare const tsXxe: CodeCheck;
|
|
13
|
+
/** Framework CSRF protection: explicit opt-outs are findings; evidence of protection passes; otherwise manual. */
|
|
14
|
+
export declare const tsCsrf: CodeCheck;
|
|
15
|
+
//# sourceMappingURL=treesitter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"treesitter.d.ts","sourceRoot":"","sources":["../../../src/code/checks/treesitter.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAmF7C,eAAO,MAAM,kBAAkB,WAa7B,CAAC;AAgBH,eAAO,MAAM,YAAY,WAgBvB,CAAC;AAWH,eAAO,MAAM,cAAc,WAoBzB,CAAC;AAWH,eAAO,MAAM,cAAc,WAgBzB,CAAC;AAWH,eAAO,MAAM,kBAAkB,WAmB7B,CAAC;AAcH,eAAO,MAAM,eAAe,WAU1B,CAAC;AAUH,eAAO,MAAM,eAAe,WAE3B,CAAC;AAUF,eAAO,MAAM,KAAK,WAahB,CAAC;AAUH,eAAO,MAAM,cAAc,WAWzB,CAAC;AAMH,eAAO,MAAM,eAAe,WAQ1B,CAAC;AAMH,eAAO,MAAM,KAAK,WAQhB,CAAC;AAGH,kHAAkH;AAClH,eAAO,MAAM,MAAM,WAoBjB,CAAC"}
|
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
import { outcomeFromFindings, snippetOf } from "../findings.js";
|
|
2
|
+
import { SPECS } from "../treesitter/index.js";
|
|
3
|
+
import { looksSecret, SECRET_ASSIGN } from "./secrets.js";
|
|
4
|
+
import { codeCheck, SECRET_IDENT, SECURITY_CONTEXT } from "./shared.js";
|
|
5
|
+
/** Pattern rules for Java, C#, Ruby and Go, driven by per-language sink tables. */
|
|
6
|
+
const LANGS = ["java", "csharp", "ruby", "go"];
|
|
7
|
+
function finding(module, node, rule, confidence, why) {
|
|
8
|
+
const line = module.lineOf(node);
|
|
9
|
+
return { file: module.path, line, snippet: snippetOf(module.line(line)), why, confidence, method: "ast", rule };
|
|
10
|
+
}
|
|
11
|
+
function tsCheck(key, what, find) {
|
|
12
|
+
return codeCheck(key, LANGS, async (ctx) => {
|
|
13
|
+
const index = await ctx.treeSitter();
|
|
14
|
+
const langs = [...new Set(index.modules.map((m) => SPECS[m.lang].label))].join(", ");
|
|
15
|
+
return outcomeFromFindings(find(index), `No ${what} found in ${index.modules.length} ${langs} file(s). Static analysis cannot prove absence; review dynamic code paths.`);
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
/** Same scale as the other languages: direct request data = high, derived/built = medium, unknown = low. */
|
|
19
|
+
function risk(site, arg, opts = {}) {
|
|
20
|
+
if (!arg)
|
|
21
|
+
return undefined;
|
|
22
|
+
const m = site.module;
|
|
23
|
+
if (m.isLiteral(arg))
|
|
24
|
+
return undefined;
|
|
25
|
+
const scope = m.scope(site.fn);
|
|
26
|
+
const direct = m.spec.requestData.test(arg.text);
|
|
27
|
+
const derived = direct || m.isRequestDerived(arg, scope);
|
|
28
|
+
const dynamic = m.isDynamicString(arg) || (m.spec.identifierTypes.includes(arg.type) && scope.dynamicStrings.has(arg.text));
|
|
29
|
+
if (opts.requestOnly && !derived)
|
|
30
|
+
return undefined;
|
|
31
|
+
if (opts.dynamicOnly && !dynamic)
|
|
32
|
+
return undefined;
|
|
33
|
+
if (direct)
|
|
34
|
+
return "high";
|
|
35
|
+
if (dynamic)
|
|
36
|
+
return derived ? "high" : "medium";
|
|
37
|
+
if (derived)
|
|
38
|
+
return "medium";
|
|
39
|
+
return opts.unknownIsLow === false ? undefined : "low";
|
|
40
|
+
}
|
|
41
|
+
/** Findings for calls whose callee matches the language's pattern; `pick` chooses which arguments matter. */
|
|
42
|
+
function sinks(index, patterns, rule, why, opts = {}, pick = (s) => s.args) {
|
|
43
|
+
const out = [];
|
|
44
|
+
for (const site of index.calls()) {
|
|
45
|
+
const pattern = patterns[site.module.lang];
|
|
46
|
+
if (!pattern || !pattern.test(site.callee))
|
|
47
|
+
continue;
|
|
48
|
+
const risks = pick(site).map((a) => risk(site, a, opts)).filter((r) => !!r);
|
|
49
|
+
if (risks.length === 0)
|
|
50
|
+
continue;
|
|
51
|
+
out.push(finding(site.module, site.node, rule, risks.includes("high") ? "high" : risks.includes("medium") ? "medium" : "low", why));
|
|
52
|
+
}
|
|
53
|
+
return out;
|
|
54
|
+
}
|
|
55
|
+
/** Findings for nodes of given types whose text matches (configuration constants, option fields...). */
|
|
56
|
+
function markers(index, lang, types, pattern, rule, confidence, why) {
|
|
57
|
+
const out = [];
|
|
58
|
+
for (const m of index.byLang(lang)) {
|
|
59
|
+
for (const n of m.root.descendantsOfType(types))
|
|
60
|
+
if (pattern.test(n.text))
|
|
61
|
+
out.push(finding(m, n, rule, confidence, why));
|
|
62
|
+
}
|
|
63
|
+
return out;
|
|
64
|
+
}
|
|
65
|
+
const firstArg = (s) => [s.args[0]];
|
|
66
|
+
const lineOfSite = (s) => s.module.line(s.module.lineOf(s.node));
|
|
67
|
+
// --- #6 Hard-coded secrets -----------------------------------------------------------------
|
|
68
|
+
const SECRET_WHY = "Credentials in source code leak through repositories, backups and builds; load them from the environment or a secret store.";
|
|
69
|
+
const PAIR_TYPES = { ruby: ["pair"], go: ["keyed_element"] };
|
|
70
|
+
export const tsHardcodedSecrets = tsCheck("ts-hardcoded-secrets", "hard-coded credentials", (index) => {
|
|
71
|
+
const out = [];
|
|
72
|
+
for (const m of index.modules) {
|
|
73
|
+
for (const a of m.root.descendantsOfType([...m.spec.assignTypes, ...(PAIR_TYPES[m.lang] ?? []), "const_spec"])) {
|
|
74
|
+
const left = a.childForFieldName("left") ?? a.childForFieldName("name") ?? a.childForFieldName("key") ?? a.namedChildren[0];
|
|
75
|
+
const right = a.childForFieldName("right") ?? a.childForFieldName("value") ?? a.namedChildren.at(-1);
|
|
76
|
+
if (!left || !right || left.id === right.id)
|
|
77
|
+
continue;
|
|
78
|
+
const name = left.text.replace(/^[@$:"']+|["':]+$/g, "").split(/[.\s]/).pop() ?? "";
|
|
79
|
+
const literal = right.type === "equals_value_clause" || right.type === "expression_list" || right.type === "literal_element" ? right.namedChildren[0] : right;
|
|
80
|
+
if (SECRET_ASSIGN.test(name) && looksSecret(m.stringValue(literal ?? undefined)))
|
|
81
|
+
out.push(finding(m, a, "ts-hardcoded-secret", "medium", SECRET_WHY));
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
return out;
|
|
85
|
+
});
|
|
86
|
+
// --- #19 Weak algorithms ---------------------------------------------------------------------
|
|
87
|
+
const WEAK_HASH_ARG = /^(md5|sha-?1|md2|md4)$/i;
|
|
88
|
+
const WEAK_CIPHER_ARG = /^(des|desede|3des|rc2|rc4|arcfour|blowfish)(\/|-|$)/i;
|
|
89
|
+
const WEAK_HASH_WHY = "MD5/SHA-1 are broken for security purposes (collisions, fast brute force).";
|
|
90
|
+
const WEAK_HASH_CALL = {
|
|
91
|
+
csharp: /(^|\.)(MD5|SHA1)\.Create$|^new (MD5CryptoServiceProvider|SHA1CryptoServiceProvider|SHA1Managed)$/,
|
|
92
|
+
ruby: /^(Digest|OpenSSL::Digest)::(MD5|SHA1)(\.(hexdigest|digest|base64digest|new|file))?$/,
|
|
93
|
+
go: /^(md5|sha1)\.(New|Sum)$/,
|
|
94
|
+
};
|
|
95
|
+
const WEAK_CIPHER_CALL = {
|
|
96
|
+
csharp: /(^|\.)(DES|TripleDES|RC2)\.Create$|^new (DES|TripleDES|RC2)CryptoServiceProvider$/,
|
|
97
|
+
go: /^(des\.NewCipher|des\.NewTripleDESCipher|rc4\.NewCipher)$/,
|
|
98
|
+
};
|
|
99
|
+
export const tsWeakCrypto = tsCheck("ts-weak-crypto", "use of weak hashes or ciphers", (index) => {
|
|
100
|
+
const out = [];
|
|
101
|
+
for (const s of index.calls()) {
|
|
102
|
+
const lang = s.module.lang;
|
|
103
|
+
const arg = s.module.stringValue(s.args[0]) ?? "";
|
|
104
|
+
const security = SECURITY_CONTEXT.test(lineOfSite(s));
|
|
105
|
+
const weakHash = () => out.push(finding(s.module, s.node, "ts-weak-hash", security ? "high" : "low", security ? WEAK_HASH_WHY : "MD5/SHA-1 in use; acceptable only for non-security checksums. Confirm how it is used."));
|
|
106
|
+
const weakCipher = () => out.push(finding(s.module, s.node, "ts-weak-cipher", "high", "DES/3DES/RC2/RC4/Blowfish-class ciphers are deprecated and breakable."));
|
|
107
|
+
if (lang === "java" && /(^|\.)MessageDigest\.getInstance$/.test(s.callee) && WEAK_HASH_ARG.test(arg))
|
|
108
|
+
weakHash();
|
|
109
|
+
else if (lang === "java" && /(^|\.)(Cipher|KeyGenerator|SecretKeyFactory)\.getInstance$/.test(s.callee) && WEAK_CIPHER_ARG.test(arg))
|
|
110
|
+
weakCipher();
|
|
111
|
+
else if (lang === "ruby" && /^OpenSSL::Cipher(::Cipher)?\.new$/.test(s.callee) && WEAK_CIPHER_ARG.test(arg))
|
|
112
|
+
weakCipher();
|
|
113
|
+
else if (WEAK_HASH_CALL[lang]?.test(s.callee))
|
|
114
|
+
weakHash();
|
|
115
|
+
else if (WEAK_CIPHER_CALL[lang]?.test(s.callee))
|
|
116
|
+
weakCipher();
|
|
117
|
+
}
|
|
118
|
+
return out;
|
|
119
|
+
});
|
|
120
|
+
// --- #20 Misuse of primitives --------------------------------------------------------------------
|
|
121
|
+
const TLS_WHY = "Disabling certificate verification allows man-in-the-middle attacks.";
|
|
122
|
+
const INSECURE_RANDOM = {
|
|
123
|
+
java: /^new Random$|^Math\.random$/,
|
|
124
|
+
csharp: /^new Random$/,
|
|
125
|
+
ruby: /^(rand|Random\.rand|Kernel\.rand)$/,
|
|
126
|
+
go: /^rand\.(Intn|Int|Int31|Int63|Float64|Read|Perm)$/,
|
|
127
|
+
};
|
|
128
|
+
export const tsCryptoMisuse = tsCheck("ts-crypto-misuse", "cryptographic misuse (ECB, insecure randomness, disabled TLS verification)", (index) => {
|
|
129
|
+
const out = [];
|
|
130
|
+
for (const s of index.calls()) {
|
|
131
|
+
const lang = s.module.lang;
|
|
132
|
+
const arg = s.module.stringValue(s.args[0]) ?? "";
|
|
133
|
+
if (lang === "java" && /(^|\.)Cipher\.getInstance$/.test(s.callee) && (/\/ECB\//i.test(arg) || /^AES$/i.test(arg))) {
|
|
134
|
+
out.push(finding(s.module, s.node, "ts-ecb-mode", "high", "ECB mode (the default for a bare \"AES\") leaks plaintext patterns; use AES/GCM/NoPadding."));
|
|
135
|
+
}
|
|
136
|
+
else if (INSECURE_RANDOM[lang]?.test(s.callee) && SECURITY_CONTEXT.test(lineOfSite(s))) {
|
|
137
|
+
// Go: crypto/rand shares the package name; only math/rand is predictable.
|
|
138
|
+
if (lang === "go" && !/"math\/rand"/.test(s.module.source))
|
|
139
|
+
continue;
|
|
140
|
+
out.push(finding(s.module, s.node, "ts-insecure-random", "medium", "This random generator is predictable; use a cryptographically secure one for tokens, passwords and keys."));
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
out.push(...markers(index, "csharp", ["member_access_expression"], /^CipherMode\.ECB$/, "ts-ecb-mode", "high", "ECB mode leaks plaintext patterns; use an authenticated mode such as GCM."), ...markers(index, "csharp", ["assignment_expression"], /ServerCertificate(Custom)?ValidationCallback\s*[+]?=/, "ts-tls-verify-off", "medium", "A custom certificate validation callback often accepts every certificate; confirm it validates properly."), ...markers(index, "ruby", ["scope_resolution"], /VERIFY_NONE$/, "ts-tls-verify-off", "high", TLS_WHY), ...markers(index, "go", ["keyed_element"], /^InsecureSkipVerify:\s*true$/, "ts-tls-verify-off", "high", TLS_WHY));
|
|
144
|
+
return out;
|
|
145
|
+
});
|
|
146
|
+
// --- #41 SQL injection ------------------------------------------------------------------------------
|
|
147
|
+
const SQL = {
|
|
148
|
+
java: /\.(executeQuery|executeUpdate|execute|addBatch|prepareStatement|prepareCall|createQuery|createNativeQuery|queryForObject|queryForList|queryForMap|query|update)$/,
|
|
149
|
+
csharp: /^new \w*(Sql|MySql|Npgsql|Oracle|SQLite|Sqlite|OleDb|Odbc)Command$|\.(ExecuteSqlRaw|ExecuteSqlRawAsync|FromSqlRaw|SqlQuery|ExecuteSqlCommand)$/,
|
|
150
|
+
ruby: /\.(where|find_by_sql|execute|exec_query|select_all|select_value|order|having|joins|group|pluck|count_by_sql)$/,
|
|
151
|
+
go: /\.(Query|QueryRow|Exec|QueryContext|QueryRowContext|ExecContext|Raw|Prepare|Select|Get)$/,
|
|
152
|
+
};
|
|
153
|
+
const SQL_WHY = "SQL built from strings can be manipulated by user input; use parameterised queries / bound placeholders.";
|
|
154
|
+
export const tsSqlInjection = tsCheck("ts-sql-injection", "SQL built by string concatenation or interpolation", (index) => {
|
|
155
|
+
// Method names like "query", "where" or "Exec" are generic, so only built strings count.
|
|
156
|
+
// Only the query text is judged: later arguments are bound values (the safe form). In Go's *Context variants the query is second.
|
|
157
|
+
const out = sinks(index, SQL, "ts-sql-string-building", SQL_WHY, { dynamicOnly: true }, (s) => [/Context$/.test(s.callee) ? s.args[1] : s.args[0]]);
|
|
158
|
+
// C#: command.CommandText = "..." + x
|
|
159
|
+
for (const m of index.byLang("csharp")) {
|
|
160
|
+
for (const a of m.root.descendantsOfType(["assignment_expression"])) {
|
|
161
|
+
const left = a.childForFieldName("left");
|
|
162
|
+
const right = a.childForFieldName("right");
|
|
163
|
+
if (!left || !right || !/\.CommandText$/.test(left.text))
|
|
164
|
+
continue;
|
|
165
|
+
const site = { module: m, node: a, callee: "CommandText", args: [right], fn: m.enclosingFunction(a) };
|
|
166
|
+
const r = risk(site, right, { dynamicOnly: true });
|
|
167
|
+
if (r)
|
|
168
|
+
out.push(finding(m, a, "ts-sql-string-building", r, SQL_WHY));
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
return out;
|
|
172
|
+
});
|
|
173
|
+
// --- #44 OS command injection --------------------------------------------------------------------------
|
|
174
|
+
const COMMAND = {
|
|
175
|
+
java: /(^|\.)getRuntime\(\)\.exec$|^new ProcessBuilder$/,
|
|
176
|
+
csharp: /(^|\.)Process\.Start$|^new ProcessStartInfo$/,
|
|
177
|
+
ruby: /^(system|exec|spawn|IO\.popen|Kernel\.system|Kernel\.exec|Open3\.(capture2|capture2e|capture3|popen2|popen3))$/,
|
|
178
|
+
go: /^exec\.(Command|CommandContext)$/,
|
|
179
|
+
};
|
|
180
|
+
const COMMAND_WHY = "Running a command built from variables allows command injection; pass a fixed program and separate arguments, without a shell.";
|
|
181
|
+
export const tsCommandInjection = tsCheck("ts-command-injection", "OS command execution with variable input", (index) => {
|
|
182
|
+
const out = sinks(index, COMMAND, "ts-shell-command", COMMAND_WHY, {}, (s) => {
|
|
183
|
+
// Go: exec.Command with fixed program and non-shell arguments is the safe form; only request data or "sh -c" count.
|
|
184
|
+
if (s.module.lang === "go") {
|
|
185
|
+
const literals = s.args.map((a) => s.module.stringValue(a));
|
|
186
|
+
const shell = literals.some((l) => l === "-c") && literals.some((l) => /^(sh|bash|cmd|\/bin\/sh|\/bin\/bash)$/.test(l ?? ""));
|
|
187
|
+
return shell ? s.args : s.args.filter((a) => s.module.isRequestDerived(a, s.module.scope(s.fn)));
|
|
188
|
+
}
|
|
189
|
+
return s.args;
|
|
190
|
+
});
|
|
191
|
+
// Ruby backticks with interpolation.
|
|
192
|
+
for (const m of index.byLang("ruby")) {
|
|
193
|
+
for (const n of m.root.descendantsOfType(["subshell"])) {
|
|
194
|
+
if (n.descendantsOfType(["interpolation"]).length === 0)
|
|
195
|
+
continue;
|
|
196
|
+
const derived = m.isRequestDerived(n, m.scope(m.enclosingFunction(n)));
|
|
197
|
+
out.push(finding(m, n, "ts-shell-command", derived ? "high" : "medium", COMMAND_WHY));
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
return out;
|
|
201
|
+
});
|
|
202
|
+
// --- #54 Dynamic code / unsafe deserialisation ------------------------------------------------------------
|
|
203
|
+
const CODE_EVAL = {
|
|
204
|
+
java: /\.eval$/,
|
|
205
|
+
ruby: /^(eval|instance_eval|class_eval|module_eval)$|\.(instance_eval|class_eval)$/,
|
|
206
|
+
};
|
|
207
|
+
const DESERIALIZE = {
|
|
208
|
+
java: /\.readObject$|^new XMLDecoder$/,
|
|
209
|
+
csharp: /(BinaryFormatter|LosFormatter|NetDataContractSerializer|ObjectStateFormatter)\w*\.Deserialize$|JavaScriptSerializer\w*\.DeserializeObject$/,
|
|
210
|
+
ruby: /^(Marshal\.load|YAML\.load|YAML\.unsafe_load|Psych\.load)$/,
|
|
211
|
+
};
|
|
212
|
+
const REFLECTION = { ruby: /\.(constantize|send|public_send)$|^(send|public_send)$/, java: /^Class\.forName$/, csharp: /^Type\.GetType$/ };
|
|
213
|
+
export const tsCodeInjection = tsCheck("ts-code-injection", "evaluation of variable input, unsafe deserialisation or request-driven reflection", (index) => [
|
|
214
|
+
...sinks(index, CODE_EVAL, "ts-eval", "Evaluating non-constant input runs arbitrary code.", {}, firstArg),
|
|
215
|
+
...index.calls()
|
|
216
|
+
.filter((s) => DESERIALIZE[s.module.lang]?.test(s.callee) && (s.module.lang !== "java" || /ObjectInputStream|XMLDecoder/.test(s.module.source)))
|
|
217
|
+
.map((s) => finding(s.module, s.node, "ts-unsafe-deserialization", s.args.some((a) => s.module.isRequestDerived(a, s.module.scope(s.fn))) ? "high" : "medium", "Deserialising untrusted data with this API can instantiate arbitrary objects and run code; use a data-only format such as JSON.")),
|
|
218
|
+
...sinks(index, REFLECTION, "ts-request-reflection", "Choosing classes or methods from request data lets attackers call unintended code.", { requestOnly: true }, (s) => {
|
|
219
|
+
const receiver = s.node.childForFieldName("receiver");
|
|
220
|
+
return [...s.args, ...(receiver ? [receiver] : [])];
|
|
221
|
+
}),
|
|
222
|
+
]);
|
|
223
|
+
// --- #42 Path traversal --------------------------------------------------------------------------------------
|
|
224
|
+
const PATHS = {
|
|
225
|
+
java: /^new (File|FileInputStream|FileOutputStream|FileReader|FileWriter|RandomAccessFile)$|^(Paths\.get|Path\.of|Files\.(readAllBytes|readString|readAllLines|newInputStream|newOutputStream|write|delete|lines|copy))$/,
|
|
226
|
+
csharp: /(^|\.)File\.(ReadAllText|ReadAllBytes|ReadAllLines|OpenRead|OpenWrite|WriteAllText|WriteAllBytes|Delete|Open|Copy)$|(^|\.)Path\.Combine$|^new (FileStream|StreamReader|StreamWriter)$|^(PhysicalFile|File)$/,
|
|
227
|
+
ruby: /^(File\.(open|read|new|delete|readlines|write|binread)|IO\.(read|readlines)|send_file|send_data|Pathname\.new)$/,
|
|
228
|
+
go: /^(os\.(Open|OpenFile|ReadFile|WriteFile|Remove|Create)|ioutil\.(ReadFile|WriteFile)|filepath\.Join|path\.Join|http\.ServeFile)$/,
|
|
229
|
+
};
|
|
230
|
+
export const tsPathTraversal = tsCheck("ts-path-traversal", "file paths built from request data", (index) => sinks(index, PATHS, "ts-path-from-request", "File paths built from request data allow ../ traversal; normalise and confine paths to an allowed directory.", { requestOnly: true }));
|
|
231
|
+
// --- #43 Cross-site scripting ---------------------------------------------------------------------------------
|
|
232
|
+
const RESPONSE_WRITE = {
|
|
233
|
+
java: /getWriter\(\)\.(print|println|write|printf|append)$|^out\.(print|println|write)$/,
|
|
234
|
+
csharp: /(^|\.)Response\.Write(Async)?$/,
|
|
235
|
+
go: /^(fmt\.(Fprintf|Fprint|Fprintln)|io\.WriteString)$/,
|
|
236
|
+
};
|
|
237
|
+
const RAW_HTML = { csharp: /(^|\.)Html\.Raw$|^new (HtmlString|MvcHtmlString)$/, ruby: /^raw$/, go: /^template\.(HTML|JS|URL)$/ };
|
|
238
|
+
export const tsXss = tsCheck("ts-xss", "unescaped output of request data", (index) => {
|
|
239
|
+
const out = [
|
|
240
|
+
...sinks(index, RESPONSE_WRITE, "ts-response-write", "Writing request data into a response without escaping enables reflected XSS.", { requestOnly: true }),
|
|
241
|
+
...sinks(index, RAW_HTML, "ts-raw-html", "This marks a value as safe HTML and bypasses escaping; safe only if it can never contain user input.", {}, firstArg),
|
|
242
|
+
];
|
|
243
|
+
// Ruby: "...".html_safe on a non-literal receiver.
|
|
244
|
+
for (const s of index.calls().filter((c) => c.module.lang === "ruby" && /\.html_safe$/.test(c.callee))) {
|
|
245
|
+
const receiver = s.node.childForFieldName("receiver");
|
|
246
|
+
if (!receiver || s.module.isLiteral(receiver))
|
|
247
|
+
continue;
|
|
248
|
+
const derived = s.module.isRequestDerived(receiver, s.module.scope(s.fn));
|
|
249
|
+
out.push(finding(s.module, s.node, "ts-raw-html", derived ? "high" : "low", "html_safe bypasses escaping; safe only if the value can never contain user input."));
|
|
250
|
+
}
|
|
251
|
+
return out;
|
|
252
|
+
});
|
|
253
|
+
// --- #60 / #97 Open redirect ---------------------------------------------------------------------------------------
|
|
254
|
+
const REDIRECT = {
|
|
255
|
+
java: /\.sendRedirect$|^new (RedirectView|ModelAndView)$/,
|
|
256
|
+
csharp: /^(this\.)?(Redirect|RedirectPermanent)$|(^|\.)Response\.Redirect$/,
|
|
257
|
+
ruby: /^redirect_to$/,
|
|
258
|
+
go: /^http\.Redirect$|\.Redirect$/,
|
|
259
|
+
};
|
|
260
|
+
export const tsOpenRedirect = tsCheck("ts-open-redirect", "redirects to request-controlled URLs", (index) => {
|
|
261
|
+
const why = "Redirecting to a URL taken from the request lets attackers send users to malicious sites; allow only relative paths or known hosts.";
|
|
262
|
+
const out = sinks(index, REDIRECT, "ts-open-redirect", why, { requestOnly: true });
|
|
263
|
+
// Spring MVC: return "redirect:" + target
|
|
264
|
+
for (const m of index.byLang("java")) {
|
|
265
|
+
for (const b of m.root.descendantsOfType(["binary_expression"])) {
|
|
266
|
+
if (!/^"redirect:/.test(b.text) || !m.isRequestDerived(b, m.scope(m.enclosingFunction(b))))
|
|
267
|
+
continue;
|
|
268
|
+
out.push(finding(m, b, "ts-open-redirect", "medium", why));
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
return out;
|
|
272
|
+
});
|
|
273
|
+
// --- #77 Secrets in logs ---------------------------------------------------------------------------------------------
|
|
274
|
+
const LOG_CALL = /(^|\.)(log|logger|LOG|LOGGER|Logger|_logger|_log|Log|slog|logging|Rails\.logger)\.(debug|info|warn|warning|error|fatal|trace|Debug|Info|Warn|Error|Fatal|Printf|Println|Print|Infof|Errorf|Debugf|Warnf|LogInformation|LogWarning|LogError|LogDebug|LogCritical|Information|Warning)$|^(puts|p|System\.out\.println|System\.err\.println|Console\.WriteLine|fmt\.Println|fmt\.Printf)$/;
|
|
275
|
+
export const tsSecretsInLogs = tsCheck("ts-secrets-in-logs", "logging of passwords or tokens", (index) => {
|
|
276
|
+
const out = [];
|
|
277
|
+
for (const s of index.calls()) {
|
|
278
|
+
if (!LOG_CALL.test(s.callee))
|
|
279
|
+
continue;
|
|
280
|
+
const secret = s.args.filter((a) => !s.module.isLiteral(a)).flatMap((a) => s.module.identifiers(a)).find((i) => SECRET_IDENT.test(i.replace(/^@/, "")));
|
|
281
|
+
if (secret)
|
|
282
|
+
out.push(finding(s.module, s.node, "ts-secret-logged", "medium", `Logs a value named "${secret}", which may be a credential or token.`));
|
|
283
|
+
}
|
|
284
|
+
return out;
|
|
285
|
+
});
|
|
286
|
+
// --- #86 XML external entities ------------------------------------------------------------------------------------------
|
|
287
|
+
const JAVA_XML_FACTORY = /(^|\.)(DocumentBuilderFactory|SAXParserFactory|XMLInputFactory|TransformerFactory|SchemaFactory)\.newInstance$|^new (SAXBuilder|SAXReader)$/;
|
|
288
|
+
const JAVA_XML_HARDENED = /disallow-doctype-decl|FEATURE_SECURE_PROCESSING|setExpandEntityReferences\(\s*false|SUPPORT_DTD|ACCESS_EXTERNAL_DTD|IS_SUPPORTING_EXTERNAL_ENTITIES/;
|
|
289
|
+
export const tsXxe = tsCheck("ts-xml-xxe", "XML parser configuration that allows external entities", (index) => [
|
|
290
|
+
...index.calls()
|
|
291
|
+
.filter((s) => s.module.lang === "java" && JAVA_XML_FACTORY.test(s.callee) && !JAVA_XML_HARDENED.test(s.module.source))
|
|
292
|
+
.map((s) => finding(s.module, s.node, "java-xml-factory-not-hardened", "medium", "This XML parser is created without disabling DTDs or external entities in the same file (XXE). Set disallow-doctype-decl or the secure-processing feature.")),
|
|
293
|
+
...markers(index, "csharp", ["member_access_expression"], /^DtdProcessing\.Parse$/, "dotnet-dtd-processing", "high", "DtdProcessing.Parse enables DTDs and external entities (XXE)."),
|
|
294
|
+
...markers(index, "csharp", ["object_creation_expression"], /^new XmlUrlResolver\b/, "dotnet-xml-url-resolver", "high", "XmlUrlResolver lets documents load external resources (XXE, SSRF)."),
|
|
295
|
+
...markers(index, "ruby", ["scope_resolution", "identifier", "call"], /(^|::|\.)(NOENT|DTDLOAD|noent|dtdload)$/, "nokogiri-noent", "high", "NOENT / DTDLOAD make Nokogiri substitute external entities or load DTDs (XXE)."),
|
|
296
|
+
]);
|
|
297
|
+
// --- #61 CSRF ------------------------------------------------------------------------------------------------------------
|
|
298
|
+
/** Framework CSRF protection: explicit opt-outs are findings; evidence of protection passes; otherwise manual. */
|
|
299
|
+
export const tsCsrf = codeCheck("ts-csrf", LANGS, async (ctx) => {
|
|
300
|
+
const index = await ctx.treeSitter();
|
|
301
|
+
const out = [
|
|
302
|
+
...index.calls()
|
|
303
|
+
.filter((s) => s.module.lang === "java" && (/csrf\(\)\.disable$/.test(s.callee) || (/\.csrf$/.test(s.callee) && /disable/.test(s.node.text))))
|
|
304
|
+
.map((s) => finding(s.module, s.node, "spring-csrf-disabled", "medium", "CSRF protection is disabled in Spring Security; safe only for token-authenticated, stateless APIs.")),
|
|
305
|
+
...markers(index, "csharp", ["attribute"], /^IgnoreAntiforgeryToken/, "aspnet-ignore-antiforgery", "low", "This action or controller opts out of anti-forgery validation; confirm it is safe."),
|
|
306
|
+
...index.calls()
|
|
307
|
+
.filter((s) => s.module.lang === "ruby" && (/^skip_forgery_protection$/.test(s.callee) || (/^skip_before_action$/.test(s.callee) && /verify_authenticity_token/.test(s.node.text))))
|
|
308
|
+
.map((s) => finding(s.module, s.node, "rails-csrf-skipped", "low", "CSRF verification is skipped for this controller; confirm it is safe (e.g. token-authenticated API).")),
|
|
309
|
+
];
|
|
310
|
+
if (out.length)
|
|
311
|
+
return outcomeFromFindings(out, "");
|
|
312
|
+
const protectedBy = [
|
|
313
|
+
index.mentions("java", /EnableWebSecurity|SecurityFilterChain|WebSecurityConfigurerAdapter/) ? "Spring Security (CSRF on by default)" : "",
|
|
314
|
+
index.mentions("csharp", /AutoValidateAntiforgeryToken|ValidateAntiForgeryToken|AddAntiforgery/) ? "ASP.NET anti-forgery validation" : "",
|
|
315
|
+
index.mentions("ruby", /protect_from_forgery|ActionController::Base/) ? "Rails forgery protection" : "",
|
|
316
|
+
index.mentions("go", /gorilla\/csrf|justinas\/nosurf|csrf\.Protect/) ? "a Go CSRF middleware" : "",
|
|
317
|
+
].filter(Boolean);
|
|
318
|
+
if (protectedBy.length)
|
|
319
|
+
return { status: "pass", detail: `CSRF protection is in place: ${protectedBy.join("; ")}. No opt-outs found.` };
|
|
320
|
+
return { status: "manual", detail: "No CSRF protection or opt-out was found in the code. If the application uses cookie sessions, confirm state-changing requests are protected." };
|
|
321
|
+
});
|
|
322
|
+
//# sourceMappingURL=treesitter.js.map
|