From 343268c003eadbaa014267ee69491dad0f8a56cf Mon Sep 17 00:00:00 2001 From: Liana Perry <62174756+lperry022@users.noreply.github.com> Date: Sat, 20 Sep 2025 19:10:57 +1000 Subject: [PATCH 1/3] Add scanner folder from redback-cyber repo (ignore __pycache__) --- .gitignore | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.gitignore b/.gitignore index 8fc4e6f3b..e15806414 100644 --- a/.gitignore +++ b/.gitignore @@ -22,3 +22,7 @@ package-lock.json package.json yarn.lock package-lock.json + +__pycache__/ +*.pyc +*.pyo From 63acb92e6e60b51de7132164f07a93935974ee29 Mon Sep 17 00:00:00 2001 From: Liana Perry <62174756+lperry022@users.noreply.github.com> Date: Thu, 25 Sep 2025 18:58:41 +1000 Subject: [PATCH 2/3] Add OWASP workflow and updated core.py + main.py --- .github/workflows/owasp.yml | 133 +++++++++++++++++ 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 | 140 ++++++++++++++++++ scanner/main.py | 33 +++++ 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 +++++ 31 files changed, 818 insertions(+) create mode 100644 .github/workflows/owasp.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 diff --git a/.github/workflows/owasp.yml b/.github/workflows/owasp.yml new file mode 100644 index 000000000..c84461d77 --- /dev/null +++ b/.github/workflows/owasp.yml @@ -0,0 +1,133 @@ +name: OWASP PR Scanner + +on: + pull_request_target: + types: [opened, synchronize, reopened] + +permissions: + contents: read + pull-requests: write + issues: write + +jobs: + scan: + runs-on: ubuntu-latest + + steps: + - name: Checkout PR HEAD + uses: actions/checkout@v4 + with: + ref: ${{ github.event.pull_request.head.sha }} + 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 + id: diff + run: | + BASE_SHA="${{ github.event.pull_request.base.sha }}" + HEAD_SHA="${{ github.event.pull_request.head.sha }}" + 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|conf|yml|yaml|json)$' \ + || true)" + if [ -z "$APP_CHANGED" ]; then + APP_CHANGED="$(git ls-files)" + fi + echo "changed_files<> $GITHUB_OUTPUT + echo "$APP_CHANGED" >> $GITHUB_OUTPUT + echo "EOF" >> $GITHUB_OUTPUT + + - name: Run OWASP scanner + id: owasp + run: | + CHANGED_FILES="${{ steps.diff.outputs.changed_files }}" + if [ -z "$CHANGED_FILES" ]; then + echo "Nothing to scan." | tee owasp-results.txt + echo "vulnerabilities_found=false" >> $GITHUB_OUTPUT + exit 0 + fi + + if [ ! -d "scanner" ]; then + echo "::error::Scanner module not found (scanner/)." + exit 1 + fi + + : > owasp-results.txt + EXIT=0 + while IFS= read -r file; do + [ -z "$file" ] && continue + echo "### File: $file" >> owasp-results.txt + echo '```' >> owasp-results.txt + python -m scanner.main "$file" >> owasp-results.txt 2>&1 || EXIT=1 + echo '```' >> owasp-results.txt + echo "" >> owasp-results.txt + done <<< "$CHANGED_FILES" + + if [ $EXIT -ne 0 ]; then + echo "vulnerabilities_found=true" >> $GITHUB_OUTPUT + else + echo "vulnerabilities_found=false" >> $GITHUB_OUTPUT + fi + + - name: Create PR comment body + if: always() + run: | + RESULTS=$(cat owasp-results.txt || echo "No results.") + if [ "${{ steps.owasp.outputs.vulnerabilities_found }}" == "true" ]; then + echo 'comment_body<> $GITHUB_ENV + echo '## πŸ”’ OWASP Scanner Results' >> $GITHUB_ENV + echo '' >> $GITHUB_ENV + echo 'Vulnerabilities were detected:' >> $GITHUB_ENV + echo '```' >> $GITHUB_ENV + echo "$RESULTS" >> $GITHUB_ENV + echo '```' >> $GITHUB_ENV + echo 'β›” Please address these before merging.' >> $GITHUB_ENV + echo 'EOF' >> $GITHUB_ENV + else + echo 'comment_body<> $GITHUB_ENV + echo '## πŸ”’ OWASP Scanner Results' >> $GITHUB_ENV + echo '' >> $GITHUB_ENV + echo 'No vulnerabilities detected.' >> $GITHUB_ENV + echo '```' >> $GITHUB_ENV + echo "$RESULTS" >> $GITHUB_ENV + echo '```' >> $GITHUB_ENV + echo 'βœ… Good to go.' >> $GITHUB_ENV + echo 'EOF' >> $GITHUB_ENV + fi + + - name: Comment PR + uses: peter-evans/create-or-update-comment@v4 + with: + issue-number: ${{ github.event.pull_request.number }} + body: ${{ env.comment_body }} + + - name: Upload scan artifact + uses: actions/upload-artifact@v4 + with: + name: owasp-scan-results + path: owasp-results.txt + retention-days: 5 + + - name: Fail if vulnerabilities found + if: steps.owasp.outputs.vulnerabilities_found == 'true' + run: | + echo "::error::❌ Vulnerabilities detected! Merge blocked." + exit 1 + + - name: Safe to merge + if: steps.owasp.outputs.vulnerabilities_found == 'false' + run: | + echo "βœ… No vulnerabilities found. Safe to merge." \ No newline at end of file diff --git a/scanner/__init__.py b/scanner/__init__.py new file mode 100644 index 000000000..e69de29bb 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 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 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 000000000..dd3b1627d --- /dev/null +++ b/scanner/core.py @@ -0,0 +1,140 @@ +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) + + 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: + rule.check(self.code_lines, self.add_vulnerability) + + def run(self): + if not self.parse_file(): + return + self.run_checks() + + def report(self): + """Outputs results with colors locally, or clean Markdown when in GitHub Actions.""" + def supports_truecolor() -> bool: + return os.environ.get("COLORTERM", "").lower() in ("truecolor", "24bit") + + disable_color = os.environ.get("GITHUB_ACTIONS") == "true" + + def rgb(r, g, b) -> str: + return f"\033[38;2;{r};{g};{b}m" + + ANSI = { + "reset": "" if disable_color else "\033[0m", + "bold": "" if disable_color else "\033[1m", + "cyan": "" if disable_color else "\033[96m", + "magenta": "" if disable_color else "\033[95m", + "yellow": "" if disable_color else "\033[93m", + "red": "" if disable_color else "\033[91m", + "green": "" if disable_color else "\033[92m", + "blue": "" if disable_color else "\033[94m", + } + + TRUECOLOR = supports_truecolor() and not disable_color + + sev_color = { + "CRITICAL": "**CRITICAL**" if disable_color else (rgb(220, 20, 60) if TRUECOLOR else ANSI["red"] + ANSI["bold"]), + "HIGH": "**HIGH**" if disable_color else (rgb(255, 0, 0) if TRUECOLOR else ANSI["red"]), + "MEDIUM": "**MEDIUM**" if disable_color else (rgb(255, 165, 0) if TRUECOLOR else ANSI["yellow"]), + "LOW": "**LOW**" if disable_color else (rgb(0, 200, 0) if TRUECOLOR else ANSI["green"]), + } + + # ---- Print header ---- + if disable_color: + print(f"\n### πŸ”’ OWASP Scanner Results for `{self.file_path}`") + else: + print(f"\n{ANSI['bold']}{ANSI['cyan']}Scan Results for {self.file_path}:{ANSI['reset']}") + + if not self.vulnerabilities: + msg = "βœ… No vulnerabilities found." + print(msg) + 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"]) + sev_counts = {"CRITICAL": 0, "HIGH": 0, "MEDIUM": 0, "LOW": 0} + for v in items: + sev_counts[v["severity"]] += 1 + + if disable_color: + print(f"\n#### {cat} ({len(items)} findings)") + chips = [] + for k in ["CRITICAL", "HIGH", "MEDIUM", "LOW"]: + if sev_counts[k]: + chips.append(f"{k}: {sev_counts[k]}") + if chips: + print(f"**Summary:** " + ", ".join(chips)) + else: + print(f"\n{ANSI['bold']}{ANSI['magenta']}=== {cat} ({len(items)} findings) ==={ANSI['reset']}") + + # ---- List individual vulnerabilities ---- + for v in items: + sev = sev_color.get(v["severity"], v["severity"]) + if disable_color: + print(f"- Line {v['line']} | Severity {sev} | Confidence {v['confidence']}") + print(f" β†’ {v['description']}") + else: + print(f" {ANSI['bold']}β€’ Line {v['line']} |{ANSI['reset']} " + f"Severity {sev}{ANSI['reset']} | " + f"Confidence {v['confidence']}") + print(f" β†’ {v['description']}") \ No newline at end of file diff --git a/scanner/main.py b/scanner/main.py new file mode 100644 index 000000000..0dfa7da4a --- /dev/null +++ b/scanner/main.py @@ -0,0 +1,33 @@ +import sys +import os +from scanner.core import VulnerabilityScanner + + +def main(file_paths): + any_vulns = False + + for file_path in file_paths: + scanner = VulnerabilityScanner(file_path) + if not scanner.parse_file(): + if os.environ.get("GITHUB_ACTIONS") == "true": + print(f"\n### ⚠️ File `{file_path}` not found") + else: + print(f"\n[!] File {file_path} does not exist.") + continue + + scanner.run_checks() + scanner.report() + + if scanner.vulnerabilities: + any_vulns = True + + if any_vulns: + sys.exit(1) + + +if __name__ == "__main__": + if len(sys.argv) < 2: + print("Usage: python scanner/main.py ...") + sys.exit(1) + + main(sys.argv[1:]) \ No newline at end of file diff --git a/scanner/rules/__init__.py b/scanner/rules/__init__.py new file mode 100644 index 000000000..e69de29bb 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 000000000..1d04ded58 --- /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 000000000..faacceb1e --- /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 000000000..2038991d8 --- /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 000000000..554825603 --- /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 000000000..b9a29c964 --- /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 000000000..dbe62d859 --- /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 000000000..17885d2b3 --- /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 000000000..c914ea78d --- /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 000000000..a220d9c14 --- /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 000000000..c5cb654ec --- /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 000000000..a7c028867 --- /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 From 43c193692460fbf80ce7ec04ed8c6ee2de5af2b9 Mon Sep 17 00:00:00 2001 From: Liana Perry <62174756+lperry022@users.noreply.github.com> Date: Thu, 25 Sep 2025 19:00:57 +1000 Subject: [PATCH 3/3] Remove __pycache__ and init files, keep only OWASP scanner and workflow --- .gitignore | 1 + scanner/__init__.py | 0 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 scanner/rules/__init__.py | 0 .../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 18 files changed, 1 insertion(+) delete mode 100644 scanner/__init__.py 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/__init__.py 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/.gitignore b/.gitignore index e15806414..9789fcd65 100644 --- a/.gitignore +++ b/.gitignore @@ -26,3 +26,4 @@ package-lock.json __pycache__/ *.pyc *.pyo + diff --git a/scanner/__init__.py b/scanner/__init__.py deleted file mode 100644 index e69de29bb..000000000 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 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 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/__init__.py b/scanner/rules/__init__.py deleted file mode 100644 index e69de29bb..000000000 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*(