From 53e35e02bb4adc81f3dcdf83dd5e05273bcf91e2 Mon Sep 17 00:00:00 2001 From: Liana Perry <62174756+lperry022@users.noreply.github.com> Date: Sun, 7 Sep 2025 16:15:13 +1000 Subject: [PATCH 1/9] Remove scanner/venv from repo and add to .gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3e8b403 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +"scanner/venv/" From e9dc69fabaddb6105eba3269b825dd6472fefb30 Mon Sep 17 00:00:00 2001 From: Liana Perry <62174756+lperry022@users.noreply.github.com> Date: Sun, 7 Sep 2025 16:17:20 +1000 Subject: [PATCH 2/9] Add OWASP scanner code and workflow --- .github/workflows/scan.yml | 59 +++++++ scanner/__init__.py | 0 scanner/__pycache__/__init__.cpython-313.pyc | Bin 0 -> 162 bytes scanner/__pycache__/core.cpython-313.pyc | Bin 0 -> 7896 bytes scanner/__pycache__/main.cpython-313.pyc | Bin 0 -> 871 bytes scanner/core.py | 146 ++++++++++++++++++ scanner/main.py | 21 +++ scanner/rules/__init__.py | 0 .../__pycache__/__init__.cpython-313.pyc | Bin 0 -> 168 bytes .../__pycache__/auth_failures.cpython-313.pyc | Bin 0 -> 1602 bytes .../broken_access_control.cpython-313.pyc | Bin 0 -> 4148 bytes .../insecure_design.cpython-313.pyc | Bin 0 -> 1426 bytes .../integrity_failures.cpython-313.pyc | Bin 0 -> 1476 bytes .../logging_failures.cpython-313.pyc | Bin 0 -> 2132 bytes .../security_misconfig.cpython-313.pyc | Bin 0 -> 3273 bytes .../security_misconfiguration.cpython-313.pyc | Bin 0 -> 3280 bytes .../sensitive_data_exposure.cpython-313.pyc | Bin 0 -> 1604 bytes .../__pycache__/sql_injection.cpython-313.pyc | Bin 0 -> 1251 bytes .../rules/__pycache__/ssrf.cpython-313.pyc | Bin 0 -> 1537 bytes .../vulnerable_components.cpython-313.pyc | Bin 0 -> 1230 bytes scanner/rules/_template.py | 11 ++ scanner/rules/auth_failures.py | 45 ++++++ scanner/rules/broken_access_control.py | 94 +++++++++++ scanner/rules/insecure_design.py | 25 +++ scanner/rules/integrity_failures.py | 52 +++++++ scanner/rules/logging_failures.py | 50 ++++++ scanner/rules/security_misconfig.py | 86 +++++++++++ scanner/rules/sensitive_data_exposure.py | 38 +++++ scanner/rules/sql_injection.py | 39 +++++ scanner/rules/ssrf.py | 39 +++++ scanner/rules/vulnerable_components.py | 33 ++++ scanner/tests/test_negative.py | 43 ++++++ scanner/tests/test_positive.py | 95 ++++++++++++ 33 files changed, 876 insertions(+) create mode 100644 .github/workflows/scan.yml create mode 100644 scanner/__init__.py create mode 100644 scanner/__pycache__/__init__.cpython-313.pyc create mode 100644 scanner/__pycache__/core.cpython-313.pyc create mode 100644 scanner/__pycache__/main.cpython-313.pyc create mode 100644 scanner/core.py create mode 100644 scanner/main.py create mode 100644 scanner/rules/__init__.py create mode 100644 scanner/rules/__pycache__/__init__.cpython-313.pyc create mode 100644 scanner/rules/__pycache__/auth_failures.cpython-313.pyc create mode 100644 scanner/rules/__pycache__/broken_access_control.cpython-313.pyc create mode 100644 scanner/rules/__pycache__/insecure_design.cpython-313.pyc create mode 100644 scanner/rules/__pycache__/integrity_failures.cpython-313.pyc create mode 100644 scanner/rules/__pycache__/logging_failures.cpython-313.pyc create mode 100644 scanner/rules/__pycache__/security_misconfig.cpython-313.pyc create mode 100644 scanner/rules/__pycache__/security_misconfiguration.cpython-313.pyc create mode 100644 scanner/rules/__pycache__/sensitive_data_exposure.cpython-313.pyc create mode 100644 scanner/rules/__pycache__/sql_injection.cpython-313.pyc create mode 100644 scanner/rules/__pycache__/ssrf.cpython-313.pyc create mode 100644 scanner/rules/__pycache__/vulnerable_components.cpython-313.pyc create mode 100644 scanner/rules/_template.py create mode 100644 scanner/rules/auth_failures.py create mode 100644 scanner/rules/broken_access_control.py create mode 100644 scanner/rules/insecure_design.py create mode 100644 scanner/rules/integrity_failures.py create mode 100644 scanner/rules/logging_failures.py create mode 100644 scanner/rules/security_misconfig.py create mode 100644 scanner/rules/sensitive_data_exposure.py create mode 100644 scanner/rules/sql_injection.py create mode 100644 scanner/rules/ssrf.py create mode 100644 scanner/rules/vulnerable_components.py create mode 100644 scanner/tests/test_negative.py create mode 100644 scanner/tests/test_positive.py diff --git a/.github/workflows/scan.yml b/.github/workflows/scan.yml new file mode 100644 index 0000000..db7c865 --- /dev/null +++ b/.github/workflows/scan.yml @@ -0,0 +1,59 @@ +name: OWASP PR Scanner + +on: + pull_request: + paths: + - 'scanner/**' + - 'src/**' + - 'backend/**' + - '.github/workflows/**' + +jobs: + scan: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.11' + - name: Install deps + run: | + python -m pip install -U pip + if [ -f scanner/requirements.txt ]; then + pip install -r scanner/requirements.txt + elif [ -f requirements.txt ]; then + pip install -r requirements.txt + fi + - name: Determine changed files for this PR + if: ${{ github.event_name == 'pull_request' }} + run: | + BASE_SHA="${{ github.event.pull_request.base.sha }}" + HEAD_SHA="${{ github.event.pull_request.head.sha }}" + CHANGED_FILES=$(git diff --name-only "$BASE_SHA" "$HEAD_SHA" \ + | grep -E '\.(js|jsx|ts|tsx|py|java|go|rb|php|html|css|md)$' || true) + if [ -n "$CHANGED_FILES" ]; then + CHANGED_FILES=$(echo "$CHANGED_FILES" | grep -E '^(src/|backend/|scanner/)' || true) + fi + if [ -z "$CHANGED_FILES" ]; then + CHANGED_FILES="$(git ls-files src backend 2>/dev/null || true)" + fi + echo "CHANGED_FILES<> $GITHUB_ENV + echo "$CHANGED_FILES" >> $GITHUB_ENV + echo "EOF" >> $GITHUB_ENV + - name: Run OWASP scanner on changed files + run: | + if [ -z "${CHANGED_FILES}" ]; then + echo "Nothing to scan." + exit 0 + fi + EXIT=0 + while IFS= read -r file; do + [ -z "$file" ] && continue + echo "Scanning: $file" + python scanner/main.py --file "$file" || EXIT=1 + done <<< "${CHANGED_FILES}" + exit $EXIT diff --git a/scanner/__init__.py b/scanner/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/scanner/__pycache__/__init__.cpython-313.pyc b/scanner/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..309c0b1130a3049d5ff4cc4775b8d9832fd2107f GIT binary patch literal 162 zcmey&%ge<81l7|fWq|0%AOZ#$p^VQgK*m&tbOudEzm*I{OhDdekkl{%)A(v{N&Qy)Vz}7828K)kJ6-={PM)&0^Q=|#Js%Jq8Jz*AD@|*SrQ+wS5SG2 f!zMRBr8Fniu80+ABFM&K5aS~=BO_xGGmr%U3cV>l literal 0 HcmV?d00001 diff --git a/scanner/__pycache__/core.cpython-313.pyc b/scanner/__pycache__/core.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..84218431a68f2b6cb8de42c306d5d34c399a6be0 GIT binary patch literal 7896 zcma($TWlLwb~Bven<7P#)XTOsdfB2a(UxV|mSic`%eK4{m2fn%V|l|;W30R3st9dbxn zX3|_i=U(1>?(3X$&ugC7)EEdT+UbA0=5`RoAMp>R(51%n*P-zV!4fR_GXfQB5|Q$6 z6;jDHb&*DDhG13A1kzm9v=alR$w=Esv=c1VOtAETs@REiLDH#yjDNWOB|N#(0Homm z`6P*VJW2?}LV4U%nqBUIMmw>foRaY1*?YbbM!G!l}$eKc%cMPzzQCkE2*hp^LI_0PlN+ zAUMKfQ}8M^S6+u9W|s-VNC4gGWIN&s^8o-=@0`wx4lleN}RHUagQXDTrma6cVRjZ-XgTbDnNW~ni zF920M+C2#Hlhr76D(?ynPQ}tJK@-(Dr>t7WuZ30A3r1yaWv;UB37`y119hu#6OTEy zZb>&WHa~TK&hwT;NaWCCOdEs+P$I{iT1gw^qrPYqNosK=9Ew5}MWGeRAVz&CD&7o5 zuSwdF7zhPnj0y=+N$2$nzGdF)by5AapSaR1`h9}HqpM2ok09Q)a$BOV^S7l|Z#d!$cu_3O zdzT{ts6^M9aK!Hmi=!?;Xo9`OzXWiHcxZMce97jNXWQHzr+$C*cwD@Dd+m18mh^2~ zn3OeV>Bw80Ig4}0awMaQ(}gbQKYBm+rrCVgV6JO06WHq8I-a?fA9d$O-TBeibEB^( z$dCII(d1ZiJaOxzk*uXHtK+kmE@!VSc6%q!9s|4>;A5wnP$yRfhZvvqYkxTRx451r}JrFsK1z)V)egghk8(|Kb{eJ4C@; zd*}SaCMMt1`*l#>Y=eN(Vx`jjm)-@DVS_`-D~b-{{vI& zQzOwlPCl!p)JxkxqYeO04eG;ftohJM3{GKynT-Yk zi1-dzk`)43eSv^?H#u)h2dh%*6}%&Oxx)Z}WI9t`w?C)bpVxKdbRCcOcNSU?7aI2$ zpwZI()TpU3|B2G+4Mj4Rc@A^&abz;?hu%;AAERE)Z~)(JqK7D- z6)3T!Vu)6Pfxn=3glZ+0NT<%dsy+v=fmy9(0uf$hgh-U(Z-vCD>zqWQRdp=7)O*@V zN;;59k z*L_#E$(eNyX6uG_Ov43R-IeM{Yrn2Jof#{%w0?H|{`Frj$0t9W`<^1q zgO4n>@6}N6)SQOq|A^RJf7wz$HbQ+lVi-4SU-D+K@|H#H9cb(pG4TDIMK7l6J0(0c zOxRPZ3r>ZVv+ydg-xVZKFHCjPlF7r(OnGPLCZS>xmQ|U`Xb2CNNKMN|ZMWVspqSv5 zJu~qKs9ixAg_Xok0C$K#*qQj`BUA0&i)$AX(R&}Pf3QL4yGL`~qYq54JyR2owpX8E zSw$DUdAK16;NGH$@opgvVy%ZMyQe6)6)LFEIlw_b1K=cO=@RiCC38x_RiwtMA_XH8 zfGF+<0BXi!yZgb~2YCyVvw;1p1GRJ4vS#^4*YxVGupcdR{hxsU5TlTlc;SQ!Te^2> zfrQ?&$$?(5e9(*iNqE$(mesL(*1#HBlS?O199fB71e&82O86iK;8YOF)UamOqWH#h z2Jqi&vFJf<#WfZOL0x5D9cyE4<$1I+Pta7wweP`o;I##96Yo9#JSOs@K+`j%L+ygSMsS0i#+p_0e#XHnoe)IAzw$()o<^?=i zE-b?t%milw)!bJ7?ZHSpr-vv?t0;X2%cV|Pp;9Xx3mgKjyvn~HqL-y|8qNZ{b;!Hz zinW5oF+!-Vz9;Ay*iEos`3jU~r*T`x)QL0NL^ z7O)2@7uc^HNh*C{7M0OD_RyfpMw}-v!1EGpyXSE>d?L0R;&Q+dK;BA!m@^K}4mWC( zZ{(;NgIZaqJm041MD{S2e9i%Pl}CUa#B4|L7L~Y^vnzKL>MQgvao-43Kf>7n_Hgw8 zd%1e{@X$?yz;QV1f|&6r^d6J>VpP^)aF(EfZH4nr!+9II>gR0)SuE)Qp^0lM%Lw%0 z2%l@H$`LaszaWszMh^}N_7sKAFbvyRJi z`}=A3ga^lGdkWW}+!WJSX~UYBL#)gp^N(=|-u?jQRQdMJmHpY1_|>tefM=&M4zvg5 z(+qLVaHkR{OSc<_-)D+g{iez}cC9u77^5W&RRv>~D^lS+ISb~YXf>U$xg`zaqcJ4- zcV8HrVmT!dfW~ekUM=}k%}e0di?E}35CFJWHpji8^`U#C>!Yb78)(~hJa0Rlvz^|y4P{5?bGG@c zX})M2DZp0>_C#fB;_wg~M-H=C#gQ_EloU(#Yh@a&YWFSlpB_FwERn;=4%p}|08R>x zKqZkuiCk0?6~%i}D7Ywf|D=ln<}9ot9smHw-O-vNlVYkZHJ0j6`8LRuxY3ri^<+&w z@ueGA8yG3;AjRqtCEJXx#;idBEHDvcd4{yMP?F0dN$BJhM>jD!Oc?Z534AjJ0t zA%o(RG`IOM@JgZ(ACS~R#DhCbFNS0MNXvf5qUh@lPJNZs8V|+sAnT;1cR4Jk#^j6Js+HePR0i1xYhIH95`AO4Q8U zC5bewT5&kdcz7`uj*84u1ThGl3?haJ_1&j`!?+{Njh6#pK#BcQY82suHXz zbCq@F9GwcRZ54Z}tR?5_)NYw-2;<2NLpV%;02!xZ5w?_a-UwkkiRv)83;p8n{svbL_Q z>7DGt)$H58{M*6Y+rjO(L)k0uKxa|y?1Noj5(6x>vKU}GU@>4x%w~A!Dt9qWAir%= zB5&<>&vj?QzU9S$Z&d32NjF!}--ES99e_JT+LzJ%bEtSMITe9-Rxe-Rb z0xb95s)1okX%c4D%)G};lv5>)oCJyCQ{fcIcfa^sM$VErYp{NZge3~{wP+lM56K^I z(fFt$*CkC5MPe(WMBYHFFj*E`Ne#);Wf314qsAah3iLQ2Frlsa0QVh|^E&3CE#rdv{#_XARNiTBi~d$-1OxrU+&NK&&KKBw;z#*t;w;E&1rj@-`oeC9X)Fzq(`ab8#|WPbZ7e3rVH}dZHJ+^ zFX2!2efWNwPG8!riBpei?TJh4HE}frl!$U`& z>`00`){b-_b8<5rS6AQYQCn9=%-QGS`v20^BvgqLiGxW;Qk!i(mbU-4WwRxF?Cch~ z)wR`-HBay8E<)NOj}z$~uo~>~20r%3EpY0ao4xUIoE`BbCbNwLX+C`^TQ{;}8ijcG ziH4}N$2A43BX8~6v3AABpfhhekTV@f`cj7b%Nw3;)3LniRL*oNLq0GKJ*9}cgHJRn z{dMwF+Y18MFLZ>xIeGB2BlnN|vUlUsH@1QJ#6##gnd-Pdoa=clXFnUCdUWtm+M4lw zMdq3=#xH*BXikuYo|C_Ef8oybWv={g-;;Wz$1HoeCk`r5bSAhx#wPYr}|Pp5R3%*2b(?7SFktTn^~VpO>g)}uUrp&;0-&n4GY^QeokMbQIE%$!D{&U#x%I71%iyNCg+m6=~DmQ1m^W~H`&85#n_nFQ@XICox`CPW+#1plu;UrMs zdJ3pxtxxJw*3?L@tuM#gd$1uc`ZR2z9nkECCvXxv^~>V$j+yoL~Z-FrQ?q2d)+cg4m=|; Hl-K(|9EFKR literal 0 HcmV?d00001 diff --git a/scanner/__pycache__/main.cpython-313.pyc b/scanner/__pycache__/main.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..46735deab6d402eca8db754cd99244c5bab2ab46 GIT binary patch literal 871 zcmaJ<-D}fO6hAk){jgc8n;X+PMi`sWhY5mvXqm#?*u-M33EhMRLe|DM&?ey~MYg97 z6n&S0kLru~-*gV`yy%m@_%`f6;JH~NY!4o|zjJ=)>zw^UFQ;EGr(k%8npBjz|I1g@;oe$3N>hwCMl$T3tlM@KDpIx zdk(XA-L@O;8Fkx3Rv%@@DupQH8Gc1Lgq_$aKu@Om=K+|E#d&-5}m)1Fk5boG2pu%r{-BXaFjyLunThV$u6HZg z<5U=I0-Gs#!Yzazd{?Ii>cVGrp?~|yk*fDLPH1jGr;q6L+jM{NeqWetC(8IhnLScw zkCnOJ`mg-_yE`9??~BLzrQYTVRbG`3$}b)XqjFIBLMMmV8F<#R#D$hsNHKh>z-irQ z&e_L{Aw+^_cO1+51neq0*m&$+>G+Lq+quWCAr&tnyoqos6GDc0AXk3C;-7SeOr1^u V9ecfhEMNadvoAMJWuP;0pT8e{#tZ-e literal 0 HcmV?d00001 diff --git a/scanner/core.py b/scanner/core.py new file mode 100644 index 0000000..f48deb8 --- /dev/null +++ b/scanner/core.py @@ -0,0 +1,146 @@ +# Responsibilities: +# - Reads target file, stores code lines +# - Manages vulnerability list +# - Runs all rule checks (auto-discovers rules in scanner/rules) +# - Provides add_vulnerability callback +# - Prints a grouped, colourised report + +import os +import importlib +import pkgutil +import scanner.rules as rules_pkg + + +# -------- Rule auto-discovery -------- +def _load_rule_modules(): + modules = [] + for _, modname, _ in pkgutil.iter_modules(rules_pkg.__path__): + if modname.startswith("_"): + continue # skip __init__, _template, etc. + mod = importlib.import_module(f"{rules_pkg.__name__}.{modname}") + if hasattr(mod, "check"): + modules.append(mod) + + # Stable order: by CATEGORY "A01: ..." if provided, else by module name + def key(m): + cat = getattr(m, "CATEGORY", "") + head = cat.split(":", 1)[0].strip() if cat else "" + return (0, int(head[1:])) if head.startswith("A") and head[1:].isdigit() else (1, m.__name__) + + return sorted(modules, key=key) + + +RULE_MODULES = _load_rule_modules() + + +# -------- Scanner -------- +class VulnerabilityScanner: + def __init__(self, file_path): + self.file_path = file_path + self.code_lines = [] + self.vulnerabilities = [] + + def add_vulnerability(self, category, description, line, severity, confidence): + self.vulnerabilities.append( + { + "category": category, + "description": description, + "line": line, + "severity": severity, + "confidence": confidence, + } + ) + + def parse_file(self): + if not os.path.exists(self.file_path): + print(f"File {self.file_path} does not exist.") + return False + with open(self.file_path, "r", encoding="utf-8") as f: + self.code_lines = f.readlines() + return True + + def run_checks(self): + for rule in RULE_MODULES: + # each rule exposes: check(code_lines, add_vulnerability) + rule.check(self.code_lines, self.add_vulnerability) + + def run(self): + if not self.parse_file(): + return + self.run_checks() + + def report(self): + # ---- colour helpers ---- + def supports_truecolor() -> bool: + return os.environ.get("COLORTERM", "").lower() in ("truecolor", "24bit") + + def rgb(r, g, b) -> str: + return f"\033[38;2;{r};{g};{b}m" + + ANSI = { + "reset": "\033[0m", + "bold": "\033[1m", + "cyan": "\033[96m", + "magenta": "\033[95m", + "yellow": "\033[93m", + "red": "\033[91m", + "green": "\033[92m", + "blue": "\033[94m", + } + + TRUECOLOR = supports_truecolor() + + # Severity colours (true-color -> fallback) + CRIT = (rgb(220, 20, 60) if TRUECOLOR else ANSI["red"] + ANSI["bold"]) # crimson + HIGH = (rgb(255, 0, 0) if TRUECOLOR else ANSI["red"]) # red + MED = (rgb(255, 165, 0) if TRUECOLOR else ANSI["yellow"]) # orange-ish + LOW = (rgb(0, 200, 0) if TRUECOLOR else ANSI["green"]) # green + + RESET = ANSI["reset"] + BOLD = ANSI["bold"] + HDR = (rgb(180, 130, 255) if TRUECOLOR else ANSI["magenta"]) # section header + TITLE = (rgb(120, 220, 200) if TRUECOLOR else ANSI["cyan"]) # title + SUM = (rgb(255, 215, 0) if TRUECOLOR else ANSI["yellow"]) # summary label + + sev_color = {"CRITICAL": CRIT, "HIGH": HIGH, "MEDIUM": MED, "LOW": LOW} + + print(f"\n{BOLD}{TITLE}Scan Results for {self.file_path}:{RESET}") + + if not self.vulnerabilities: + ok = rgb(0, 200, 0) if TRUECOLOR else ANSI["green"] + print(f"{ok}✅ No vulnerabilities found.{RESET}") + return + + # Group by category + groups = {} + for v in self.vulnerabilities: + groups.setdefault(v["category"], []).append(v) + + def cat_key(cat: str): + head = cat.split(":", 1)[0].strip() + return (0, int(head[1:])) if head.startswith("A") and head[1:].isdigit() else (1, cat.lower()) + + for cat in sorted(groups.keys(), key=cat_key): + items = sorted(groups[cat], key=lambda x: x["line"]) + # tally + sev_counts = {"CRITICAL": 0, "HIGH": 0, "MEDIUM": 0, "LOW": 0} + for v in items: + sev_counts[v["severity"]] = sev_counts.get(v["severity"], 0) + 1 + + total = len(items) + print(f"\n{BOLD}{HDR}=== {cat} ({total} finding{'s' if total != 1 else ''}) ==={RESET}") + + chips = [] + for k in ["CRITICAL", "HIGH", "MEDIUM", "LOW"]: + n = sev_counts.get(k, 0) + if n: + chips.append(f"{sev_color[k]}{k.title()}{RESET}: {n}") + if chips: + print(f"{SUM}Summary:{RESET} " + ", ".join(chips)) + + for v in items: + sc = sev_color.get(v["severity"], ANSI["blue"]) + print(f"\n {BOLD}• Line {v['line']} |{RESET} " + f"Severity {sc}{v['severity']}{RESET} | " + f"Confidence {v['confidence']}") + print(f" → {v['description']}") diff --git a/scanner/main.py b/scanner/main.py new file mode 100644 index 0000000..8f78b47 --- /dev/null +++ b/scanner/main.py @@ -0,0 +1,21 @@ +# Entry point for the OWASP PR Scanner CLI tool. +# This script parses the command-line arguments (i.e., the file path to scan), +# initializes the VulnerabilityScanner with the specified file, runs all rule checks, +# and prints a formatted vulnerability report to the console. + + +import argparse +from scanner.core import VulnerabilityScanner + + +def main(): + parser = argparse.ArgumentParser(description="OWASP PR Vulnerability Scanner") + parser.add_argument("path", help="Path to Python file to scan") + args = parser.parse_args() + + scanner = VulnerabilityScanner(args.path) + scanner.run() + scanner.report() + +if __name__ == "__main__": + main() diff --git a/scanner/rules/__init__.py b/scanner/rules/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/scanner/rules/__pycache__/__init__.cpython-313.pyc b/scanner/rules/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..276807cad9d7c679b942d79020047381b6b658fb GIT binary patch literal 168 zcmey&%ge<81l7|fWq|0%AOZ#$p^VQgK*m&tbOudEzm*I{OhDdekkl=AXRDad;?$zz z7{`>{%)A(v{N&Qy)Vz}7828K)kJ6-={PM)&0^Q=|#Js%Jq8J!mRGO1o91|a(nU`4- kAFo$Xd5gm)H$Md^YFESxG#6xVF^KVznURsPh#ANN06=dm%K!iX literal 0 HcmV?d00001 diff --git a/scanner/rules/__pycache__/auth_failures.cpython-313.pyc b/scanner/rules/__pycache__/auth_failures.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..07c117af2dda3a62c50f777aeabf0e9ee434184a GIT binary patch literal 1602 zcmcIkPi)&%7=O;cHgOWA?HC9Fvbt`IC#5ZnFtuYFVNH{0O{wZdnyB0A)qZU}TsypH zuhgm#Cj?vuheC%xzI@9+C( zyPZlU5YW3fKdFBjL+Cyix)ka#?LlB}Aq6SI6-1E|dKyt-O_0M6xWla0_2W`MoG_gB zIB0L71%WF$)lj~}522D!hFmUqG)^oWM$bnMJbUk9$XN^LJ3iTt2}^({fGwniYvK96 z-iQM}-%+K}*-$N{L~>ArVPv4awNvPuI#9$ zv0=KDFz@`I4LM3o>=0cF5=t9}Q-{nFd8=U>n&C)1rKHt~_6D1j9`HBVkCZE=vLCA! zrz*9o7pLS+moSHoddVAv8mpTZrm@M0H&U?}(IBUEbw-f_Q@0r1V0gtO`lRHY$-0ct ztcBN!d`V?zF2Gqhtqw0{vAzyHQ`>grKp}f4%b4n#twXgsS;emDNIaOwg2Evh^Z}6L z*#>58!>0NXc+bCZ_QkEP{cnJ~Err6@=I1g~`2G%UjW0j%rGH(b+#*cHPxoK0Ua{77KrU%dq6&s?5VYtRo<3%qr8 z%GMy6Y9Y9d{4`(oh6SpHvMR@G&%t2JOfL??%qK^ z4Lx)F_2y9a{l52vpT*S8i4P`rpD5mOn&Ql!IP+uj*;b~|8m#W<_paZ){++qAw3|^| znSl=rA4OZ4`2f literal 0 HcmV?d00001 diff --git a/scanner/rules/__pycache__/broken_access_control.cpython-313.pyc b/scanner/rules/__pycache__/broken_access_control.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..aa1f71182c171a5624e82377e5e392982b5142a3 GIT binary patch literal 4148 zcmcgueM}qY8Gp}T*gn2(D44GcNeG67qy(DbKgwX;S{9Bihve_Pn+a(8qs*p`P-cG6z5tt6rBBx&>+ zZ3vs@D7)ziabkS&On-Y+oQnuS%%4j{FwZTWU66|}Sd8-0me)L!6l4=}c!^9!TAatR zh$KbCqAvSlO@I3dM_{2d&}Bb?#e~cUq>e!QV)Yxaa{O#S>LkS6x6UmJLQz(dcnJu{ z2yq%FXEZ7pe#B@7M(}8`hXQtmE zb7yW3-}b`;dS??th)662+U=pOcr`+{weW28>j|o-3n)N>U~DX?!@{5n-@d*Lfc-$3Xf67+tpVDKA! z+5P;&gW`0G3|`nP(!gD0w17P9R#c_^P*F#9Qph4z*Db>1CtP>v33{MtPB{brx3mp~In z5f_5iL+3aMcC{Yp7U0h9x9?0AGIWx1Lw8Qs=k-B$HoL-thQ|;}fUofnrfn+yMLqR= z*K!0;1oxU3#9Ug4x$GOnOc!D<-5Rrd93dKF-^Od_rq_-Cqu2SGlz(&od|#=Fdm!)M z9I{u8hbYwvSjo@tFgzQOS+pBMxSIQ=4nukcY8t-fhj;T=s(yh~QYt{tqpNxbxgj`` zggJCXJ4@Z7#_bxVcF;Q%9C5$q@lFJtV`H94=lJ)6UYBB!cov7kz@h0nr0Aj%ftR2{ zh+&k80Y|_u@mLBtxmZL942q#dj2C1na55qfCuRcT0xQK2NFi1bcpOkyoPgstFoVT; zUI?JbHKVVPkiuu`kuwcV|zg@kFxm#K)swZjhwC)w+km#N{U>oeI_e|4Az*eFkZ( za{4ERgM+RCkJmZj@j^~9Ilb%}Y|OdC71D{xZ4r|r1DC=?N?i=G+4IrpopTRc4rN-7 zq*{*Lp)xIwWQ*gY(JwJMNEEc#P`r7lr+0hd__iqQ!!dbmuUMO~G846}Tvm8CI5Azzer&_k2W(%tZOT@mPcdt_2qJ z4#r-gXj$Q+qK)!`Qa&{1^qmTNJ^l%oS{ubM=o-p_KPt&M5?9QU%wkz05h(@|pTH^l zJV}Fj#XNFy+~ai(IDIZfKL=TeD<)omUVw0UrGj+U)|RStO}*xI`Fxv3?PZENByxN( z*Ih~#%W=W;i6}G;I}?dUX_+Db!iT7-sV~SH%RE zyl^lK30A0>VwCvvpnslMv=QzCY2m3Pbl?LjS01NHt8} zA5Aw*rt2n?x|($ZGMO(qvu64yp_Pue#fRpmY(@R$A1v!0(e)XcNzu$rYld$78{PJ} zzF~PZTUoX2%vM`g*wwwi+I{`(FAt`x?H{y#c;Nj5cg<_(?)RqpPiMNlsctWDuAE*q ztWa4?)9SH|Wnap&FKw}BEIlbp&uw4E(wDOIWv$H_>)w=g@6EIC#4>GtskXj!+jk#Y zkFJ*>i}h=aq`STAr3lQtd^z!R>AK@*Z>B5S{`ks!$8H_FV_T!{xl(-{JTi27djjZi?&~d-xll^PW$-1d*UHx_E4Slwb$<{SLr42Q;>*c7X_ByrdTBRQ6 zQwE#TvX!dKL(9(f0SeKj`OlUy16Gq?@QDB zK5yz;edYG>^5AEsP1(x*+3K;ii3hLWfBlod+IX^ZI$KHVR+6o>W-Dv|O&iL}zbHYq zolon&s`u2Jx#Z(y1Yr>WX5+U|aJf9XNt1^^D( z&A1sBr6eTA;t}X>@~_9_l7s8WM6Mi|90HhJ@bGRj5wzYy_+_#uzf#3`G$wLz)*Qz@ z@Jjyu(lwaYwG>5d*brrY3`HEhGMd$KS)JiW?jO20^x7It5>>4mQML7&?TRhg*mHa4 z!`S<=bk*Rc@~nyesqM$Mq}BdzHvvl8)OX477&tqwbR?TjrK`s-Rb=ZMeq;NEEx9L{ mWJ4KNNU=h?UQD7|&2 literal 0 HcmV?d00001 diff --git a/scanner/rules/__pycache__/insecure_design.cpython-313.pyc b/scanner/rules/__pycache__/insecure_design.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3e2c4be831ec8dfe6c215c6261ee4ea6bf8c7e0f GIT binary patch literal 1426 zcmbVLOKcle6ur+g<4@c;PTVR{i=}BQArHu=QXPn_kYLqOJ`kt!#G)bt(R?1xP|m2!gr%zNcw>8<3JDgylJC27zvq73 z9UUD3Mw83mZGXuD{Na806dDNK$4oc?8D#W25Ky2L@Lf*dX^;cV!k=nC;WfhOFZG^;CUc?1xcY}uZ?0Wt!Q=IuoW%yf}*8Ni(&-p2QQzLv znvP1Tq-d=R%pKPpo2aDOkFj?!A*O+u@nuCbOsZ;CoHC>Zg}#{bqEpzao4Q&zZA)6{ z<+F*oNn3CCRV;2=(fSow{mNvS zf-Su3ka>4X-^Til%v{yh)heCO3@ziVHod^ulVjM2qgeL-?C0voySI1mh98YxJQ}-r zfA(+cjpW0mh+1d!m$Nx>WpI{ap;xLkea)_WSEW`(ABEUQA=XrK1CyM1>`l!Lt}PZ; z%4;t7#`>EQ??$l2`Xg!`yFzamE~i>eH+(I>v9VYx%97xY=(d5ks-}ghJFXhW)=s0! zdRH~G%Ki>6GF{#?67L47w_K6YYb4@T6M8)pwhI{b%q7O2?8EPg=kC7sYa(?!bPIJ7 zlb_}8^~2Sp_(CT>{z?9h(1}lV z;^!VmgQKy(B5+pfvaG<61_*XTx?OY3Dt1R!mWu18#jAOFkxVdq55zqnQ=Z%d7oMxx z23y=bNiv-`9cnOk!XbpZVL&4%K@dV8t$wh2G`8@=#DnCI$u_^<;e&lu`U~>)&3iZ7 q{HsIN^26z$(?6x#{Pm$Kd#HV1`=-|B3nvlbOkh9UjYUzchvFa2ibeDQ literal 0 HcmV?d00001 diff --git a/scanner/rules/__pycache__/integrity_failures.cpython-313.pyc b/scanner/rules/__pycache__/integrity_failures.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..74b9fa5356045fadbde3cd74db5b69ae22ae236b GIT binary patch literal 1476 zcmaJ=&2Jk;6rZ)%j^nkB9S2CG5Jn9Zc0@ue6hKY@ndZYuaRgpg3UnoFvO9@aUGG{m zy9rq?J(gPws+t3bN?d}ta6vK$ME-^!ro@}dQuq;tbtaJqEL?q{gK#HAX^q5e|+ z{`gscVEC7Y{GTq)n0^sQ`F8*$0KqQEXEuFd#*5%RWagOq3TrtpU%dW)0m}A{vyTXd z$kO35au8g%9K1^m=K!uE!*mIz-Zh0{XzxHBS-Y6nE`|Dmh3bX|Iirgl!?p_W8K1u| zme$uwUZP=WcTC(}oSc^&@@USR`_y)@u0_!fhB~I0FyHXHTyW1ou!ku?f0UzAE1Uz6Yd*M4Gt16N)%u|;*Vm>tz&{5>_@0WjD{bl4k8ZaC_kla%Ei@{ zD(|maS@EM3Bcjz9F0R=+JA`3j>SvIySNB}gVm#Z1$*$TL3}56Q{aBZ@fpyAv6>{-E zyID}#1PE1&dfl+pWm{uCSPoU!45#F7tM)#kjXA0zi&3h9Ca#I8Y6Rn~?)1kw-#G9i zS`BM=$T+LP|6&?rv%3ra%uXGC`A0VQ&B#5WoqhFD;X&cMrF*fqls%KCPo?QYdM3@B zN;6Lr<*@WbdgExSIq@W43?NI*t>(r_dhLIZe|d-h+!DZezQZmAN^@d?$l3=m!b^Qp)%iPYgmmgcI+d8}E56Kng^8KgN>~$U%1fdfHLj12x_R-|G UlP6QxTbUboQyuXwA=SnI2YqsLlmGw# literal 0 HcmV?d00001 diff --git a/scanner/rules/__pycache__/logging_failures.cpython-313.pyc b/scanner/rules/__pycache__/logging_failures.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3f41262f15de1e1b194b6cddbb63c8c9e569c628 GIT binary patch literal 2132 zcmaJ?TW=Fb6rS}xv2*byTq+tC8woo%5``2nQ6LE+X-h6-;}Q`QYw=E;CC;ujv&O_P zMCwZr4-i!mLFxlf6;zd)A|X|3ANUEjjIbH?AyS|6hMG#HZ$0BnQlMqzojE&a=6v7z z&g^)%p`ji@TN-`7IO;&?cm7gqrmC@f0E`VJAxZcW5hR(2Aelcw#FP+1mL0xmB4lHZ zluT(xCknHbEf^_|6KpWMoKj;;c!k-(;G@;hm#`ME#pe+K8=2wR&B5%veFM!2e44ii z>Z{sbG%Acm5K6yNB{YEDz)(_>p9htPx8i>NQ17lC2Z=NOsBI zSLG|B8T)^7bG#o9Az0>uv95fs7w{iZ}$d~r&=&9)l75o=};5mzs6NxB+Lf#1` z1hTdiQ8gnN-#gh5j-TLOcy>FW8xR7pegjUt7mY#Qx_idNF>j#_tBA&(wRoq^3qnYk zc#1-1X6ou@LWY@>dWP~~qPWT?hr`hb#e_x&m1R|nUex31Wvm%AI;t9D=^If!BhyqT zjmw&bNwhYSbQ05OQcomQEfHIg)nuAr+Lc;ib^Qsf;oB)Pzz)S1aeS$(KdHy%BpvA5 z^YBwdxzulW&??ILhn_{ZgeT_j&OD3$O`&AyYqw9xb`Ex*4T}&s!;>Wil{HZl%B#wtO|BgPGVwk_8OK+SY+nQlUK9e3vw#eMSv&{o;V3h zRqez(lm5!2e}@;33E~d_)UqQ}{HTa&3h=5X%4uT}>ZitKgA-eTVxI$eya4djAnCXP zl~OvmNMO_$b%p9#9oC@qL^d$9!b&y8tgsmqR!g4oA ziJ1s&SeA`Ac#~LTmYce&g&eFtt}8f}R5eVQTUM0Vt#lI3kbFZ;LOq$FG7EPyM;R=I z6=v3M8_bqbHAT;mCPPl`VlAnsaoSn->7Yeh9S5t}YmO~F`*Jn3x zKDn~xej3TO_7+-B60 literal 0 HcmV?d00001 diff --git a/scanner/rules/__pycache__/security_misconfig.cpython-313.pyc b/scanner/rules/__pycache__/security_misconfig.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..759c9a06f9ff0ca5df4f1448a994f5a629565dee GIT binary patch literal 3273 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 literal 0 HcmV?d00001 diff --git a/scanner/rules/__pycache__/sensitive_data_exposure.cpython-313.pyc b/scanner/rules/__pycache__/sensitive_data_exposure.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e4cf0d64727356126eb0c4487f9f2b8094112f4a GIT binary patch literal 1604 zcmcIk%}*Ow5P!RCFNOu%U_uD_+5%}~(HL4vDs3n#F$hNysAX|eH5S(5eZ?EwyViSa z3`mubdP&s_s6ydV^;9Y3#4&#Ymr77qVBVopm0q|-HL8+Z=PfpkhKYR$i;7WtUx=`x?VH4>{7w#j1bg>5!VNr-m$J}8i)9~Y30A2~+ znk^tSFT*K87Y9XK97MYGxnN5V!G20fG*Xu%DDfjgCBP|8CjGPg{5cw_>jimEp7o#G z1XcGnux{2j4M3Z|h0w#MhIdl_NYMQ~=usdkMo)nmnHppeS?l9YFK+Z$NLY>oUk~r?Qv(_h)$o-^wHOZywV> zA*3J`Euv_6hDil;g!yRJ_#i5?K+&MI>=2X5o&+n!8G>CV8$~OJS6CCQX)gF(Cx>k& z^kvT413AJ#pka7x31DMFAB5RNNd)b>h>+7hC(gd<4L+P2h2@uALYsaSOCNwo}7A!k8;bck$Y`Ziy zWx0vcLdq!{w0MhV4BN&e)mTX>k7x3}lRT zXRU?{> zS6IC=J~XVUdLyJd0U_1NddSsx9$+J_yecFFM3>vjx?}F3z~v)YM~MMdnM~ zn@aru-2lUD0uzCl@X2b6Lu1%pvWR1^2KlY?)RGE)-YQLH^7Q1yz2tP%$C|MXEhmPHnctfa)uvWRhX8M4MO(he_ux8&lS9 zm}YvZl!t*Z7OcGGt}vwxcIToj1c}? z;SHy|z+SDPYNUJX-Sxq>`|Q$#Jzv+Kkt_3;gV zHQWWyi6XZJPddCq@%^E@m0h7kJAKlCin4cg-ne1T*nGuM!f^Au_Ikbp96)U!lt@in4H2L)p->mg)7GTz}--0dy|+UB`C8=$iH*;9PA1M z^*A}Cu*ISO@>1Q@FuD}z@#L@^NpYPA0#X8SH!mY(hU5VyG>qivCFstdqyio0M~)O? z|4SkKTp>S)Pz(tu|ES}uNE|FQ#VaZ~zjjVN^Dg*=EUie3rL9~+mWrh<<*lVmN!nV1 zWJwY!bD=rtgVfs>MDSOh`9ErNICF6>E#%Diu zAdYRARYA8*9df4TW77g)3|6G+<*IB3QHabsEhr7v3yNiy2v>TwQY@#Z_&~ZBWkdrUA^T zZdGu(W|)}JF|AT5Z@4uRD%~_{hOXV{~OPtttfDM9oy{hN&)DIy7lIL|rzVtb0?nZfm5GB)VpTqT2JeTf;;pw`ygh zccrt9P0H!(SbyK11c5<3!@4@HX=_HuTBmk1w~5W%lO3 zAOGRhcc*@e-FO_k(NfDTy?UTlpH9Z_M81mLec}Gh!Q?C3xnD*mz3G`dSHHZvJ9jXh z{50YvXYY^QQ(B|K_tTFS_GVlBb&o%`!+a)reB9$t{uK+2 zjl-v6O5DzRV^ceco%r3k*2s&y{F9O7&t3k)gVHyv4_Cid9~4^rwSIp5??XNZ{69}4 zUTni)NDSEi=jm|Vd&r*h;}fva$U@z!zzw}_Ps7pw{}FgI&>umDVcI-mqHT7X89yZa E1HddK@&Et; literal 0 HcmV?d00001 diff --git a/scanner/rules/__pycache__/ssrf.cpython-313.pyc b/scanner/rules/__pycache__/ssrf.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3ebfd83f12c5d5b6fa6c1f44fad989f6e64064af GIT binary patch literal 1537 zcmcIk&rcgi6rT0^SG<4=HiWneU4p1=BBLPcrdYHRq7bJAlBnNU_$0h-pPa(pu zbD7`^Z)v4IrIrZ(qK2tU)yecacDJeyg{cOg=IZ4w16$a|TV<>n8I?|`Y5mnp&H;!Q zPHPrcAE4FlMF{0Qco=qI)ejdb$PXle3KfKE;8T4^2}(fl_3^iVNC`=i*LM+6_Oa64 zO%2eNvk0w5{nJuAg}&91UOJReuFJs=D`DT?@cfq%QUX%%Ss%|*!czR7t1|CL;Y$%M zI|vC#ME}D3>;H}S8-9FJ!q-kEaPHRT4BzV>U#P}=P^ri(nHxCu^fky_P>J-xJk6hG z@C@2GpmA_6B!`tyADRv*yv##MkQ|XCxo(w3$j$Osog6LicH}zBL{}I$R?mJ0J(gab z%ihwm_2ODrxsshJu1$FD_da~44c*yp#TOo>dO$7J#kr{~c~Qn>1Cy+58d#J%T`ev; zWF3>udf|rSV%s$}OO$14F)!Ba4b3tQ&BcaTqu{1Hwo4q#0$Y)m#HvOqsIDXW4`gy8 zoym(VSh#+zkcly#Vwau6cCCU5ps)~iiCJYa>T1NLcTBg;0tA*T-aBHvW*98APMlhm z#ifOt%7Xk!aei)TsVFV5FvS|t%b6&P>5hSmmT6Mu5F-7+pr|4#Eo1PP>^cs3d^zDDj!X~-L^_LSlivf*=D`F z%&rr~r`~YjkXEQ72gIEV7yf}Bb4-*%+z~>N=p~#gEOG8^>W#~RcbMOs_c8DH-n`!M za1yALe*IqHW zb$^uz=0n|SxW?O560}4}y0=J)JcXq`9RL*~YFW7PBS4F0dE+Wui&_lH^<}D$4yST8 zKC%u_VWPJt`bM3;Hdg>x5~8TuP^1k|<)S=?SNSN{40C5FL%t>sVk2!Z(%Nuj8{i33 z;eWE(lZtUwSdya5`;_ZwQ5p&!R7yedeb2x?q{PdPQF#ArL9#FfNUe*!Yh{RAL_ z3=PN+%@bz)D4gz5IR5rW<;$NH<-0B0L0EraBUyJ$`Bp11^#GZ2#cg`7gPg!`e=?0c zI(<`}Bkb(0d-((z zLQbnmO9hDVet<2HB>h0gfxm7AD@0hhIbW|;3E$KMV}#a5jyN0kI zZk^m0a;H+%RG(V4ve~p8ZQ3;`Roc6D-3o5B9%$~m?t2B_&>cD~EkfL~k*`H{ZB#c- zhIYx@AiS}Hj8%M&RtmSorz;GP;E(LN?Zw}-6Pxi4)6Ks3L-9%R`>UPAD*;lu195au z9NqR0#PL0G{6#AFGL_ky-JIP%v!5F4Fn^v(Q`@0*YOAr?c&vZ-@IV^flScO?xx;n` z)((3pO3yQ6zlmcnQs=wrE8WcWGv!%zC$m%8nc7X??xs&aEZz!7t%fMT}C>7>^1AE9?t*( literal 0 HcmV?d00001 diff --git a/scanner/rules/_template.py b/scanner/rules/_template.py new file mode 100644 index 0000000..1d04ded --- /dev/null +++ b/scanner/rules/_template.py @@ -0,0 +1,11 @@ +# Template for adding new OWASP rule modules +def check(code_lines, add_vulnerability): + for i, line in enumerate(code_lines): + if "pattern" in line: # replace with real logic + add_vulnerability( + "Axx: Rule Name", + f"Description: {line.strip()}", + i + 1, + "HIGH", + "MEDIUM" + ) diff --git a/scanner/rules/auth_failures.py b/scanner/rules/auth_failures.py new file mode 100644 index 0000000..faacceb --- /dev/null +++ b/scanner/rules/auth_failures.py @@ -0,0 +1,45 @@ +# A07:2021 – Identification and Authentication Failures +# Detects default credentials (`admin`, `password`) +# Flags login routes without auth checks +# Warns about disabled TLS verification (`verify=False`) +import re + +def check(code_lines, add_vulnerability): + for i, line in enumerate(code_lines): + # Flask/Django style routes that should require auth + if re.search(r"@app\.route\([\"'](/login|/auth|/signin)[\"']", line): + add_vulnerability( + "A07: Identification and Authentication Failures", + f"Authentication-related route without explicit auth checks: {line.strip()}", + i + 1, + "HIGH", + "MEDIUM" + ) + + # Python requests with TLS verify disabled + if "requests." in line and "verify=False" in line: + add_vulnerability( + "A07: Identification and Authentication Failures", + f"Insecure TLS verification disabled: {line.strip()}", + i + 1, + "HIGH", + "HIGH" + ) + + # Hardcoded default creds + if re.search(r"(user(name)?\s*=\s*['\"](admin|root)['\"])", line, re.IGNORECASE): + add_vulnerability( + "A07: Identification and Authentication Failures", + f"Hardcoded default username detected: {line.strip()}", + i + 1, + "HIGH", + "HIGH" + ) + if re.search(r"(password\s*=\s*['\"](admin|1234|password)['\"])", line, re.IGNORECASE): + add_vulnerability( + "A07: Identification and Authentication Failures", + f"Hardcoded default password detected: {line.strip()}", + i + 1, + "HIGH", + "HIGH" + ) diff --git a/scanner/rules/broken_access_control.py b/scanner/rules/broken_access_control.py new file mode 100644 index 0000000..2038991 --- /dev/null +++ b/scanner/rules/broken_access_control.py @@ -0,0 +1,94 @@ +# A01:2021 – Broken Access Control +# +# It looks for common patterns that suggest missing or weak authorization checks: +# 1) Flask routes without an auth/role decorator (e.g., @login_required, @jwt_required). +# 2) Django REST Framework endpoints that explicitly allow unauthenticated access +# (e.g., permission_classes = [AllowAny]). +# 3) Express.js routes that attach a handler directly with no middleware +# (e.g., app.get('/admin', (req, res) => ...)) which often implies no auth check. +# +# Function: +# - `check(code_lines, add_vulnerability)`: Scans lines and reports findings with context. + +import re + +AUTH_DECORATOR_RE = re.compile( + r'@(login_required|jwt_required|roles_required|requires_auth|auth_required|permission_required)', + re.IGNORECASE, +) +FLASK_ROUTE_RE = re.compile(r'@(?:\w+\.)?route\s*\(', re.IGNORECASE) +DEF_RE = re.compile(r'^\s*def\s+\w+\s*\(', re.IGNORECASE) + +DRF_ALLOWANY_RE = re.compile(r'permission_classes\s*=\s*\[\s*AllowAny\s*\]') +DRF_IMPORT_ALLOWANY_RE = re.compile(r'from\s+rest_framework\.permissions\s+import\s+.*AllowAny', re.IGNORECASE) + +# app.get('/path', handler) or router.post("/path", handler) +# If there is a direct callback right after the path, there is probably no middleware. +EXPRESS_ROUTE_RE = re.compile( + r'\b(?:app|router)\.(get|post|put|patch|delete|options|head)\s*\(\s*[\'"][^\'"]+[\'"]\s*,\s*(?:function|\()', + re.IGNORECASE, +) + +def check(code_lines, add_vulnerability): + # Track whether DRF AllowAny is imported to increase confidence + drf_allowany_seen = any(DRF_IMPORT_ALLOWANY_RE.search(line) for line in code_lines) + + # -------- Flask route without auth decorator ---------- + i = 0 + while i < len(code_lines): + line = code_lines[i] + if FLASK_ROUTE_RE.search(line): + # Collect decorators until we hit the function def line + decorators = [] + j = i + while j + 1 < len(code_lines) and not DEF_RE.search(code_lines[j + 1]): + j += 1 + if code_lines[j].lstrip().startswith('@'): + decorators.append(code_lines[j].strip()) + + # If next line is a function def, evaluate decorators + if j + 1 < len(code_lines) and DEF_RE.search(code_lines[j + 1]): + has_auth = any(AUTH_DECORATOR_RE.search(d) for d in decorators) + # Heuristic: mark as High likelihood if path looks sensitive + path_hint = "" + m = re.search(r'route\s*\(\s*[\'"]([^\'"]+)', line, re.IGNORECASE) + if m: + path_hint = m.group(1) + + if not has_auth: + sev_like = "HIGH" if re.search(r'/?(admin|settings|manage|delete|update|user|account)', path_hint, re.IGNORECASE) else "MEDIUM" + add_vulnerability( + "A02: Broken Access Control", + f"Flask route appears without an auth decorator: {line.strip()}", + i + 1, + sev_like, + "HIGH", + ) + i = j + 1 + else: + i += 1 + else: + i += 1 + + # -------- DRF AllowAny on views / viewsets ---------- + for idx, line in enumerate(code_lines): + if DRF_ALLOWANY_RE.search(line): + like = "HIGH" if drf_allowany_seen else "MEDIUM" + add_vulnerability( + "A02: Broken Access Control", + f"DRF endpoint allows unauthenticated access with AllowAny: {line.strip()}", + idx + 1, + like, + "HIGH", + ) + + # -------- Express routes without middleware ---------- + for idx, line in enumerate(code_lines): + if EXPRESS_ROUTE_RE.search(line): + add_vulnerability( + "A02: Broken Access Control", + f"Express route handler attached without visible auth middleware: {line.strip()}", + idx + 1, + "MEDIUM", + "HIGH", + ) diff --git a/scanner/rules/insecure_design.py b/scanner/rules/insecure_design.py new file mode 100644 index 0000000..5548256 --- /dev/null +++ b/scanner/rules/insecure_design.py @@ -0,0 +1,25 @@ +# A04:2021 – Insecure Design +# Flags insecure “TODO” markers, temporary overrides, or auth bypass notes + + +import re + +PATTERNS = [ + re.compile(r'\btodo\b.*\b(insecure|security|auth|bypass)\b', re.IGNORECASE), + re.compile(r'\btemporary\b.*\boverride\b', re.IGNORECASE), + re.compile(r'\bdisable(d)?\s+(auth(entication)?|authori[sz]ation)\b', re.IGNORECASE), + re.compile(r'\bbypass(ing)?\s+(auth|security)\b', re.IGNORECASE), +] + +def check(code_lines, add_vulnerability): + for i, line in enumerate(code_lines): + stripped = line.strip() + # do NOT skip comments — we want to catch insecure design notes in comments too + if any(p.search(stripped) for p in PATTERNS): + add_vulnerability( + "A04: Insecure Design", + f"Potential insecure design marker: {stripped}", + i + 1, + "MEDIUM", + "LOW", + ) \ No newline at end of file diff --git a/scanner/rules/integrity_failures.py b/scanner/rules/integrity_failures.py new file mode 100644 index 0000000..b9a29c9 --- /dev/null +++ b/scanner/rules/integrity_failures.py @@ -0,0 +1,52 @@ +# A08:2021 – Software and Data Integrity Failure +# Flags: eval/exec, unsafe deserialization (pickle), unsafe YAML load, and shell=True + +import re + +UNSAFE_YAML_RE = re.compile(r'\byaml\.load\s*\(') # safe form is yaml.safe_load + +def check(code_lines, add_vulnerability): + for i, line in enumerate(code_lines): + stripped = line.strip() + if stripped.startswith("#"): + continue + + # Dangerous dynamic evaluation + if "eval(" in stripped or "exec(" in stripped: + add_vulnerability( + "A08: Software and Data Integrity Failures", + f"Use of dangerous dynamic evaluation: {stripped}", + i + 1, + "HIGH", + "HIGH", + ) + + # Unsafe deserialization (pickle) + if "pickle.load(" in stripped or "pickle.loads(" in stripped: + add_vulnerability( + "A08: Software and Data Integrity Failures", + f"Potential unsafe deserialization via pickle: {stripped}", + i + 1, + "HIGH", + "HIGH", + ) + + # Unsafe YAML load (must be yaml.safe_load) + if UNSAFE_YAML_RE.search(stripped) and "safe_load" not in stripped: + add_vulnerability( + "A08: Software and Data Integrity Failures", + f"Unsafe YAML load detected; use yaml.safe_load(): {stripped}", + i + 1, + "HIGH", + "MEDIUM", + ) + + # shell=True in subprocess calls + if "subprocess." in stripped and "shell=True" in stripped: + add_vulnerability( + "A08: Software and Data Integrity Failures", + f"subprocess call with shell=True detected: {stripped}", + i + 1, + "HIGH", + "MEDIUM", + ) diff --git a/scanner/rules/logging_failures.py b/scanner/rules/logging_failures.py new file mode 100644 index 0000000..dbe62d8 --- /dev/null +++ b/scanner/rules/logging_failures.py @@ -0,0 +1,50 @@ +# A09:2021 – Security Logging and Monitoring Failures +# Flags: printing secrets, bare except with print, and print in login/auth paths + +import re + +SECRET_WORDS = ("password", "passwd", "secret", "api_key", "apikey", "token") + +def check(code_lines, add_vulnerability): + for i, line in enumerate(code_lines): + stripped = line.strip() + low = stripped.lower() + + if stripped.startswith("#"): + continue + + # Printing potential secrets + if "print(" in low and any(w in low for w in SECRET_WORDS): + add_vulnerability( + "A09: Security Logging and Monitoring Failures", + f"Possible secret printed to stdout: {stripped}", + i + 1, + "MEDIUM", + "MEDIUM", + ) + + # Bare except printing errors (poor monitoring/alerting) + if low.startswith("except:") or re.match(r"^except\s+[A-Za-z_][A-Za-z0-9_]*\s+as\s+\w+\s*:\s*$", low): + # Peek next line(s) for print + nxt = code_lines[i + 1].strip().lower() if i + 1 < len(code_lines) else "" + if "print(" in nxt: + add_vulnerability( + "A09: Security Logging and Monitoring Failures", + f"Exception handled with print() instead of proper logging/alerting near: {stripped}", + i + 1, + "MEDIUM", + "LOW", + ) + + # Print statements in login/auth contexts (heuristic) + if ("@app.route('/login'" in low or "@app.route(\"/login\"" in low) and i + 3 < len(code_lines): + # scan a small window after the route for print usage + window = " ".join(code_lines[i : i + 5]).lower() + if "print(" in window: + add_vulnerability( + "A09: Security Logging and Monitoring Failures", + "Print used in authentication flow; prefer structured, secure logging.", + i + 1, + "MEDIUM", + "LOW", + ) diff --git a/scanner/rules/security_misconfig.py b/scanner/rules/security_misconfig.py new file mode 100644 index 0000000..17885d2 --- /dev/null +++ b/scanner/rules/security_misconfig.py @@ -0,0 +1,86 @@ +# A05:2021 – Security Misconfiguration +# +# It flags risky configuration patterns commonly seen in Python, JS, and YAML: +# 1) Debug modes enabled (Django DEBUG=True, Flask app.run(debug=True)). +# 2) Overly permissive hosts or CORS settings (ALLOWED_HOSTS=['*'], Access-Control-Allow-Origin: *). +# 3) Insecure cookie or transport flags (SECURE_... = False, SESSION_COOKIE_SECURE=False). +# 4) Hardcoded or default-like secrets in config contexts (SECRET_KEY='...', password='admin'). +# +# Function: +# - `check(code_lines, add_vulnerability)`: Scans lines and reports findings with context. + +import re + +DJANGO_DEBUG_RE = re.compile(r'\bDEBUG\s*=\s*True\b') +FLASK_DEBUG_RE = re.compile(r'\bapp\.run\s*\(\s*.*\bdebug\s*=\s*True\b', re.IGNORECASE) +DJANGO_ALLOWED_HOSTS_ANY_RE = re.compile(r'\bALLOWED_HOSTS\s*=\s*\[\s*[\'"]\*\s*[\'"]\s*\]', re.IGNORECASE) + +CORS_WILDCARD_RE = re.compile(r'(Access-Control-Allow-Origin\s*[:=]\s*[\'"]\*\s*[\'"])|("allowAllOrigins"\s*:\s*true)', re.IGNORECASE) +SECURE_FLAG_FALSE_RE = re.compile(r'\b(SECURE_[A-Z_]+|SESSION_COOKIE_SECURE|CSRF_COOKIE_SECURE)\s*=\s*False\b') +INSECURE_COOKIE_RE = re.compile(r'cookie\s*(secure|httpOnly)\s*[:=]\s*false', re.IGNORECASE) + +DEFAULTY_SECRET_RE = re.compile( + r'\b(SECRET_KEY|APP_SECRET|JWT_SECRET|API_KEY|TOKEN|PASSWORD)\s*[:=]\s*[\'"]([^\'"]+)[\'"]', re.IGNORECASE +) +OBVIOUS_DEFAULTS = {'admin', 'password', 'changeme', 'change_me', 'default', 'test', 'secret'} + +def check(code_lines, add_vulnerability): + for i, line in enumerate(code_lines): + # Debug modes + if DJANGO_DEBUG_RE.search(line): + add_vulnerability( + "A05: Security Misconfiguration", + f"Django DEBUG is enabled: {line.strip()}", + i + 1, + "HIGH", + "MEDIUM", + ) + if FLASK_DEBUG_RE.search(line): + add_vulnerability( + "A05: Security Misconfiguration", + f"Flask debug mode is enabled: {line.strip()}", + i + 1, + "HIGH", + "MEDIUM", + ) + + # Permissive hosts and CORS + if DJANGO_ALLOWED_HOSTS_ANY_RE.search(line): + add_vulnerability( + "A05: Security Misconfiguration", + f"ALLOWED_HOSTS permits all hosts: {line.strip()}", + i + 1, + "MEDIUM", + "MEDIUM", + ) + if CORS_WILDCARD_RE.search(line): + add_vulnerability( + "A05: Security Misconfiguration", + f"Wildcard CORS detected: {line.strip()}", + i + 1, + "MEDIUM", + "MEDIUM", + ) + + # Insecure cookie and transport flags + if SECURE_FLAG_FALSE_RE.search(line) or INSECURE_COOKIE_RE.search(line): + add_vulnerability( + "A05: Security Misconfiguration", + f"Insecure cookie or transport flag: {line.strip()}", + i + 1, + "MEDIUM", + "MEDIUM", + ) + + # Default-like or hardcoded secrets + m = DEFAULTY_SECRET_RE.search(line) + if m: + key, value = m.group(1), m.group(2) + like = "HIGH" if value.strip().lower() in OBVIOUS_DEFAULTS else "MEDIUM" + add_vulnerability( + "A05: Security Misconfiguration", + f"Hardcoded secret or credential in config context: {key} = '***'", + i + 1, + like, + "HIGH", + ) diff --git a/scanner/rules/sensitive_data_exposure.py b/scanner/rules/sensitive_data_exposure.py new file mode 100644 index 0000000..c914ea7 --- /dev/null +++ b/scanner/rules/sensitive_data_exposure.py @@ -0,0 +1,38 @@ +# A02:2021 – Cryptographic Failures +# Detects weak hashing algorithms (MD5, SHA1) +# Flags hardcoded secrets, API keys, and default passwords +# Warns about unsafe fallback values +import re + +def check(code_lines, add_vulnerability): + weak_hashes = ["md5", "sha1"] + sensitive_keywords = ["password", "passwd", "secret", "apikey", "api_key", "token"] + + for i, line in enumerate(code_lines): + stripped = line.strip() + + # Skip comments + if stripped.startswith("#"): + continue + + # Weak crypto usage + if any(h in stripped.lower() for h in weak_hashes): + add_vulnerability( + "A03: Sensitive Data Exposure", + f"Weak hashing algorithm detected: {stripped}", + i + 1, + "HIGH", + "HIGH" + ) + + # Hardcoded secrets (but ignore env lookups and hashes) + if any(kw in stripped.lower() for kw in sensitive_keywords) and "=" in stripped: + if "os.environ" in stripped or "hashlib.sha256" in stripped: + continue # safe usage, skip + add_vulnerability( + "A03: Sensitive Data Exposure", + f"Potential hardcoded sensitive data: {stripped}", + i + 1, + "HIGH", + "MEDIUM" + ) diff --git a/scanner/rules/sql_injection.py b/scanner/rules/sql_injection.py new file mode 100644 index 0000000..a220d9c --- /dev/null +++ b/scanner/rules/sql_injection.py @@ -0,0 +1,39 @@ +# A03:2021 – Injection* + +# Specifically, it searches for suspicious SQL query patterns in Python code, +# such as unparameterized queries or string concatenation in `execute()` calls. + +# Function: +# - `check(code_lines, add_vulnerability)`: Accepts lines of code and a callback to report findings. +# Uses regular expressions to detect potential SQLi and sends alerts via `add_vulnerability()`. + +import re + +def check(code_lines, add_vulnerability): + assigned_queries = {} + + for i, line in enumerate(code_lines): + if re.search(r"=\s*['\"]\s*(SELECT|INSERT|UPDATE|DELETE)", line, re.IGNORECASE) and '+' in line: + var_match = re.match(r"\s*(\w+)\s*=", line) + if var_match: + var_name = var_match.group(1) + assigned_queries[var_name] = i + 1 + + add_vulnerability( + "A01: Injection", + f"SQL query created via string concatenation: {line.strip()}", + i + 1, + "HIGH", + "MEDIUM" + ) + + # Detect execution of those suspicious queries + for var_name in assigned_queries: + if f"execute({var_name})" in line: + add_vulnerability( + "A01: Injection", + f"Suspicious query passed to execute(): {line.strip()}", + i + 1, + "HIGH", + "HIGH" + ) \ No newline at end of file diff --git a/scanner/rules/ssrf.py b/scanner/rules/ssrf.py new file mode 100644 index 0000000..c5cb654 --- /dev/null +++ b/scanner/rules/ssrf.py @@ -0,0 +1,39 @@ +# A10:2021 – Server-Side Request Forgery (SSRF) +# Heuristic data-flow: user input -> variable -> requests.*(var) + +import re + +REQUEST_CALL_RE = re.compile(r'\brequests\.(get|post|put|patch|delete|head)\s*\(') + +def check(code_lines, add_vulnerability): + input_vars = set() + + # Track variables that come from input() + for i, line in enumerate(code_lines): + stripped = line.strip() + if stripped.startswith("#"): + continue + + # var = input("...") + m = re.match(r'^\s*([A-Za-z_][A-Za-z0-9_]*)\s*=\s*input\s*\(', stripped) + if m: + input_vars.add(m.group(1)) + + # Flag when those variables are used in requests.*(var) + for i, line in enumerate(code_lines): + stripped = line.strip() + if stripped.startswith("#"): + continue + + if REQUEST_CALL_RE.search(stripped): + # naive arg capture + for var in input_vars: + if re.search(rf'\b{var}\b', stripped): + add_vulnerability( + "A10: Server-Side Request Forgery", + f"Potential SSRF: unvalidated user-controlled URL passed to requests.*(): {stripped}", + i + 1, + "HIGH", + "HIGH", + ) + break diff --git a/scanner/rules/vulnerable_components.py b/scanner/rules/vulnerable_components.py new file mode 100644 index 0000000..a7c0288 --- /dev/null +++ b/scanner/rules/vulnerable_components.py @@ -0,0 +1,33 @@ +# A06:2021 – Vulnerable and Outdated Components +# Placeholder rule: looks for requirements with outdated versions. + +import re + +# e.g., flask==2.0.1, Django==1.11.29, requests==2.25.1 +PIN_RE = re.compile(r'^\s*([A-Za-z0-9][A-Za-z0-9_\-]*)\s*==\s*([A-Za-z0-9\.\-\+]+)\s*$') + +SUSPECT_PACKAGES = {"flask", "django"} # expand as needed + +def check(code_lines, add_vulnerability): + for i, line in enumerate(code_lines): + stripped = line.strip() + + # Skip comments entirely + if stripped.startswith("#"): + continue + + m = PIN_RE.match(stripped) + if not m: + continue + + pkg = m.group(1).lower() + ver = m.group(2) + + if pkg in SUSPECT_PACKAGES: + add_vulnerability( + "A06: Vulnerable and Outdated Components", + f"Dependency pin detected (manual review required): {pkg}=={ver}", + i + 1, + "MEDIUM", + "LOW", + ) \ No newline at end of file diff --git a/scanner/tests/test_negative.py b/scanner/tests/test_negative.py new file mode 100644 index 0000000..06ac430 --- /dev/null +++ b/scanner/tests/test_negative.py @@ -0,0 +1,43 @@ +# This file should produce clean results + +import os +import sqlite3 +import hashlib +import requests +from flask import Flask + +# Assume a real auth decorator exists in the project. The scanner only checks for its presence. +def login_required(fn): + return fn + +# --- Secure Flask setup --- +app = Flask(__name__) +app.config["DEBUG"] = False +app.config["SECRET_KEY"] = os.environ.get("SECRET_KEY", "fallback_only_for_dev_builds") + +@app.route("/dashboard") +@login_required +def dashboard(): + return "secure dashboard" + +# --- Parameterised SQL query (safe) --- +username = input("Enter your username: ") +query = "SELECT * FROM users WHERE username = ?" + +conn = sqlite3.connect("example.db") +cursor = conn.cursor() +cursor.execute(query, (username,)) + +# --- Secure cryptography usage --- +hashed_password = hashlib.sha256(username.encode()).hexdigest() + +# --- Secure HTTP request (TLS verification enabled) --- +resp = requests.get("https://example.com", verify=True) +print(resp.status_code) + + +# --- Safe YAML load --- +data = yaml.safe_load("key: value") + +# --- Safe subprocess usage (no shell=True) --- +subprocess.run(["echo", "hello"], check=True) \ No newline at end of file diff --git a/scanner/tests/test_positive.py b/scanner/tests/test_positive.py new file mode 100644 index 0000000..7bb30b1 --- /dev/null +++ b/scanner/tests/test_positive.py @@ -0,0 +1,95 @@ +# Triggers: +# A01 Injection +# A02 Broken Access Control +# A03 Sensitive Data Exposure (Cryptographic Failures) +# A04 Insecure Design +# A05 Security Misconfiguration +# A06 Vulnerable and Outdated Components +# A07 Identification and Authentication Failures +# A08 Software and Data Integrity Failures +# A09 Security Logging and Monitoring Failures +# A10 Server-Side Request Forgery (SSRF) + +import sqlite3 +import hashlib +import requests +import yaml +import pickle +import subprocess +from flask import Flask, Response + +# ---------- A05: Security Misconfiguration ---------- +SECRET_KEY = "changeme" # hardcoded secret +ALLOWED_HOSTS = ['*'] # permissive hosts +SESSION_COOKIE_SECURE = False # insecure cookie flag +CSRF_COOKIE_SECURE = False # insecure CSRF flag + +# ---------- A03: Sensitive Data Exposure ---------- +password = "SuperSecret123" # potential hardcoded password +api_key = "sk_test_123456" # potential hardcoded API key +hashlib.md5(b"weak") # weak hashing algorithm + +# ---------- A07: Identification and Authentication Failures ---------- +username = "admin" # default username +default_password = "password" # default password +requests.get("https://example.com", verify=False) # TLS verification disabled + +app = Flask(__name__) + +# ---------- A02: Broken Access Control ---------- +# Sensitive route without auth decorator +@app.route("/admin") +def admin_panel(): + # Wildcard CORS header (also A05) + resp = Response("admin panel") + resp.headers["Access-Control-Allow-Origin"] = "*" + return resp + +# Login route that should be protected or checked (A07 heuristic) +@app.route("/login") +def login_page(): + print("login attempt for user") # A09: print in auth flow + return "login page" + +# ---------- A04: Insecure Design ---------- +# TODO insecure: temporary admin override without proper checks + +# ---------- A06: Vulnerable and Outdated Components ---------- +# Simulated vulnerable pins inside code string (still scanned by our rule) +requirements_block = """ +flask==0.12 +django==1.11 +""" + +# ---------- A08: Software and Data Integrity Failures ---------- +user_code = "1 + 2" +result = eval(user_code) # dangerous dynamic evaluation +data = yaml.load("key: value") # unsafe YAML load (should be yaml.safe_load) +with open("tmp.bin", "wb") as fh: + pickle.dump({"x": 1}, fh) # create a pickle to then load (unsafe) +with open("tmp.bin", "rb") as fh: + obj = pickle.load(fh) # unsafe deserialization +subprocess.run("echo hi", shell=True) # shell=True + +# ---------- A09: Security Logging and Monitoring Failures ---------- +try: + raise ValueError("x") +except: + print("error:", default_password) # prints secret-ish value and uses bare-except + +# ---------- A10: SSRF ---------- +url = input("Enter URL: ") +requests.get(url) # user-controlled URL + +# ---------- A01: Injection ---------- +user_input = input("Enter your username: ") +# Unparameterized, concatenated query assignment beginning with SELECT +query = "SELECT * FROM users WHERE username = '" + user_input + "'" + +conn = sqlite3.connect("example.db") +cursor = conn.cursor() +cursor.execute(query) # executes suspicious query var (A01) + +# Explicit Flask debug enable (A05) +if __name__ == "__main__": + app.run(debug=True) From d03e785a47f4bab479141a34204e1a42cfa57fdb Mon Sep 17 00:00:00 2001 From: Liana Perry <62174756+lperry022@users.noreply.github.com> Date: Sun, 7 Sep 2025 16:20:05 +1000 Subject: [PATCH 3/9] Remove scanner/tests folder --- scanner/tests/test_negative.py | 43 --------------- scanner/tests/test_positive.py | 95 ---------------------------------- 2 files changed, 138 deletions(-) delete mode 100644 scanner/tests/test_negative.py delete mode 100644 scanner/tests/test_positive.py diff --git a/scanner/tests/test_negative.py b/scanner/tests/test_negative.py deleted file mode 100644 index 06ac430..0000000 --- a/scanner/tests/test_negative.py +++ /dev/null @@ -1,43 +0,0 @@ -# This file should produce clean results - -import os -import sqlite3 -import hashlib -import requests -from flask import Flask - -# Assume a real auth decorator exists in the project. The scanner only checks for its presence. -def login_required(fn): - return fn - -# --- Secure Flask setup --- -app = Flask(__name__) -app.config["DEBUG"] = False -app.config["SECRET_KEY"] = os.environ.get("SECRET_KEY", "fallback_only_for_dev_builds") - -@app.route("/dashboard") -@login_required -def dashboard(): - return "secure dashboard" - -# --- Parameterised SQL query (safe) --- -username = input("Enter your username: ") -query = "SELECT * FROM users WHERE username = ?" - -conn = sqlite3.connect("example.db") -cursor = conn.cursor() -cursor.execute(query, (username,)) - -# --- Secure cryptography usage --- -hashed_password = hashlib.sha256(username.encode()).hexdigest() - -# --- Secure HTTP request (TLS verification enabled) --- -resp = requests.get("https://example.com", verify=True) -print(resp.status_code) - - -# --- Safe YAML load --- -data = yaml.safe_load("key: value") - -# --- Safe subprocess usage (no shell=True) --- -subprocess.run(["echo", "hello"], check=True) \ No newline at end of file diff --git a/scanner/tests/test_positive.py b/scanner/tests/test_positive.py deleted file mode 100644 index 7bb30b1..0000000 --- a/scanner/tests/test_positive.py +++ /dev/null @@ -1,95 +0,0 @@ -# Triggers: -# A01 Injection -# A02 Broken Access Control -# A03 Sensitive Data Exposure (Cryptographic Failures) -# A04 Insecure Design -# A05 Security Misconfiguration -# A06 Vulnerable and Outdated Components -# A07 Identification and Authentication Failures -# A08 Software and Data Integrity Failures -# A09 Security Logging and Monitoring Failures -# A10 Server-Side Request Forgery (SSRF) - -import sqlite3 -import hashlib -import requests -import yaml -import pickle -import subprocess -from flask import Flask, Response - -# ---------- A05: Security Misconfiguration ---------- -SECRET_KEY = "changeme" # hardcoded secret -ALLOWED_HOSTS = ['*'] # permissive hosts -SESSION_COOKIE_SECURE = False # insecure cookie flag -CSRF_COOKIE_SECURE = False # insecure CSRF flag - -# ---------- A03: Sensitive Data Exposure ---------- -password = "SuperSecret123" # potential hardcoded password -api_key = "sk_test_123456" # potential hardcoded API key -hashlib.md5(b"weak") # weak hashing algorithm - -# ---------- A07: Identification and Authentication Failures ---------- -username = "admin" # default username -default_password = "password" # default password -requests.get("https://example.com", verify=False) # TLS verification disabled - -app = Flask(__name__) - -# ---------- A02: Broken Access Control ---------- -# Sensitive route without auth decorator -@app.route("/admin") -def admin_panel(): - # Wildcard CORS header (also A05) - resp = Response("admin panel") - resp.headers["Access-Control-Allow-Origin"] = "*" - return resp - -# Login route that should be protected or checked (A07 heuristic) -@app.route("/login") -def login_page(): - print("login attempt for user") # A09: print in auth flow - return "login page" - -# ---------- A04: Insecure Design ---------- -# TODO insecure: temporary admin override without proper checks - -# ---------- A06: Vulnerable and Outdated Components ---------- -# Simulated vulnerable pins inside code string (still scanned by our rule) -requirements_block = """ -flask==0.12 -django==1.11 -""" - -# ---------- A08: Software and Data Integrity Failures ---------- -user_code = "1 + 2" -result = eval(user_code) # dangerous dynamic evaluation -data = yaml.load("key: value") # unsafe YAML load (should be yaml.safe_load) -with open("tmp.bin", "wb") as fh: - pickle.dump({"x": 1}, fh) # create a pickle to then load (unsafe) -with open("tmp.bin", "rb") as fh: - obj = pickle.load(fh) # unsafe deserialization -subprocess.run("echo hi", shell=True) # shell=True - -# ---------- A09: Security Logging and Monitoring Failures ---------- -try: - raise ValueError("x") -except: - print("error:", default_password) # prints secret-ish value and uses bare-except - -# ---------- A10: SSRF ---------- -url = input("Enter URL: ") -requests.get(url) # user-controlled URL - -# ---------- A01: Injection ---------- -user_input = input("Enter your username: ") -# Unparameterized, concatenated query assignment beginning with SELECT -query = "SELECT * FROM users WHERE username = '" + user_input + "'" - -conn = sqlite3.connect("example.db") -cursor = conn.cursor() -cursor.execute(query) # executes suspicious query var (A01) - -# Explicit Flask debug enable (A05) -if __name__ == "__main__": - app.run(debug=True) From cab835269ff408e65f4e9e034d32746aae170578 Mon Sep 17 00:00:00 2001 From: Liana Perry <62174756+lperry022@users.noreply.github.com> Date: Sun, 7 Sep 2025 16:22:59 +1000 Subject: [PATCH 4/9] Fix workflow: run scanner as module --- .github/workflows/scan.yml | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/.github/workflows/scan.yml b/.github/workflows/scan.yml index db7c865..75153b9 100644 --- a/.github/workflows/scan.yml +++ b/.github/workflows/scan.yml @@ -44,16 +44,16 @@ jobs: echo "CHANGED_FILES<> $GITHUB_ENV echo "$CHANGED_FILES" >> $GITHUB_ENV echo "EOF" >> $GITHUB_ENV - - name: Run OWASP scanner on changed files - run: | - if [ -z "${CHANGED_FILES}" ]; then - echo "Nothing to scan." - exit 0 - fi - EXIT=0 - while IFS= read -r file; do - [ -z "$file" ] && continue - echo "Scanning: $file" - python scanner/main.py --file "$file" || EXIT=1 - done <<< "${CHANGED_FILES}" - exit $EXIT + - name: Run OWASP scanner + run: | + if [ -z "${CHANGED_FILES}" ]; then + echo "Nothing to scan." + exit 0 + fi + EXIT=0 + while IFS= read -r file; do + [ -z "$file" ] && continue + echo "Scanning: $file" + python -m scanner.main --file "$file" || EXIT=1 + done <<< "${CHANGED_FILES}" + exit $EXIT From 33b2783ce5e9e0373bcad46d3010b6375cda78fa Mon Sep 17 00:00:00 2001 From: Liana Perry <62174756+lperry022@users.noreply.github.com> Date: Sun, 7 Sep 2025 16:24:48 +1000 Subject: [PATCH 5/9] modifying imports in main --- scanner/main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scanner/main.py b/scanner/main.py index 8f78b47..7201f2a 100644 --- a/scanner/main.py +++ b/scanner/main.py @@ -5,7 +5,7 @@ import argparse -from scanner.core import VulnerabilityScanner +from core import VulnerabilityScanner def main(): From 4badb7f987328bfb5a42ed1c096092f854fe8ec2 Mon Sep 17 00:00:00 2001 From: Liana Perry <62174756+lperry022@users.noreply.github.com> Date: Sun, 7 Sep 2025 16:26:10 +1000 Subject: [PATCH 6/9] Fix bash step: remove :: lines and run scanner as module --- .github/workflows/scan.yml | 41 +++++++++++++++++++++----------------- 1 file changed, 23 insertions(+), 18 deletions(-) diff --git a/.github/workflows/scan.yml b/.github/workflows/scan.yml index 75153b9..edec13c 100644 --- a/.github/workflows/scan.yml +++ b/.github/workflows/scan.yml @@ -16,10 +16,12 @@ jobs: uses: actions/checkout@v4 with: fetch-depth: 0 + - name: Set up Python uses: actions/setup-python@v5 with: python-version: '3.11' + - name: Install deps run: | python -m pip install -U pip @@ -28,8 +30,9 @@ jobs: elif [ -f requirements.txt ]; then pip install -r requirements.txt fi + - name: Determine changed files for this PR - if: ${{ github.event_name == 'pull_request' }} + shell: bash run: | BASE_SHA="${{ github.event.pull_request.base.sha }}" HEAD_SHA="${{ github.event.pull_request.head.sha }}" @@ -39,21 +42,23 @@ jobs: CHANGED_FILES=$(echo "$CHANGED_FILES" | grep -E '^(src/|backend/|scanner/)' || true) fi if [ -z "$CHANGED_FILES" ]; then - CHANGED_FILES="$(git ls-files src backend 2>/dev/null || true)" + CHANGED_FILES="$(git ls-files src backend scanner 2>/dev/null || true)" + fi + echo "CHANGED_FILES<> "$GITHUB_ENV" + echo "$CHANGED_FILES" >> "$GITHUB_ENV" + echo "EOF" >> "$GITHUB_ENV" + + - name: Run OWASP scanner on changed files + shell: bash + run: | + if [ -z "${CHANGED_FILES}" ]; then + echo "Nothing to scan." + exit 0 fi - echo "CHANGED_FILES<> $GITHUB_ENV - echo "$CHANGED_FILES" >> $GITHUB_ENV - echo "EOF" >> $GITHUB_ENV - - name: Run OWASP scanner - run: | - if [ -z "${CHANGED_FILES}" ]; then - echo "Nothing to scan." - exit 0 - fi - EXIT=0 - while IFS= read -r file; do - [ -z "$file" ] && continue - echo "Scanning: $file" - python -m scanner.main --file "$file" || EXIT=1 - done <<< "${CHANGED_FILES}" - exit $EXIT + EXIT=0 + while IFS= read -r file; do + [ -z "$file" ] && continue + echo "Scanning: $file" + python -m scanner.main --file "$file" || EXIT=1 + done <<< "${CHANGED_FILES}" + exit $EXIT From 2d8ade3213d8e99ceeeafa4f4f6baec63c608f3e Mon Sep 17 00:00:00 2001 From: Liana Perry <62174756+lperry022@users.noreply.github.com> Date: Sun, 7 Sep 2025 16:27:52 +1000 Subject: [PATCH 7/9] scanner: use package-relative imports (.core / ..core) --- scanner/main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scanner/main.py b/scanner/main.py index 7201f2a..7bc4d88 100644 --- a/scanner/main.py +++ b/scanner/main.py @@ -5,7 +5,7 @@ import argparse -from core import VulnerabilityScanner +from .core import VulnerabilityScanner def main(): From dbd1d6833d2ed8382149568a4d3030198504c24e Mon Sep 17 00:00:00 2001 From: Liana Perry <62174756+lperry022@users.noreply.github.com> Date: Sun, 7 Sep 2025 16:32:57 +1000 Subject: [PATCH 8/9] workflow: skip when only scanner/** changes; exclude scanner from targets; run module form --- .github/workflows/scan.yml | 36 +++++++++++++++++++++++++++--------- 1 file changed, 27 insertions(+), 9 deletions(-) diff --git a/.github/workflows/scan.yml b/.github/workflows/scan.yml index edec13c..3e35a29 100644 --- a/.github/workflows/scan.yml +++ b/.github/workflows/scan.yml @@ -3,10 +3,12 @@ name: OWASP PR Scanner on: pull_request: paths: - - 'scanner/**' - 'src/**' - 'backend/**' + - 'app/**' + - 'services/**' - '.github/workflows/**' + - 'scanner/**' jobs: scan: @@ -32,23 +34,39 @@ jobs: fi - name: Determine changed files for this PR + id: diff shell: bash run: | BASE_SHA="${{ github.event.pull_request.base.sha }}" HEAD_SHA="${{ github.event.pull_request.head.sha }}" - CHANGED_FILES=$(git diff --name-only "$BASE_SHA" "$HEAD_SHA" \ - | grep -E '\.(js|jsx|ts|tsx|py|java|go|rb|php|html|css|md)$' || true) - if [ -n "$CHANGED_FILES" ]; then - CHANGED_FILES=$(echo "$CHANGED_FILES" | grep -E '^(src/|backend/|scanner/)' || true) + + RAW=$(git diff --name-only "$BASE_SHA" "$HEAD_SHA" || true) + + APP_CHANGED=$(echo "$RAW" \ + | grep -E '\.(js|jsx|ts|tsx|py|java|go|rb|php|html|css|md)$' \ + | grep -E '^(src/|backend/|app/|services/)' || true) + + SCANNER_ONLY=$(echo "$RAW" | grep -E '^scanner/' || true) + + if [ -z "$APP_CHANGED" ] && [ -n "$SCANNER_ONLY" ]; then + echo "only_scanner_changes=true" >> $GITHUB_OUTPUT + exit 0 fi - if [ -z "$CHANGED_FILES" ]; then - CHANGED_FILES="$(git ls-files src backend scanner 2>/dev/null || true)" + + if [ -z "$APP_CHANGED" ]; then + APP_CHANGED="$(git ls-files src backend app services 2>/dev/null || true)" fi + echo "CHANGED_FILES<> "$GITHUB_ENV" - echo "$CHANGED_FILES" >> "$GITHUB_ENV" + echo "$APP_CHANGED" >> "$GITHUB_ENV" echo "EOF" >> "$GITHUB_ENV" + - name: Skip when only scanner/** changed + if: steps.diff.outputs.only_scanner_changes == 'true' + run: echo "Only scanner/** changed; skipping scan." + - name: Run OWASP scanner on changed files + if: steps.diff.outputs.only_scanner_changes != 'true' shell: bash run: | if [ -z "${CHANGED_FILES}" ]; then @@ -59,6 +77,6 @@ jobs: while IFS= read -r file; do [ -z "$file" ] && continue echo "Scanning: $file" - python -m scanner.main --file "$file" || EXIT=1 + python -m scanner.main "$file" || EXIT=1 done <<< "${CHANGED_FILES}" exit $EXIT From e42f36d0494dc6b4aa177f3744241a3c6e369273 Mon Sep 17 00:00:00 2001 From: Liana Perry <62174756+lperry022@users.noreply.github.com> Date: Wed, 17 Sep 2025 18:54:55 +1000 Subject: [PATCH 9/9] chore: remove __pycache__ from PR and ignore Python bytecode --- scanner/__pycache__/__init__.cpython-313.pyc | Bin 162 -> 0 bytes scanner/__pycache__/core.cpython-313.pyc | Bin 7896 -> 0 bytes scanner/__pycache__/main.cpython-313.pyc | Bin 871 -> 0 bytes .../rules/__pycache__/__init__.cpython-313.pyc | Bin 168 -> 0 bytes .../__pycache__/auth_failures.cpython-313.pyc | Bin 1602 -> 0 bytes .../broken_access_control.cpython-313.pyc | Bin 4148 -> 0 bytes .../__pycache__/insecure_design.cpython-313.pyc | Bin 1426 -> 0 bytes .../integrity_failures.cpython-313.pyc | Bin 1476 -> 0 bytes .../__pycache__/logging_failures.cpython-313.pyc | Bin 2132 -> 0 bytes .../security_misconfig.cpython-313.pyc | Bin 3273 -> 0 bytes .../security_misconfiguration.cpython-313.pyc | Bin 3280 -> 0 bytes .../sensitive_data_exposure.cpython-313.pyc | Bin 1604 -> 0 bytes .../__pycache__/sql_injection.cpython-313.pyc | Bin 1251 -> 0 bytes scanner/rules/__pycache__/ssrf.cpython-313.pyc | Bin 1537 -> 0 bytes .../vulnerable_components.cpython-313.pyc | Bin 1230 -> 0 bytes 15 files changed, 0 insertions(+), 0 deletions(-) delete mode 100644 scanner/__pycache__/__init__.cpython-313.pyc delete mode 100644 scanner/__pycache__/core.cpython-313.pyc delete mode 100644 scanner/__pycache__/main.cpython-313.pyc delete mode 100644 scanner/rules/__pycache__/__init__.cpython-313.pyc delete mode 100644 scanner/rules/__pycache__/auth_failures.cpython-313.pyc delete mode 100644 scanner/rules/__pycache__/broken_access_control.cpython-313.pyc delete mode 100644 scanner/rules/__pycache__/insecure_design.cpython-313.pyc delete mode 100644 scanner/rules/__pycache__/integrity_failures.cpython-313.pyc delete mode 100644 scanner/rules/__pycache__/logging_failures.cpython-313.pyc delete mode 100644 scanner/rules/__pycache__/security_misconfig.cpython-313.pyc delete mode 100644 scanner/rules/__pycache__/security_misconfiguration.cpython-313.pyc delete mode 100644 scanner/rules/__pycache__/sensitive_data_exposure.cpython-313.pyc delete mode 100644 scanner/rules/__pycache__/sql_injection.cpython-313.pyc delete mode 100644 scanner/rules/__pycache__/ssrf.cpython-313.pyc delete mode 100644 scanner/rules/__pycache__/vulnerable_components.cpython-313.pyc diff --git a/scanner/__pycache__/__init__.cpython-313.pyc b/scanner/__pycache__/__init__.cpython-313.pyc deleted file mode 100644 index 309c0b1130a3049d5ff4cc4775b8d9832fd2107f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 162 zcmey&%ge<81l7|fWq|0%AOZ#$p^VQgK*m&tbOudEzm*I{OhDdekkl{%)A(v{N&Qy)Vz}7828K)kJ6-={PM)&0^Q=|#Js%Jq8Jz*AD@|*SrQ+wS5SG2 f!zMRBr8Fniu80+ABFM&K5aS~=BO_xGGmr%U3cV>l diff --git a/scanner/__pycache__/core.cpython-313.pyc b/scanner/__pycache__/core.cpython-313.pyc deleted file mode 100644 index 84218431a68f2b6cb8de42c306d5d34c399a6be0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7896 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 Hl-K(|9EFKR diff --git a/scanner/__pycache__/main.cpython-313.pyc b/scanner/__pycache__/main.cpython-313.pyc deleted file mode 100644 index 46735deab6d402eca8db754cd99244c5bab2ab46..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 871 zcmaJ<-D}fO6hAk){jgc8n;X+PMi`sWhY5mvXqm#?*u-M33EhMRLe|DM&?ey~MYg97 z6n&S0kLru~-*gV`yy%m@_%`f6;JH~NY!4o|zjJ=)>zw^UFQ;EGr(k%8npBjz|I1g@;oe$3N>hwCMl$T3tlM@KDpIx zdk(XA-L@O;8Fkx3Rv%@@DupQH8Gc1Lgq_$aKu@Om=K+|E#d&-5}m)1Fk5boG2pu%r{-BXaFjyLunThV$u6HZg z<5U=I0-Gs#!Yzazd{?Ii>cVGrp?~|yk*fDLPH1jGr;q6L+jM{NeqWetC(8IhnLScw zkCnOJ`mg-_yE`9??~BLzrQYTVRbG`3$}b)XqjFIBLMMmV8F<#R#D$hsNHKh>z-irQ z&e_L{Aw+^_cO1+51neq0*m&$+>G+Lq+quWCAr&tnyoqos6GDc0AXk3C;-7SeOr1^u V9ecfhEMNadvoAMJWuP;0pT8e{#tZ-e diff --git a/scanner/rules/__pycache__/__init__.cpython-313.pyc b/scanner/rules/__pycache__/__init__.cpython-313.pyc deleted file mode 100644 index 276807cad9d7c679b942d79020047381b6b658fb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 168 zcmey&%ge<81l7|fWq|0%AOZ#$p^VQgK*m&tbOudEzm*I{OhDdekkl=AXRDad;?$zz z7{`>{%)A(v{N&Qy)Vz}7828K)kJ6-={PM)&0^Q=|#Js%Jq8J!mRGO1o91|a(nU`4- kAFo$Xd5gm)H$Md^YFESxG#6xVF^KVznURsPh#ANN06=dm%K!iX diff --git a/scanner/rules/__pycache__/auth_failures.cpython-313.pyc b/scanner/rules/__pycache__/auth_failures.cpython-313.pyc deleted file mode 100644 index 07c117af2dda3a62c50f777aeabf0e9ee434184a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1602 zcmcIkPi)&%7=O;cHgOWA?HC9Fvbt`IC#5ZnFtuYFVNH{0O{wZdnyB0A)qZU}TsypH zuhgm#Cj?vuheC%xzI@9+C( zyPZlU5YW3fKdFBjL+Cyix)ka#?LlB}Aq6SI6-1E|dKyt-O_0M6xWla0_2W`MoG_gB zIB0L71%WF$)lj~}522D!hFmUqG)^oWM$bnMJbUk9$XN^LJ3iTt2}^({fGwniYvK96 z-iQM}-%+K}*-$N{L~>ArVPv4awNvPuI#9$ zv0=KDFz@`I4LM3o>=0cF5=t9}Q-{nFd8=U>n&C)1rKHt~_6D1j9`HBVkCZE=vLCA! zrz*9o7pLS+moSHoddVAv8mpTZrm@M0H&U?}(IBUEbw-f_Q@0r1V0gtO`lRHY$-0ct ztcBN!d`V?zF2Gqhtqw0{vAzyHQ`>grKp}f4%b4n#twXgsS;emDNIaOwg2Evh^Z}6L z*#>58!>0NXc+bCZ_QkEP{cnJ~Err6@=I1g~`2G%UjW0j%rGH(b+#*cHPxoK0Ua{77KrU%dq6&s?5VYtRo<3%qr8 z%GMy6Y9Y9d{4`(oh6SpHvMR@G&%t2JOfL??%qK^ z4Lx)F_2y9a{l52vpT*S8i4P`rpD5mOn&Ql!IP+uj*;b~|8m#W<_paZ){++qAw3|^| znSl=rA4OZ4`2f diff --git a/scanner/rules/__pycache__/broken_access_control.cpython-313.pyc b/scanner/rules/__pycache__/broken_access_control.cpython-313.pyc deleted file mode 100644 index aa1f71182c171a5624e82377e5e392982b5142a3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4148 zcmcgueM}qY8Gp}T*gn2(D44GcNeG67qy(DbKgwX;S{9Bihve_Pn+a(8qs*p`P-cG6z5tt6rBBx&>+ zZ3vs@D7)ziabkS&On-Y+oQnuS%%4j{FwZTWU66|}Sd8-0me)L!6l4=}c!^9!TAatR zh$KbCqAvSlO@I3dM_{2d&}Bb?#e~cUq>e!QV)Yxaa{O#S>LkS6x6UmJLQz(dcnJu{ z2yq%FXEZ7pe#B@7M(}8`hXQtmE zb7yW3-}b`;dS??th)662+U=pOcr`+{weW28>j|o-3n)N>U~DX?!@{5n-@d*Lfc-$3Xf67+tpVDKA! z+5P;&gW`0G3|`nP(!gD0w17P9R#c_^P*F#9Qph4z*Db>1CtP>v33{MtPB{brx3mp~In z5f_5iL+3aMcC{Yp7U0h9x9?0AGIWx1Lw8Qs=k-B$HoL-thQ|;}fUofnrfn+yMLqR= z*K!0;1oxU3#9Ug4x$GOnOc!D<-5Rrd93dKF-^Od_rq_-Cqu2SGlz(&od|#=Fdm!)M z9I{u8hbYwvSjo@tFgzQOS+pBMxSIQ=4nukcY8t-fhj;T=s(yh~QYt{tqpNxbxgj`` zggJCXJ4@Z7#_bxVcF;Q%9C5$q@lFJtV`H94=lJ)6UYBB!cov7kz@h0nr0Aj%ftR2{ zh+&k80Y|_u@mLBtxmZL942q#dj2C1na55qfCuRcT0xQK2NFi1bcpOkyoPgstFoVT; zUI?JbHKVVPkiuu`kuwcV|zg@kFxm#K)swZjhwC)w+km#N{U>oeI_e|4Az*eFkZ( za{4ERgM+RCkJmZj@j^~9Ilb%}Y|OdC71D{xZ4r|r1DC=?N?i=G+4IrpopTRc4rN-7 zq*{*Lp)xIwWQ*gY(JwJMNEEc#P`r7lr+0hd__iqQ!!dbmuUMO~G846}Tvm8CI5Azzer&_k2W(%tZOT@mPcdt_2qJ z4#r-gXj$Q+qK)!`Qa&{1^qmTNJ^l%oS{ubM=o-p_KPt&M5?9QU%wkz05h(@|pTH^l zJV}Fj#XNFy+~ai(IDIZfKL=TeD<)omUVw0UrGj+U)|RStO}*xI`Fxv3?PZENByxN( z*Ih~#%W=W;i6}G;I}?dUX_+Db!iT7-sV~SH%RE zyl^lK30A0>VwCvvpnslMv=QzCY2m3Pbl?LjS01NHt8} zA5Aw*rt2n?x|($ZGMO(qvu64yp_Pue#fRpmY(@R$A1v!0(e)XcNzu$rYld$78{PJ} zzF~PZTUoX2%vM`g*wwwi+I{`(FAt`x?H{y#c;Nj5cg<_(?)RqpPiMNlsctWDuAE*q ztWa4?)9SH|Wnap&FKw}BEIlbp&uw4E(wDOIWv$H_>)w=g@6EIC#4>GtskXj!+jk#Y zkFJ*>i}h=aq`STAr3lQtd^z!R>AK@*Z>B5S{`ks!$8H_FV_T!{xl(-{JTi27djjZi?&~d-xll^PW$-1d*UHx_E4Slwb$<{SLr42Q;>*c7X_ByrdTBRQ6 zQwE#TvX!dKL(9(f0SeKj`OlUy16Gq?@QDB zK5yz;edYG>^5AEsP1(x*+3K;ii3hLWfBlod+IX^ZI$KHVR+6o>W-Dv|O&iL}zbHYq zolon&s`u2Jx#Z(y1Yr>WX5+U|aJf9XNt1^^D( z&A1sBr6eTA;t}X>@~_9_l7s8WM6Mi|90HhJ@bGRj5wzYy_+_#uzf#3`G$wLz)*Qz@ z@Jjyu(lwaYwG>5d*brrY3`HEhGMd$KS)JiW?jO20^x7It5>>4mQML7&?TRhg*mHa4 z!`S<=bk*Rc@~nyesqM$Mq}BdzHvvl8)OX477&tqwbR?TjrK`s-Rb=ZMeq;NEEx9L{ mWJ4KNNU=h?UQD7|&2 diff --git a/scanner/rules/__pycache__/insecure_design.cpython-313.pyc b/scanner/rules/__pycache__/insecure_design.cpython-313.pyc deleted file mode 100644 index 3e2c4be831ec8dfe6c215c6261ee4ea6bf8c7e0f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1426 zcmbVLOKcle6ur+g<4@c;PTVR{i=}BQArHu=QXPn_kYLqOJ`kt!#G)bt(R?1xP|m2!gr%zNcw>8<3JDgylJC27zvq73 z9UUD3Mw83mZGXuD{Na806dDNK$4oc?8D#W25Ky2L@Lf*dX^;cV!k=nC;WfhOFZG^;CUc?1xcY}uZ?0Wt!Q=IuoW%yf}*8Ni(&-p2QQzLv znvP1Tq-d=R%pKPpo2aDOkFj?!A*O+u@nuCbOsZ;CoHC>Zg}#{bqEpzao4Q&zZA)6{ z<+F*oNn3CCRV;2=(fSow{mNvS zf-Su3ka>4X-^Til%v{yh)heCO3@ziVHod^ulVjM2qgeL-?C0voySI1mh98YxJQ}-r zfA(+cjpW0mh+1d!m$Nx>WpI{ap;xLkea)_WSEW`(ABEUQA=XrK1CyM1>`l!Lt}PZ; z%4;t7#`>EQ??$l2`Xg!`yFzamE~i>eH+(I>v9VYx%97xY=(d5ks-}ghJFXhW)=s0! zdRH~G%Ki>6GF{#?67L47w_K6YYb4@T6M8)pwhI{b%q7O2?8EPg=kC7sYa(?!bPIJ7 zlb_}8^~2Sp_(CT>{z?9h(1}lV z;^!VmgQKy(B5+pfvaG<61_*XTx?OY3Dt1R!mWu18#jAOFkxVdq55zqnQ=Z%d7oMxx z23y=bNiv-`9cnOk!XbpZVL&4%K@dV8t$wh2G`8@=#DnCI$u_^<;e&lu`U~>)&3iZ7 q{HsIN^26z$(?6x#{Pm$Kd#HV1`=-|B3nvlbOkh9UjYUzchvFa2ibeDQ diff --git a/scanner/rules/__pycache__/integrity_failures.cpython-313.pyc b/scanner/rules/__pycache__/integrity_failures.cpython-313.pyc deleted file mode 100644 index 74b9fa5356045fadbde3cd74db5b69ae22ae236b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1476 zcmaJ=&2Jk;6rZ)%j^nkB9S2CG5Jn9Zc0@ue6hKY@ndZYuaRgpg3UnoFvO9@aUGG{m zy9rq?J(gPws+t3bN?d}ta6vK$ME-^!ro@}dQuq;tbtaJqEL?q{gK#HAX^q5e|+ z{`gscVEC7Y{GTq)n0^sQ`F8*$0KqQEXEuFd#*5%RWagOq3TrtpU%dW)0m}A{vyTXd z$kO35au8g%9K1^m=K!uE!*mIz-Zh0{XzxHBS-Y6nE`|Dmh3bX|Iirgl!?p_W8K1u| zme$uwUZP=WcTC(}oSc^&@@USR`_y)@u0_!fhB~I0FyHXHTyW1ou!ku?f0UzAE1Uz6Yd*M4Gt16N)%u|;*Vm>tz&{5>_@0WjD{bl4k8ZaC_kla%Ei@{ zD(|maS@EM3Bcjz9F0R=+JA`3j>SvIySNB}gVm#Z1$*$TL3}56Q{aBZ@fpyAv6>{-E zyID}#1PE1&dfl+pWm{uCSPoU!45#F7tM)#kjXA0zi&3h9Ca#I8Y6Rn~?)1kw-#G9i zS`BM=$T+LP|6&?rv%3ra%uXGC`A0VQ&B#5WoqhFD;X&cMrF*fqls%KCPo?QYdM3@B zN;6Lr<*@WbdgExSIq@W43?NI*t>(r_dhLIZe|d-h+!DZezQZmAN^@d?$l3=m!b^Qp)%iPYgmmgcI+d8}E56Kng^8KgN>~$U%1fdfHLj12x_R-|G UlP6QxTbUboQyuXwA=SnI2YqsLlmGw# diff --git a/scanner/rules/__pycache__/logging_failures.cpython-313.pyc b/scanner/rules/__pycache__/logging_failures.cpython-313.pyc deleted file mode 100644 index 3f41262f15de1e1b194b6cddbb63c8c9e569c628..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2132 zcmaJ?TW=Fb6rS}xv2*byTq+tC8woo%5``2nQ6LE+X-h6-;}Q`QYw=E;CC;ujv&O_P zMCwZr4-i!mLFxlf6;zd)A|X|3ANUEjjIbH?AyS|6hMG#HZ$0BnQlMqzojE&a=6v7z z&g^)%p`ji@TN-`7IO;&?cm7gqrmC@f0E`VJAxZcW5hR(2Aelcw#FP+1mL0xmB4lHZ zluT(xCknHbEf^_|6KpWMoKj;;c!k-(;G@;hm#`ME#pe+K8=2wR&B5%veFM!2e44ii z>Z{sbG%Acm5K6yNB{YEDz)(_>p9htPx8i>NQ17lC2Z=NOsBI zSLG|B8T)^7bG#o9Az0>uv95fs7w{iZ}$d~r&=&9)l75o=};5mzs6NxB+Lf#1` z1hTdiQ8gnN-#gh5j-TLOcy>FW8xR7pegjUt7mY#Qx_idNF>j#_tBA&(wRoq^3qnYk zc#1-1X6ou@LWY@>dWP~~qPWT?hr`hb#e_x&m1R|nUex31Wvm%AI;t9D=^If!BhyqT zjmw&bNwhYSbQ05OQcomQEfHIg)nuAr+Lc;ib^Qsf;oB)Pzz)S1aeS$(KdHy%BpvA5 z^YBwdxzulW&??ILhn_{ZgeT_j&OD3$O`&AyYqw9xb`Ex*4T}&s!;>Wil{HZl%B#wtO|BgPGVwk_8OK+SY+nQlUK9e3vw#eMSv&{o;V3h zRqez(lm5!2e}@;33E~d_)UqQ}{HTa&3h=5X%4uT}>ZitKgA-eTVxI$eya4djAnCXP zl~OvmNMO_$b%p9#9oC@qL^d$9!b&y8tgsmqR!g4oA ziJ1s&SeA`Ac#~LTmYce&g&eFtt}8f}R5eVQTUM0Vt#lI3kbFZ;LOq$FG7EPyM;R=I z6=v3M8_bqbHAT;mCPPl`VlAnsaoSn->7Yeh9S5t}YmO~F`*Jn3x zKDn~xej3TO_7+-B60 diff --git a/scanner/rules/__pycache__/security_misconfig.cpython-313.pyc b/scanner/rules/__pycache__/security_misconfig.cpython-313.pyc deleted file mode 100644 index 759c9a06f9ff0ca5df4f1448a994f5a629565dee..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3273 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 diff --git a/scanner/rules/__pycache__/sensitive_data_exposure.cpython-313.pyc b/scanner/rules/__pycache__/sensitive_data_exposure.cpython-313.pyc deleted file mode 100644 index e4cf0d64727356126eb0c4487f9f2b8094112f4a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1604 zcmcIk%}*Ow5P!RCFNOu%U_uD_+5%}~(HL4vDs3n#F$hNysAX|eH5S(5eZ?EwyViSa z3`mubdP&s_s6ydV^;9Y3#4&#Ymr77qVBVopm0q|-HL8+Z=PfpkhKYR$i;7WtUx=`x?VH4>{7w#j1bg>5!VNr-m$J}8i)9~Y30A2~+ znk^tSFT*K87Y9XK97MYGxnN5V!G20fG*Xu%DDfjgCBP|8CjGPg{5cw_>jimEp7o#G z1XcGnux{2j4M3Z|h0w#MhIdl_NYMQ~=usdkMo)nmnHppeS?l9YFK+Z$NLY>oUk~r?Qv(_h)$o-^wHOZywV> zA*3J`Euv_6hDil;g!yRJ_#i5?K+&MI>=2X5o&+n!8G>CV8$~OJS6CCQX)gF(Cx>k& z^kvT413AJ#pka7x31DMFAB5RNNd)b>h>+7hC(gd<4L+P2h2@uALYsaSOCNwo}7A!k8;bck$Y`Ziy zWx0vcLdq!{w0MhV4BN&e)mTX>k7x3}lRT zXRU?{> zS6IC=J~XVUdLyJd0U_1NddSsx9$+J_yecFFM3>vjx?}F3z~v)YM~MMdnM~ zn@aru-2lUD0uzCl@X2b6Lu1%pvWR1^2KlY?)RGE)-YQLH^7Q1yz2tP%$C|MXEhmPHnctfa)uvWRhX8M4MO(he_ux8&lS9 zm}YvZl!t*Z7OcGGt}vwxcIToj1c}? z;SHy|z+SDPYNUJX-Sxq>`|Q$#Jzv+Kkt_3;gV zHQWWyi6XZJPddCq@%^E@m0h7kJAKlCin4cg-ne1T*nGuM!f^Au_Ikbp96)U!lt@in4H2L)p->mg)7GTz}--0dy|+UB`C8=$iH*;9PA1M z^*A}Cu*ISO@>1Q@FuD}z@#L@^NpYPA0#X8SH!mY(hU5VyG>qivCFstdqyio0M~)O? z|4SkKTp>S)Pz(tu|ES}uNE|FQ#VaZ~zjjVN^Dg*=EUie3rL9~+mWrh<<*lVmN!nV1 zWJwY!bD=rtgVfs>MDSOh`9ErNICF6>E#%Diu zAdYRARYA8*9df4TW77g)3|6G+<*IB3QHabsEhr7v3yNiy2v>TwQY@#Z_&~ZBWkdrUA^T zZdGu(W|)}JF|AT5Z@4uRD%~_{hOXV{~OPtttfDM9oy{hN&)DIy7lIL|rzVtb0?nZfm5GB)VpTqT2JeTf;;pw`ygh zccrt9P0H!(SbyK11c5<3!@4@HX=_HuTBmk1w~5W%lO3 zAOGRhcc*@e-FO_k(NfDTy?UTlpH9Z_M81mLec}Gh!Q?C3xnD*mz3G`dSHHZvJ9jXh z{50YvXYY^QQ(B|K_tTFS_GVlBb&o%`!+a)reB9$t{uK+2 zjl-v6O5DzRV^ceco%r3k*2s&y{F9O7&t3k)gVHyv4_Cid9~4^rwSIp5??XNZ{69}4 zUTni)NDSEi=jm|Vd&r*h;}fva$U@z!zzw}_Ps7pw{}FgI&>umDVcI-mqHT7X89yZa E1HddK@&Et; diff --git a/scanner/rules/__pycache__/ssrf.cpython-313.pyc b/scanner/rules/__pycache__/ssrf.cpython-313.pyc deleted file mode 100644 index 3ebfd83f12c5d5b6fa6c1f44fad989f6e64064af..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1537 zcmcIk&rcgi6rT0^SG<4=HiWneU4p1=BBLPcrdYHRq7bJAlBnNU_$0h-pPa(pu zbD7`^Z)v4IrIrZ(qK2tU)yecacDJeyg{cOg=IZ4w16$a|TV<>n8I?|`Y5mnp&H;!Q zPHPrcAE4FlMF{0Qco=qI)ejdb$PXle3KfKE;8T4^2}(fl_3^iVNC`=i*LM+6_Oa64 zO%2eNvk0w5{nJuAg}&91UOJReuFJs=D`DT?@cfq%QUX%%Ss%|*!czR7t1|CL;Y$%M zI|vC#ME}D3>;H}S8-9FJ!q-kEaPHRT4BzV>U#P}=P^ri(nHxCu^fky_P>J-xJk6hG z@C@2GpmA_6B!`tyADRv*yv##MkQ|XCxo(w3$j$Osog6LicH}zBL{}I$R?mJ0J(gab z%ihwm_2ODrxsshJu1$FD_da~44c*yp#TOo>dO$7J#kr{~c~Qn>1Cy+58d#J%T`ev; zWF3>udf|rSV%s$}OO$14F)!Ba4b3tQ&BcaTqu{1Hwo4q#0$Y)m#HvOqsIDXW4`gy8 zoym(VSh#+zkcly#Vwau6cCCU5ps)~iiCJYa>T1NLcTBg;0tA*T-aBHvW*98APMlhm z#ifOt%7Xk!aei)TsVFV5FvS|t%b6&P>5hSmmT6Mu5F-7+pr|4#Eo1PP>^cs3d^zDDj!X~-L^_LSlivf*=D`F z%&rr~r`~YjkXEQ72gIEV7yf}Bb4-*%+z~>N=p~#gEOG8^>W#~RcbMOs_c8DH-n`!M za1yALe*IqHW zb$^uz=0n|SxW?O560}4}y0=J)JcXq`9RL*~YFW7PBS4F0dE+Wui&_lH^<}D$4yST8 zKC%u_VWPJt`bM3;Hdg>x5~8TuP^1k|<)S=?SNSN{40C5FL%t>sVk2!Z(%Nuj8{i33 z;eWE(lZtUwSdya5`;_ZwQ5p&!R7yedeb2x?q{PdPQF#ArL9#FfNUe*!Yh{RAL_ z3=PN+%@bz)D4gz5IR5rW<;$NH<-0B0L0EraBUyJ$`Bp11^#GZ2#cg`7gPg!`e=?0c zI(<`}Bkb(0d-((z zLQbnmO9hDVet<2HB>h0gfxm7AD@0hhIbW|;3E$KMV}#a5jyN0kI zZk^m0a;H+%RG(V4ve~p8ZQ3;`Roc6D-3o5B9%$~m?t2B_&>cD~EkfL~k*`H{ZB#c- zhIYx@AiS}Hj8%M&RtmSorz;GP;E(LN?Zw}-6Pxi4)6Ks3L-9%R`>UPAD*;lu195au z9NqR0#PL0G{6#AFGL_ky-JIP%v!5F4Fn^v(Q`@0*YOAr?c&vZ-@IV^flScO?xx;n` z)((3pO3yQ6zlmcnQs=wrE8WcWGv!%zC$m%8nc7X??xs&aEZz!7t%fMT}C>7>^1AE9?t*(