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,100 @@
|
|
|
1
|
+
import { djangoSettings } from "../django/settings.js";
|
|
2
|
+
import { NO_PATTERN, pyFinding } from "../findings.js";
|
|
3
|
+
import { callArgs, identifiers, isNode, kwarg, strConst, walk } from "../python/index.js";
|
|
4
|
+
import { PASSWORD_IDENT, patternCheck, SECURITY_CONTEXT } from "./shared.js";
|
|
5
|
+
const WEAK_HASH = new Set([
|
|
6
|
+
"hashlib.md5", "hashlib.sha1",
|
|
7
|
+
"Crypto.Hash.MD5.new", "Crypto.Hash.SHA.new", "Crypto.Hash.SHA1.new",
|
|
8
|
+
"Cryptodome.Hash.MD5.new", "Cryptodome.Hash.SHA1.new",
|
|
9
|
+
]);
|
|
10
|
+
const WEAK_CIPHER_MODULE = /^(Crypto|Cryptodome)\.Cipher\.(DES|DES3|ARC2|ARC4|Blowfish|CAST|XOR)\.new$/;
|
|
11
|
+
const WEAK_CIPHER_ALG = /^cryptography\.hazmat\.primitives\.ciphers\.algorithms\.(TripleDES|ARC4|Blowfish|IDEA|CAST5|SEED)$/;
|
|
12
|
+
const WEAK_TLS_CONST = /^ssl\.PROTOCOL_(SSLv2|SSLv3|SSLv23|TLSv1|TLSv1_1)$/;
|
|
13
|
+
const WEAK_HASHERS = /(MD5|SHA1|UnsaltedMD5|UnsaltedSHA1|Crypt)PasswordHasher$/;
|
|
14
|
+
const FAST_HASH = /^hashlib\.(md5|sha1|sha224|sha256|sha384|sha512|sha3_\w+|blake2[bs]|new)$/;
|
|
15
|
+
const RANDOM_FN = /^random\.(random|randint|randrange|choice|choices|getrandbits|sample|uniform)$/;
|
|
16
|
+
const HTTP_CLIENT = /^(requests|httpx)\.(get|post|put|patch|delete|head|options|request|Client|AsyncClient|Session)$/;
|
|
17
|
+
const hashAlg = (call) => strConst(callArgs(call)[0] ?? kwarg(call, "name"))?.toLowerCase();
|
|
18
|
+
/** #19 Weak / unproven algorithms. */
|
|
19
|
+
export const weakCrypto = patternCheck("py-weak-crypto", ["python"], (index, ctx) => {
|
|
20
|
+
const out = [];
|
|
21
|
+
for (const site of index.calls()) {
|
|
22
|
+
const { module, node, name = "" } = site;
|
|
23
|
+
if (kwarg(node, "usedforsecurity")?.["value"] === false)
|
|
24
|
+
continue; // explicitly non-security use
|
|
25
|
+
const isWeakHash = WEAK_HASH.has(name) || (name === "hashlib.new" && ["md5", "sha1"].includes(hashAlg(node) ?? ""));
|
|
26
|
+
if (isWeakHash) {
|
|
27
|
+
// MD5/SHA-1 are fine for cache keys/ETags; only security use is a definite problem.
|
|
28
|
+
const security = SECURITY_CONTEXT.test(module.line(node._l)) || SECURITY_CONTEXT.test(site.fn?.["name"] ?? "");
|
|
29
|
+
out.push(pyFinding(module, node, "py-weak-hash", security ? "high" : "low", security ? "MD5/SHA-1 are broken for security purposes (collisions, fast brute force)." : "MD5/SHA-1 in use; acceptable only for non-security checksums. Confirm how it is used."));
|
|
30
|
+
}
|
|
31
|
+
else if (WEAK_CIPHER_MODULE.test(name) || WEAK_CIPHER_ALG.test(name)) {
|
|
32
|
+
out.push(pyFinding(module, node, "py-weak-cipher", "high", "DES/3DES/RC4/Blowfish-class ciphers are deprecated and breakable."));
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
for (const module of index.modules) {
|
|
36
|
+
for (const n of walk(module.tree)) {
|
|
37
|
+
if (n._t === "Attribute" && WEAK_TLS_CONST.test(module.qualify(`${dottedAttr(n)}`) ?? "")) {
|
|
38
|
+
out.push(pyFinding(module, n, "py-weak-tls-protocol", "high", "Selects SSL/TLS protocol versions with known weaknesses."));
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
if (ctx.project.frameworks.includes("django")) {
|
|
43
|
+
const hashers = djangoSettings(index).get("PASSWORD_HASHERS");
|
|
44
|
+
if (hashers && Array.isArray(hashers.value) && typeof hashers.value[0] === "string" && WEAK_HASHERS.test(hashers.value[0])) {
|
|
45
|
+
out.push(djangoSettings(index).findingAt(hashers.at, "django-weak-password-hasher", "high", "Passwords are hashed with a weak default hasher (MD5/SHA-1/crypt)."));
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return out;
|
|
49
|
+
}, (n) => NO_PATTERN("use of weak hashes, ciphers or TLS protocol constants", n));
|
|
50
|
+
/** #20 Incorrect use of primitives. */
|
|
51
|
+
export const cryptoMisuse = patternCheck("py-crypto-misuse", ["python"], (index) => {
|
|
52
|
+
const out = [];
|
|
53
|
+
for (const site of index.calls()) {
|
|
54
|
+
const { module, node, name = "" } = site;
|
|
55
|
+
const args = [...callArgs(node), ...((node["keywords"] ?? []).map((k) => k["value"]))];
|
|
56
|
+
if (FAST_HASH.test(name) && callArgs(node).some((a) => identifiers(a).some((i) => PASSWORD_IDENT.test(i)))) {
|
|
57
|
+
out.push(pyFinding(module, node, "py-fast-password-hash", "high", "Passwords hashed with a fast, unsalted hash can be brute-forced; use a password hasher (PBKDF2, bcrypt, Argon2)."));
|
|
58
|
+
}
|
|
59
|
+
else if (RANDOM_FN.test(name) && SECURITY_CONTEXT.test(module.line(node._l))) {
|
|
60
|
+
out.push(pyFinding(module, node, "py-insecure-random", "medium", "The random module is predictable; use the secrets module for tokens, passwords and keys."));
|
|
61
|
+
}
|
|
62
|
+
else if (/\.(AES|DES3?|Blowfish)\.new$/.test(name) || /ciphers\.Cipher$/.test(name)) {
|
|
63
|
+
if (args.some((a) => isNode(a) && a._t === "Constant" && a["_bytes"])) {
|
|
64
|
+
out.push(pyFinding(module, node, "py-static-key-iv", "medium", "A hard-coded key or IV makes encryption predictable or reusable."));
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
else if (/\.modes\.ECB$/.test(name)) {
|
|
68
|
+
out.push(pyFinding(module, node, "py-ecb-mode", "high", "ECB mode leaks plaintext patterns; use an authenticated mode such as GCM."));
|
|
69
|
+
}
|
|
70
|
+
else if (name === "ssl._create_unverified_context") {
|
|
71
|
+
out.push(pyFinding(module, node, "py-tls-unverified", "high", "Disables TLS certificate verification, allowing man-in-the-middle attacks."));
|
|
72
|
+
}
|
|
73
|
+
else if (HTTP_CLIENT.test(name) && kwarg(node, "verify")?.["value"] === false) {
|
|
74
|
+
out.push(pyFinding(module, node, "py-tls-verify-false", "high", "verify=False disables TLS certificate verification, allowing man-in-the-middle attacks."));
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
for (const module of index.modules) {
|
|
78
|
+
for (const n of walk(module.tree)) {
|
|
79
|
+
if (n._t === "Attribute" && n["attr"] === "MODE_ECB") {
|
|
80
|
+
out.push(pyFinding(module, n, "py-ecb-mode", "high", "ECB mode leaks plaintext patterns; use an authenticated mode such as GCM."));
|
|
81
|
+
}
|
|
82
|
+
else if (n._t === "Attribute" && module.qualify(dottedAttr(n)) === "ssl.CERT_NONE") {
|
|
83
|
+
out.push(pyFinding(module, n, "py-tls-cert-none", "medium", "ssl.CERT_NONE disables certificate verification."));
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
return out;
|
|
88
|
+
}, (n) => NO_PATTERN("cryptographic misuse (fast password hashes, insecure randomness, ECB, static keys/IVs, disabled TLS verification)", n));
|
|
89
|
+
function dottedAttr(n) {
|
|
90
|
+
const parts = [];
|
|
91
|
+
let cur = n;
|
|
92
|
+
while (isNode(cur) && cur._t === "Attribute") {
|
|
93
|
+
parts.unshift(cur["attr"]);
|
|
94
|
+
cur = cur["value"];
|
|
95
|
+
}
|
|
96
|
+
if (isNode(cur) && cur._t === "Name")
|
|
97
|
+
parts.unshift(cur["id"]);
|
|
98
|
+
return parts.join(".");
|
|
99
|
+
}
|
|
100
|
+
//# sourceMappingURL=crypto.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"crypto.js","sourceRoot":"","sources":["../../../src/code/checks/crypto.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export declare const djangoDebug: import("../types.js").CodeCheck;
|
|
2
|
+
export declare const djangoCsrf: import("../types.js").CodeCheck;
|
|
3
|
+
export declare const djangoSessionSecure: import("../types.js").CodeCheck;
|
|
4
|
+
export declare const djangoSessionHttpOnly: import("../types.js").CodeCheck;
|
|
5
|
+
export declare const djangoSessionDomain: import("../types.js").CodeCheck;
|
|
6
|
+
export declare const djangoSessionPath: import("../types.js").CodeCheck;
|
|
7
|
+
export declare const djangoSessionExpiry: import("../types.js").CodeCheck;
|
|
8
|
+
export declare const djangoMediaRoot: import("../types.js").CodeCheck;
|
|
9
|
+
export declare const djangoLoginRotation: import("../types.js").CodeCheck;
|
|
10
|
+
export declare const djangoAuthRateLimit: import("../types.js").CodeCheck;
|
|
11
|
+
export declare const djangoUploadPath: import("../types.js").CodeCheck;
|
|
12
|
+
export declare const djangoUploadSize: import("../types.js").CodeCheck;
|
|
13
|
+
export declare const djangoUploadType: import("../types.js").CodeCheck;
|
|
14
|
+
//# sourceMappingURL=django.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"django.d.ts","sourceRoot":"","sources":["../../../src/code/checks/django.ts"],"names":[],"mappings":"AAkCA,eAAO,MAAM,WAAW,iCAgBtB,CAAC;AAKH,eAAO,MAAM,UAAU,iCAyBrB,CAAC;AAkBH,eAAO,MAAM,mBAAmB,iCAG/B,CAAC;AAEF,eAAO,MAAM,qBAAqB,iCAGjC,CAAC;AAEF,eAAO,MAAM,mBAAmB,iCAS9B,CAAC;AAEH,eAAO,MAAM,iBAAiB,iCAM5B,CAAC;AAIH,eAAO,MAAM,mBAAmB,iCAmB9B,CAAC;AAGH,eAAO,MAAM,eAAe,iCA6B1B,CAAC;AAKH,eAAO,MAAM,mBAAmB,iCAwB9B,CAAC;AAMH,eAAO,MAAM,mBAAmB,iCAuB9B,CAAC;AAiCH,eAAO,MAAM,gBAAgB,iCAkB3B,CAAC;AAEH,eAAO,MAAM,gBAAgB,iCAc3B,CAAC;AAEH,eAAO,MAAM,gBAAgB,iCAiB3B,CAAC"}
|
|
@@ -0,0 +1,313 @@
|
|
|
1
|
+
import { djangoSettings, DYNAMIC, describe } from "../django/settings.js";
|
|
2
|
+
import { locations, outcomeFromFindings, pyFinding } from "../findings.js";
|
|
3
|
+
import { callArgs, dotted, isNode, kwarg, strConst, walk } from "../python/index.js";
|
|
4
|
+
import { codeCheck, decorators, functionDefs, walkStatements } from "./shared.js";
|
|
5
|
+
const NO_SETTINGS = {
|
|
6
|
+
status: "manual",
|
|
7
|
+
detail: "Django detected but no settings module was found (looked for settings.py / settings/*.py / files defining INSTALLED_APPS). Review the configuration manually.",
|
|
8
|
+
};
|
|
9
|
+
/** A Django check that needs the parsed settings. */
|
|
10
|
+
function settingsCheck(key, run) {
|
|
11
|
+
return codeCheck(key, ["django"], async (ctx) => {
|
|
12
|
+
const index = await ctx.python();
|
|
13
|
+
const settings = djangoSettings(index);
|
|
14
|
+
return settings.found ? run(settings, index, ctx) : NO_SETTINGS;
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
/** Production value of a setting; dev-only definitions are treated as absent (Django's default applies in production). */
|
|
18
|
+
function prodValue(settings, name) {
|
|
19
|
+
const v = settings.get(name);
|
|
20
|
+
return v && !v.devOnly ? v : undefined;
|
|
21
|
+
}
|
|
22
|
+
const dynamicOutcome = (name, v) => ({
|
|
23
|
+
status: "manual",
|
|
24
|
+
detail: `${name} is computed at runtime (${v.at.module.path}:${v.at.line}); confirm the production value.`,
|
|
25
|
+
evidence: [v.at.module.line(v.at.line).trim()],
|
|
26
|
+
});
|
|
27
|
+
// --- #3 Debug functionality ----------------------------------------------------
|
|
28
|
+
export const djangoDebug = settingsCheck("django-debug", (settings) => {
|
|
29
|
+
const findings = [];
|
|
30
|
+
const debug = prodValue(settings, "DEBUG");
|
|
31
|
+
if (debug?.value === true) {
|
|
32
|
+
findings.push(settings.findingAt(debug.at, "django-debug-true", "high", "DEBUG = True in production shows stack traces, settings and SQL to visitors."));
|
|
33
|
+
}
|
|
34
|
+
const apps = prodValue(settings, "INSTALLED_APPS");
|
|
35
|
+
if (apps && Array.isArray(apps.value) && apps.value.some((a) => typeof a === "string" && /^(debug_toolbar|silk|django_extensions)$/.test(a))) {
|
|
36
|
+
findings.push(settings.findingAt(apps.at, "django-debug-apps", "medium", "Debug/profiling apps are enabled in non-development settings."));
|
|
37
|
+
}
|
|
38
|
+
const propagate = prodValue(settings, "DEBUG_PROPAGATE_EXCEPTIONS");
|
|
39
|
+
if (propagate?.value === true) {
|
|
40
|
+
findings.push(settings.findingAt(propagate.at, "django-debug-propagate", "medium", "DEBUG_PROPAGATE_EXCEPTIONS exposes raw exceptions to the server/client."));
|
|
41
|
+
}
|
|
42
|
+
if (findings.length === 0 && debug?.value === DYNAMIC)
|
|
43
|
+
return dynamicOutcome("DEBUG", debug);
|
|
44
|
+
return outcomeFromFindings(findings, debug ? `DEBUG is ${describe(debug.value)} in production settings; no debug apps enabled.` : "DEBUG is not enabled in production settings (Django default: False); no debug apps enabled.");
|
|
45
|
+
});
|
|
46
|
+
// --- #61 CSRF ------------------------------------------------------------------
|
|
47
|
+
const CSRF_MW = "django.middleware.csrf.CsrfViewMiddleware";
|
|
48
|
+
export const djangoCsrf = settingsCheck("django-csrf", (settings, index) => {
|
|
49
|
+
const mw = prodValue(settings, "MIDDLEWARE") ?? prodValue(settings, "MIDDLEWARE_CLASSES");
|
|
50
|
+
if (!mw)
|
|
51
|
+
return { status: "manual", detail: "MIDDLEWARE is not defined in the settings found; review CSRF protection manually." };
|
|
52
|
+
if (mw.value === DYNAMIC)
|
|
53
|
+
return dynamicOutcome("MIDDLEWARE", mw);
|
|
54
|
+
if (!Array.isArray(mw.value) || !mw.value.includes(CSRF_MW)) {
|
|
55
|
+
return outcomeFromFindings([settings.findingAt(mw.at, "django-csrf-middleware-missing", "high", "Without CsrfViewMiddleware, state-changing requests can be forged from other sites.")], "");
|
|
56
|
+
}
|
|
57
|
+
// Exempted views: legitimate for webhooks, but each must be justified.
|
|
58
|
+
const exempt = [];
|
|
59
|
+
for (const { module, node } of functionDefs(index)) {
|
|
60
|
+
if (decorators(module, node).some((d) => d.endsWith("csrf_exempt"))) {
|
|
61
|
+
exempt.push(pyFinding(module, node, "django-csrf-exempt", "low", "This view is exempt from CSRF protection; confirm it changes no state or uses another defence (e.g. signed webhooks)."));
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
for (const s of index.calls()) {
|
|
65
|
+
if (s.name?.endsWith("csrf_exempt"))
|
|
66
|
+
exempt.push(pyFinding(s.module, s.node, "django-csrf-exempt", "low", "This view is exempt from CSRF protection; confirm it is safe."));
|
|
67
|
+
}
|
|
68
|
+
if (exempt.length) {
|
|
69
|
+
return { status: "manual", detail: `CsrfViewMiddleware is enabled, but ${exempt.length} view(s) are exempted: ${locations(exempt)}. Review each.`, findings: exempt };
|
|
70
|
+
}
|
|
71
|
+
return { status: "pass", detail: `CsrfViewMiddleware is enabled (${mw.at.module.path}:${mw.at.line}) and no views are exempted.` };
|
|
72
|
+
});
|
|
73
|
+
// --- #67-#70, #76 Session cookie settings -------------------------------------
|
|
74
|
+
function boolSetting(name, defaultValue, insecure, why, rule) {
|
|
75
|
+
return (settings) => {
|
|
76
|
+
const v = prodValue(settings, name);
|
|
77
|
+
if (v?.value === DYNAMIC)
|
|
78
|
+
return dynamicOutcome(name, v);
|
|
79
|
+
const effective = v ? v.value : defaultValue;
|
|
80
|
+
if (effective === insecure) {
|
|
81
|
+
const f = v
|
|
82
|
+
? settings.findingAt(v.at, rule, "high", why)
|
|
83
|
+
: settings.findingAbsent(name, String(defaultValue), rule, "high", why);
|
|
84
|
+
return { status: "fail", detail: `${name} is ${String(effective)}${v ? ` (${v.at.module.path}:${v.at.line})` : " (not set; Django default)"}. ${why}`, findings: [f] };
|
|
85
|
+
}
|
|
86
|
+
return { status: "pass", detail: `${name} is ${String(effective)}${v ? ` (${v.at.module.path}:${v.at.line})` : " (Django default)"}.` };
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
export const djangoSessionSecure = settingsCheck("django-session-cookie-secure", boolSetting("SESSION_COOKIE_SECURE", false, false, "The session cookie can be sent over plain HTTP and intercepted.", "django-session-cookie-secure"));
|
|
90
|
+
export const djangoSessionHttpOnly = settingsCheck("django-session-cookie-httponly", boolSetting("SESSION_COOKIE_HTTPONLY", true, false, "JavaScript (including injected scripts) can read the session cookie.", "django-session-cookie-httponly"));
|
|
91
|
+
export const djangoSessionDomain = settingsCheck("django-session-cookie-domain", (settings) => {
|
|
92
|
+
const v = prodValue(settings, "SESSION_COOKIE_DOMAIN");
|
|
93
|
+
if (!v || v.value === null)
|
|
94
|
+
return { status: "pass", detail: "SESSION_COOKIE_DOMAIN is not set, so the session cookie is host-only (most restrictive)." };
|
|
95
|
+
if (v.value === DYNAMIC)
|
|
96
|
+
return dynamicOutcome("SESSION_COOKIE_DOMAIN", v);
|
|
97
|
+
if (typeof v.value === "string" && v.value.startsWith(".")) {
|
|
98
|
+
const why = "A leading-dot domain shares the session cookie with every subdomain.";
|
|
99
|
+
return { status: "fail", detail: `SESSION_COOKIE_DOMAIN is "${v.value}". ${why}`, findings: [settings.findingAt(v.at, "django-session-cookie-domain", "medium", why)] };
|
|
100
|
+
}
|
|
101
|
+
return { status: "manual", detail: `SESSION_COOKIE_DOMAIN is ${describe(v.value)} (${v.at.module.path}:${v.at.line}); this also covers subdomains. Confirm it is the site's exact host and no untrusted subdomains exist.` };
|
|
102
|
+
});
|
|
103
|
+
export const djangoSessionPath = settingsCheck("django-session-cookie-path", (settings) => {
|
|
104
|
+
const v = prodValue(settings, "SESSION_COOKIE_PATH");
|
|
105
|
+
if (v?.value === DYNAMIC)
|
|
106
|
+
return dynamicOutcome("SESSION_COOKIE_PATH", v);
|
|
107
|
+
const value = v ? describe(v.value) : '"/" (Django default)';
|
|
108
|
+
// Whether "/" is restrictive enough depends on where the app is mounted, which the code does not say.
|
|
109
|
+
return { status: "manual", detail: `SESSION_COOKIE_PATH is ${value}. Restrictive only if it matches the path the application is mounted at; confirm other apps do not share this host/path.` };
|
|
110
|
+
});
|
|
111
|
+
const MAX_SESSION_AGE = 24 * 60 * 60;
|
|
112
|
+
export const djangoSessionExpiry = settingsCheck("django-session-expiry", (settings) => {
|
|
113
|
+
const age = prodValue(settings, "SESSION_COOKIE_AGE");
|
|
114
|
+
const atClose = prodValue(settings, "SESSION_EXPIRE_AT_BROWSER_CLOSE");
|
|
115
|
+
if (age?.value === DYNAMIC)
|
|
116
|
+
return dynamicOutcome("SESSION_COOKIE_AGE", age);
|
|
117
|
+
const seconds = age ? age.value : 1_209_600;
|
|
118
|
+
const extra = atClose?.value === true ? " SESSION_EXPIRE_AT_BROWSER_CLOSE is True." : "";
|
|
119
|
+
if (typeof seconds !== "number")
|
|
120
|
+
return { status: "manual", detail: `SESSION_COOKIE_AGE has an unexpected value ${describe(seconds)}.` };
|
|
121
|
+
if (seconds > MAX_SESSION_AGE) {
|
|
122
|
+
const why = `Sessions stay valid for ${Math.round(seconds / 3600)} hours; long-lived sessions widen the window for hijacking.`;
|
|
123
|
+
const f = age
|
|
124
|
+
? settings.findingAt(age.at, "django-session-age", "medium", why)
|
|
125
|
+
: settings.findingAbsent("SESSION_COOKIE_AGE", "1209600 (2 weeks)", "django-session-age", "medium", why);
|
|
126
|
+
return {
|
|
127
|
+
status: "fail",
|
|
128
|
+
detail: `SESSION_COOKIE_AGE is ${seconds}s${age ? "" : " (Django default, 2 weeks)"}; this tool expects at most ${MAX_SESSION_AGE}s (24h) as INSA sets no figure.${extra}`,
|
|
129
|
+
findings: [f],
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
return { status: "pass", detail: `Sessions expire after ${seconds}s (${age.at.module.path}:${age.at.line}).${extra}` };
|
|
133
|
+
});
|
|
134
|
+
// --- #78 Uploads stored in the document root ----------------------------------
|
|
135
|
+
export const djangoMediaRoot = settingsCheck("django-media-root", (settings, index) => {
|
|
136
|
+
const media = prodValue(settings, "MEDIA_ROOT");
|
|
137
|
+
const stat = prodValue(settings, "STATIC_ROOT");
|
|
138
|
+
const findings = [];
|
|
139
|
+
const text = (v) => (v ? v.at.module.text(v.at.node["value"] ?? v.at.node) : "");
|
|
140
|
+
if (media && stat && text(media) && text(media) === text(stat)) {
|
|
141
|
+
findings.push(settings.findingAt(media.at, "django-media-equals-static", "high", "Uploads share the static files directory, which is served directly by the web server."));
|
|
142
|
+
}
|
|
143
|
+
else if (media && /static/i.test(text(media))) {
|
|
144
|
+
findings.push(settings.findingAt(media.at, "django-media-in-static", "medium", "MEDIA_ROOT appears to sit inside the static files tree."));
|
|
145
|
+
}
|
|
146
|
+
// Django serving media itself outside of DEBUG.
|
|
147
|
+
for (const module of index.modules.filter((m) => /(^|\/)urls\.py$/.test(m.path))) {
|
|
148
|
+
for (const stmt of module.tree["body"]) {
|
|
149
|
+
if (stmt._t === "If" && /DEBUG/.test(module.text(stmt["test"])))
|
|
150
|
+
continue;
|
|
151
|
+
for (const n of walk(stmt)) {
|
|
152
|
+
if (n._t !== "Call" || !/(^|\.)static$/.test(module.qualify(dotted(n["func"])) ?? ""))
|
|
153
|
+
continue;
|
|
154
|
+
if (/MEDIA_URL/.test(module.text(n))) {
|
|
155
|
+
findings.push(pyFinding(module, n, "django-serves-media", "medium", "Uploaded files are served by Django outside DEBUG; serve them from a non-executing location via the web server."));
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
if (findings.length)
|
|
161
|
+
return outcomeFromFindings(findings, "");
|
|
162
|
+
return {
|
|
163
|
+
status: "manual",
|
|
164
|
+
detail: media
|
|
165
|
+
? `MEDIA_ROOT = ${text(media) || describe(media.value)} (${media.at.module.path}:${media.at.line}) is separate from static files. Confirm the web server serves it without executing scripts.`
|
|
166
|
+
: "MEDIA_ROOT is not set. Confirm where uploads are stored and that they are not served from an executable location.",
|
|
167
|
+
};
|
|
168
|
+
});
|
|
169
|
+
// --- #64 / #73 Session regeneration on login / session fixation ---------------
|
|
170
|
+
const USER_SESSION_KEY = /user|uid|auth|logged|account|member/i;
|
|
171
|
+
export const djangoLoginRotation = codeCheck("django-login-session-rotation", ["django"], async (ctx) => {
|
|
172
|
+
const index = await ctx.python();
|
|
173
|
+
const usesAuthLogin = index.calls().some((s) => /^django\.contrib\.auth\.(login|views\.LoginView(\.as_view)?)$/.test(s.name ?? "") || s.name === "auth.login") ||
|
|
174
|
+
index.modules.some((m) => /django\.contrib\.auth\.urls|allauth\.urls|dj_rest_auth/.test(m.lines.join("\n")));
|
|
175
|
+
const findings = [];
|
|
176
|
+
for (const module of index.modules) {
|
|
177
|
+
for (const n of walk(module.tree)) {
|
|
178
|
+
if (n._t !== "Assign")
|
|
179
|
+
continue;
|
|
180
|
+
for (const t of n["targets"]) {
|
|
181
|
+
if (t._t !== "Subscript" || !/request\.session$/.test(dotted(t["value"]) ?? ""))
|
|
182
|
+
continue;
|
|
183
|
+
const key = strConst(t["slice"]);
|
|
184
|
+
if (!key || !USER_SESSION_KEY.test(key))
|
|
185
|
+
continue;
|
|
186
|
+
findings.push(pyFinding(module, n, "django-manual-session-login", "medium", "Hand-rolled login stores the user in the existing session without rotating its key (session fixation); use django.contrib.auth.login() or request.session.cycle_key()."));
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
// A cycle_key()/flush() anywhere in the same file is treated as rotation.
|
|
191
|
+
const rotated = new Set(index.calls().filter((s) => s.method === "cycle_key" || s.method === "flush").map((s) => s.module.path));
|
|
192
|
+
const real = findings.filter((f) => !rotated.has(f.file));
|
|
193
|
+
if (real.length)
|
|
194
|
+
return outcomeFromFindings(real, "");
|
|
195
|
+
if (usesAuthLogin)
|
|
196
|
+
return { status: "pass", detail: "Logins go through django.contrib.auth.login(), which rotates the session key; no hand-rolled session logins found." };
|
|
197
|
+
return { status: "manual", detail: "No use of django.contrib.auth.login() found and no hand-rolled session login detected; review how users are authenticated." };
|
|
198
|
+
});
|
|
199
|
+
// --- #37 Rate limiting on authentication ---------------------------------------
|
|
200
|
+
const RATE_APPS = /^(axes|defender|django_ratelimit|ratelimit|rest_framework\.throttling)$/;
|
|
201
|
+
const AUTH_NAME = /login|signin|sign_in|auth|token|password|otp|register|signup/i;
|
|
202
|
+
export const djangoAuthRateLimit = codeCheck("django-auth-rate-limit", ["django"], async (ctx) => {
|
|
203
|
+
const index = await ctx.python();
|
|
204
|
+
const settings = djangoSettings(index);
|
|
205
|
+
const evidence = [];
|
|
206
|
+
const apps = prodValue(settings, "INSTALLED_APPS");
|
|
207
|
+
if (apps && Array.isArray(apps.value)) {
|
|
208
|
+
for (const a of apps.value)
|
|
209
|
+
if (typeof a === "string" && RATE_APPS.test(a))
|
|
210
|
+
evidence.push(`INSTALLED_APPS includes ${a} (${apps.at.module.path}:${apps.at.line})`);
|
|
211
|
+
}
|
|
212
|
+
for (const { module, node } of functionDefs(index)) {
|
|
213
|
+
if (decorators(module, node).some((d) => /ratelimit$/.test(d)) && AUTH_NAME.test(node["name"])) {
|
|
214
|
+
evidence.push(`@ratelimit on ${node["name"]} (${module.path}:${node._l})`);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
const rf = prodValue(settings, "REST_FRAMEWORK");
|
|
218
|
+
if (rf && rf.value !== DYNAMIC && typeof rf.value === "object" && rf.value !== null && !Array.isArray(rf.value)) {
|
|
219
|
+
const classes = rf.value["DEFAULT_THROTTLE_CLASSES"];
|
|
220
|
+
if (Array.isArray(classes) && classes.length)
|
|
221
|
+
evidence.push(`REST_FRAMEWORK DEFAULT_THROTTLE_CLASSES set (${rf.at.module.path}:${rf.at.line})`);
|
|
222
|
+
}
|
|
223
|
+
if (evidence.length)
|
|
224
|
+
return { status: "pass", detail: "Application-level rate limiting is configured for authentication.", evidence };
|
|
225
|
+
return {
|
|
226
|
+
status: "manual",
|
|
227
|
+
detail: "No application-level rate limiting found (django-axes, django-defender, django-ratelimit, DRF throttling). It may be enforced by a reverse proxy or WAF; verify there.",
|
|
228
|
+
};
|
|
229
|
+
});
|
|
230
|
+
function uploads(index) {
|
|
231
|
+
const fields = [];
|
|
232
|
+
for (const module of index.modules) {
|
|
233
|
+
for (const stmt of walkStatements(module.tree)) {
|
|
234
|
+
if (stmt._t !== "Assign" || !isNode(stmt["value"]) || stmt["value"]._t !== "Call")
|
|
235
|
+
continue;
|
|
236
|
+
const call = stmt["value"];
|
|
237
|
+
const name = module.qualify(dotted(call["func"])) ?? "";
|
|
238
|
+
const m = /(^|\.)(models|forms|serializers)\.(FileField|ImageField)$|^(django\.db\.models|django\.forms|rest_framework\.serializers)\.(FileField|ImageField)$/.exec(name);
|
|
239
|
+
if (!m)
|
|
240
|
+
continue;
|
|
241
|
+
const t = stmt["targets"][0];
|
|
242
|
+
fields.push({
|
|
243
|
+
module,
|
|
244
|
+
node: stmt,
|
|
245
|
+
kind: name.endsWith("ImageField") ? "image" : "file",
|
|
246
|
+
validated: !!kwarg(call, "validators"),
|
|
247
|
+
name: t?._t === "Name" ? t["id"] : "",
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
const handlesFiles = fields.length > 0 || index.modules.some((m) => m.lines.some((l) => /request\.FILES/.test(l)));
|
|
252
|
+
return { fields, handlesFiles };
|
|
253
|
+
}
|
|
254
|
+
const NO_UPLOADS = (n) => ({ status: "pass", detail: `No file upload handling found in ${n} Python file(s) (no FileField/ImageField or request.FILES).` });
|
|
255
|
+
export const djangoUploadPath = codeCheck("django-upload-path", ["django"], async (ctx) => {
|
|
256
|
+
const index = await ctx.python();
|
|
257
|
+
if (!uploads(index).handlesFiles)
|
|
258
|
+
return NO_UPLOADS(index.modules.length);
|
|
259
|
+
const findings = [];
|
|
260
|
+
for (const s of index.calls()) {
|
|
261
|
+
if (!/^(open|io\.open)$/.test(s.name ?? ""))
|
|
262
|
+
continue;
|
|
263
|
+
const mode = strConst(callArgs(s.node)[1] ?? kwarg(s.node, "mode")) ?? "r";
|
|
264
|
+
const target = callArgs(s.node)[0];
|
|
265
|
+
if (!/[wax+]/.test(mode) || !target)
|
|
266
|
+
continue;
|
|
267
|
+
const scope = s.module.scope(s.fn);
|
|
268
|
+
// Client-supplied file name (upload.name) or other request data in the destination path.
|
|
269
|
+
const usesClientName = [...walk(target)].some((n) => n._t === "Attribute" && n["attr"] === "name" && s.module.isRequestDerived(n["value"], scope));
|
|
270
|
+
if (usesClientName || s.module.isRequestDerived(target, scope)) {
|
|
271
|
+
findings.push(pyFinding(s.module, s.node, "django-upload-client-filename", "high", "Writing an upload to a path built from the client-supplied name allows ../ to escape the upload directory; use Django storage (default_storage.save) or generate the name."));
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
return outcomeFromFindings(findings, "Uploads are not written with client-supplied paths (no open(..., 'w') using upload names or request data).");
|
|
275
|
+
});
|
|
276
|
+
export const djangoUploadSize = codeCheck("django-upload-size", ["django"], async (ctx) => {
|
|
277
|
+
const index = await ctx.python();
|
|
278
|
+
if (!uploads(index).handlesFiles)
|
|
279
|
+
return NO_UPLOADS(index.modules.length);
|
|
280
|
+
const evidence = [];
|
|
281
|
+
for (const module of index.modules) {
|
|
282
|
+
for (const n of walk(module.tree)) {
|
|
283
|
+
if (n._t === "Compare" && [...walk(n)].some((x) => x._t === "Attribute" && x["attr"] === "size"))
|
|
284
|
+
evidence.push(`${module.path}:${n._l}`);
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
if (evidence.length)
|
|
288
|
+
return { status: "pass", detail: `Upload size is checked in code (${evidence.slice(0, 3).join(", ")}).`, evidence };
|
|
289
|
+
return {
|
|
290
|
+
status: "manual",
|
|
291
|
+
detail: "The project handles uploads but no size check was found in code (Django does not limit file upload size by default). Confirm a limit at the web server (e.g. nginx client_max_body_size) or add validation.",
|
|
292
|
+
};
|
|
293
|
+
});
|
|
294
|
+
export const djangoUploadType = codeCheck("django-upload-type", ["django"], async (ctx) => {
|
|
295
|
+
const index = await ctx.python();
|
|
296
|
+
const inv = uploads(index);
|
|
297
|
+
if (!inv.handlesFiles)
|
|
298
|
+
return NO_UPLOADS(index.modules.length);
|
|
299
|
+
const all = index.modules.map((m) => m.lines.join("\n")).join("\n");
|
|
300
|
+
const typeCheckedSomewhere = /FileExtensionValidator|content_type|magic\.from_|filetype\.guess|imghdr|PIL\.Image|Image\.open/.test(all);
|
|
301
|
+
const findings = [];
|
|
302
|
+
for (const f of inv.fields) {
|
|
303
|
+
if (f.kind === "image" || f.validated)
|
|
304
|
+
continue; // ImageField verifies the file is an image
|
|
305
|
+
const cleaned = f.name && new RegExp(`def clean_${f.name}\\b`).test(all);
|
|
306
|
+
findings.push(pyFinding(f.module, f.node, "django-filefield-no-validation", typeCheckedSomewhere || cleaned ? "low" : "medium", "FileField accepts any file type unless validators (e.g. FileExtensionValidator) or content checks are applied."));
|
|
307
|
+
}
|
|
308
|
+
if (findings.length === 0 && inv.fields.length === 0) {
|
|
309
|
+
return { status: "manual", detail: "Uploads are handled via request.FILES without model/form fields; review type validation in the handling code." };
|
|
310
|
+
}
|
|
311
|
+
return outcomeFromFindings(findings, `All ${inv.fields.length} upload field(s) are ImageFields or have validators.`);
|
|
312
|
+
});
|
|
313
|
+
//# sourceMappingURL=django.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"django.js","sourceRoot":"","sources":["../../../src/code/checks/django.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export declare const flaskDebug: import("../types.js").CodeCheck;
|
|
2
|
+
export declare const flaskSessionSecure: import("../types.js").CodeCheck;
|
|
3
|
+
export declare const flaskSessionHttpOnly: import("../types.js").CodeCheck;
|
|
4
|
+
export declare const flaskCsrf: import("../types.js").CodeCheck;
|
|
5
|
+
export declare const flaskAuthRateLimit: import("../types.js").CodeCheck;
|
|
6
|
+
//# sourceMappingURL=flask.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"flask.d.ts","sourceRoot":"","sources":["../../../src/code/checks/flask.ts"],"names":[],"mappings":"AAYA,eAAO,MAAM,UAAU,iCAsBrB,CAAC;AA2DH,eAAO,MAAM,kBAAkB,iCAA+I,CAAC;AAC/K,eAAO,MAAM,oBAAoB,iCAAuJ,CAAC;AAGzL,eAAO,MAAM,SAAS,iCAIpB,CAAC;AAEH,eAAO,MAAM,kBAAkB,iCAI7B,CAAC"}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { outcomeFromFindings, pyFinding } from "../findings.js";
|
|
2
|
+
import { dotted, isTrue, kwarg, strConst, walk } from "../python/index.js";
|
|
3
|
+
import { codeCheck } from "./shared.js";
|
|
4
|
+
/** Configuration rules for Flask and FastAPI. The generic Python pattern rules cover the rest. */
|
|
5
|
+
const flaskCheck = (key, frameworks, run) => codeCheck(key, frameworks, async (ctx) => run(await ctx.python(), ctx));
|
|
6
|
+
// --- #3 Debug mode -------------------------------------------------------------------
|
|
7
|
+
export const flaskDebug = flaskCheck("flask-debug", ["flask", "fastapi"], (index) => {
|
|
8
|
+
const out = [];
|
|
9
|
+
for (const s of index.calls()) {
|
|
10
|
+
if (s.method === "run" && isTrue(kwarg(s.node, "debug"))) {
|
|
11
|
+
out.push(pyFinding(s.module, s.node, "flask-run-debug", "high", "debug=True enables the interactive debugger, which lets anyone who triggers an error run code on the server."));
|
|
12
|
+
}
|
|
13
|
+
else if (/(^|\.)FastAPI$/.test(s.name ?? "") && isTrue(kwarg(s.node, "debug"))) {
|
|
14
|
+
out.push(pyFinding(s.module, s.node, "fastapi-debug", "high", "debug=True returns stack traces to clients."));
|
|
15
|
+
}
|
|
16
|
+
else if (s.name === "uvicorn.run" && isTrue(kwarg(s.node, "reload"))) {
|
|
17
|
+
out.push(pyFinding(s.module, s.node, "uvicorn-reload", "medium", "reload=True is a development setting; do not run production with it."));
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
for (const { module, node, value } of configAssignments(index, "DEBUG")) {
|
|
21
|
+
if (isTrue(value))
|
|
22
|
+
out.push(pyFinding(module, node, "flask-config-debug", "high", "DEBUG is enabled in the application configuration."));
|
|
23
|
+
}
|
|
24
|
+
for (const module of index.modules) {
|
|
25
|
+
for (const n of walk(module.tree)) {
|
|
26
|
+
if (n._t === "Assign" && /(^|\.)app\.debug$/.test(dotted(n["targets"][0]) ?? "") && isTrue(n["value"])) {
|
|
27
|
+
out.push(pyFinding(module, n, "flask-app-debug", "high", "app.debug = True enables the interactive debugger."));
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return outcomeFromFindings(out, `No debug mode enabled in ${index.modules.length} Python file(s).`);
|
|
32
|
+
});
|
|
33
|
+
/**
|
|
34
|
+
* Values assigned to a Flask config key, in any of the usual forms:
|
|
35
|
+
* app.config["KEY"] = v, app.config.update(KEY=v), and KEY = v in a module or Config class.
|
|
36
|
+
*/
|
|
37
|
+
function configAssignments(index, key) {
|
|
38
|
+
const out = [];
|
|
39
|
+
for (const module of index.modules) {
|
|
40
|
+
for (const n of walk(module.tree)) {
|
|
41
|
+
if (n._t === "Assign") {
|
|
42
|
+
const target = n["targets"][0];
|
|
43
|
+
const value = n["value"];
|
|
44
|
+
if (target._t === "Name" && target["id"] === key)
|
|
45
|
+
out.push({ module, node: n, value });
|
|
46
|
+
else if (target._t === "Subscript" && /config$/.test(dotted(target["value"]) ?? "") && strConst(target["slice"]) === key) {
|
|
47
|
+
out.push({ module, node: n, value });
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
else if (n._t === "Call" && /config\.(update|from_mapping)$/.test(dotted(n["func"]) ?? "")) {
|
|
51
|
+
const v = kwarg(n, key);
|
|
52
|
+
if (v)
|
|
53
|
+
out.push({ module, node: n, value: v });
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return out;
|
|
58
|
+
}
|
|
59
|
+
function cookieFlag(key, setting, defaultValue, why) {
|
|
60
|
+
return flaskCheck(key, ["flask"], (index) => {
|
|
61
|
+
const values = configAssignments(index, setting);
|
|
62
|
+
const literal = values.filter((v) => v.value._t === "Constant" && typeof v.value["value"] === "boolean");
|
|
63
|
+
const off = literal.filter((v) => v.value["value"] === false);
|
|
64
|
+
if (off.length) {
|
|
65
|
+
return outcomeFromFindings(off.map((v) => pyFinding(v.module, v.node, `${key}-false`, "high", why)), "");
|
|
66
|
+
}
|
|
67
|
+
if (literal.length) {
|
|
68
|
+
const at = literal[0];
|
|
69
|
+
return { status: "pass", detail: `${setting} is True (${at.module.path}:${at.node._l}).` };
|
|
70
|
+
}
|
|
71
|
+
if (values.length) {
|
|
72
|
+
const at = values[0];
|
|
73
|
+
return { status: "manual", detail: `${setting} is computed at runtime (${at.module.path}:${at.node._l}); confirm the production value.` };
|
|
74
|
+
}
|
|
75
|
+
if (defaultValue)
|
|
76
|
+
return { status: "pass", detail: `${setting} is not set; Flask's default is True.` };
|
|
77
|
+
const where = index.modules.find((m) => [...m.imports.values()].some((v) => v === "flask" || v.startsWith("flask.")));
|
|
78
|
+
return {
|
|
79
|
+
status: "fail",
|
|
80
|
+
detail: `${setting} is not set (Flask default: False). ${why}`,
|
|
81
|
+
findings: [{ file: where?.path ?? "(app)", line: 1, snippet: `${setting} not set (Flask default: False)`, why, confidence: "medium", method: "ast", rule: `${key}-missing` }],
|
|
82
|
+
};
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
export const flaskSessionSecure = cookieFlag("flask-session-cookie-secure", "SESSION_COOKIE_SECURE", false, "The session cookie can be sent over plain HTTP and intercepted.");
|
|
86
|
+
export const flaskSessionHttpOnly = cookieFlag("flask-session-cookie-httponly", "SESSION_COOKIE_HTTPONLY", true, "JavaScript (including injected scripts) can read the session cookie.");
|
|
87
|
+
// --- #61 CSRF / #37 rate limiting --------------------------------------------------------
|
|
88
|
+
export const flaskCsrf = flaskCheck("flask-csrf", ["flask"], (index) => {
|
|
89
|
+
const used = ["flask_wtf", "flask_seasurf"].filter((p) => index.imports(p));
|
|
90
|
+
if (used.length)
|
|
91
|
+
return { status: "pass", detail: `CSRF protection is used (${used.join(", ")}). Confirm CSRFProtect is initialised for the app and covers every state-changing route.` };
|
|
92
|
+
return { status: "manual", detail: "No CSRF extension (Flask-WTF, Flask-SeaSurf) found. If the app uses cookie sessions and forms, add one; token-only APIs are not affected." };
|
|
93
|
+
});
|
|
94
|
+
export const flaskAuthRateLimit = flaskCheck("flask-auth-rate-limit", ["flask", "fastapi"], (index) => {
|
|
95
|
+
const used = ["flask_limiter", "slowapi", "fastapi_limiter"].filter((p) => index.imports(p));
|
|
96
|
+
if (used.length)
|
|
97
|
+
return { status: "pass", detail: `Rate limiting is used (${used.join(", ")}). Confirm it is applied to login, password-reset and token endpoints.`, evidence: used };
|
|
98
|
+
return { status: "manual", detail: "No rate-limiting extension (Flask-Limiter, SlowAPI) found. It may be enforced by a reverse proxy or WAF; verify there." };
|
|
99
|
+
});
|
|
100
|
+
//# sourceMappingURL=flask.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"flask.js","sourceRoot":"","sources":["../../../src/code/checks/flask.ts"],"names":[],"mappings":"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"}
|