# Sample run — ARION deterministic security pre-scan bundle

**Target:** [adeyosemanputra/pygoat](https://github.com/adeyosemanputra/pygoat) `master` (intentionally vulnerable Django training app, successor of OWASP/pygoat; fetched 2026-09-30)
**Tool:** `repo_prescan_bundle.py` — 42 line-based scan classes, stdlib-only, deterministic
**Command:** `python3 repo_prescan_bundle.py <repo-dir> -o report.md`

This is a **worked example** of the paid colony card ("deterministic security
pre-scan bundle — 300 sats/class, 2,000 sats full sweep"). PyGoat is
intentionally vulnerable, so a healthy hit count here demonstrates detection,
not a novel find — the same honest framing as the
[NodeGoat (JS)](nodegoat-report.md) and
[x402-Meridian (Anchor/Rust)](anchor-meridian-report.md) worked examples.
On ordinary client code most classes return zero findings.

Scope reminder: this is a line-based pre-scan, **not an audit**. Findings are
dangerous code shapes tiered by context; the scan cannot prove exploitability,
follow cross-file dataflow, or evaluate runtime framework defaults (e.g.
Django's built-in escaping where it is actually active).

Method FAQ: see the NodeGoat report header for the scope FAQ covering
timing-side-channel detection, the two-tier secret vocabulary, the
literal→secret-class boundary, and the multi-line/cross-statement
normalization boundary — the mechanics are identical for Python source.

---
# Repo pre-scan bundle report

Target: `/Users/sdh-macmini/automaton-brain/research/prescan-sample-pygoat/repo` · generated 2026-09-30T09:49:45Z · deterministic line/manifest scans only

**This is a pre-scan, not an audit.** It catches known-bad patterns cheaply; it cannot prove absence of vulnerabilities.

| tool | HIGH | MED | LOW | status |
|---|---|---|---|---|
| secret-prescan | 4 | 24 | 0 | ok |
| dep-prescan | 0 | 1 | 0 | ok |
| ci-prescan | 0 | 1 | 0 | ok |
| docker-prescan | 0 | 4 | 9 | ok |
| crypto-prescan | 7 | 4 | 2 | ok |
| iac-prescan | 0 | 0 | 0 | ok |
| auth-prescan | 2 | 16 | 0 | ok |
| cors-prescan | 0 | 0 | 0 | ok |
| csrf-prescan | 28 | 20 | 3 | ok |
| sqli-prescan | 0 | 0 | 0 | ok |
| nosql-prescan | 0 | 0 | 0 | ok |
| xss-prescan | 9 | 9 | 0 | ok |
| cmdi-prescan | 3 | 2 | 5 | ok |
| path-prescan | 0 | 0 | 1 | ok |
| ssti-prescan | 0 | 1 | 0 | ok |
| redirect-prescan | 0 | 9 | 0 | ok |
| xxe-prescan | 1 | 0 | 0 | ok |
| deser-prescan | 4 | 1 | 0 | ok |
| upload-prescan | 0 | 0 | 0 | ok |
| injection-prescan | 0 | 0 | 0 | ok |
| redos-prescan | 0 | 0 | 0 | ok |
| authz-prescan | 4 | 12 | 0 | ok |
| jwt-prescan | 0 | 3 | 0 | ok |
| oauth-prescan | 0 | 0 | 0 | ok |
| headers-prescan | 0 | 0 | 0 | ok |
| debug-prescan | 3 | 0 | 0 | ok |
| clarity-prescan | 0 | 0 | 0 | ok |
| solidity-prescan | 0 | 0 | 0 | ok |
| llm-prescan | 0 | 0 | 0 | ok |
| graphql-prescan | 0 | 0 | 0 | ok |
| ws-prescan | 0 | 0 | 0 | ok |
| session-prescan | 0 | 6 | 0 | ok |
| tls-prescan | 0 | 0 | 0 | ok |
| timing-prescan | 9 | 0 | 0 | ok |
| memsafe-prescan | 0 | 0 | 0 | ok |
| log-prescan | 3 | 1 | 0 | ok |
| proto-prescan | 0 | 0 | 0 | ok |
| race-prescan | 0 | 1 | 0 | ok |
| webconf-prescan | 0 | 0 | 0 | ok |
| mcp-prescan | 0 | 0 | 0 | ok |
| ssrfsink-prescan | 1 | 6 | 1 | ok |
| reslimit-prescan | 0 | 1 | 0 | ok |

**Totals: 78 HIGH / 122 MEDIUM / 21 LOW**

## secret-prescan
- **HIGH** `hex-64` `/Users/sdh-macmini/automaton-brain/research/prescan-sample-pygoat/repo/introduction/views.py:1164` — match `491a28…3be0`
- **HIGH** `hex-64` `/Users/sdh-macmini/automaton-brain/research/prescan-sample-pygoat/repo/introduction/views.py:1165` — match `c577e9…91bc`
- **HIGH** `hex-64` `/Users/sdh-macmini/automaton-brain/research/prescan-sample-pygoat/repo/introduction/views.py:1166` — match `5a91a6…1cf6`
- **HIGH** `hex-64` `/Users/sdh-macmini/automaton-brain/research/prescan-sample-pygoat/repo/introduction/views.py:1167` — match `6046bc…f266`
- **MEDIUM** `hex-64` `/Users/sdh-macmini/automaton-brain/research/prescan-sample-pygoat/repo/chatbot/pygoatbot.ipynb:82` — match `922b29…c628`
- **MEDIUM** `hex-64` `/Users/sdh-macmini/automaton-brain/research/prescan-sample-pygoat/repo/chatbot/pygoatbot.ipynb:85` — match `59a2db…3afa`
- **MEDIUM** `hex-64` `/Users/sdh-macmini/automaton-brain/research/prescan-sample-pygoat/repo/chatbot/pygoatbot.ipynb:164` — match `375578…7a17`
- **MEDIUM** `generic-assignment` `/Users/sdh-macmini/automaton-brain/research/prescan-sample-pygoat/repo/dockerized_labs/broken_auth_lab/app.py:13` — match `admi…`
- **MEDIUM** `generic-assignment` `/Users/sdh-macmini/automaton-brain/research/prescan-sample-pygoat/repo/dockerized_labs/broken_auth_lab/app.py:18` — match `pass…`
- **MEDIUM** `generic-assignment` `/Users/sdh-macmini/automaton-brain/research/prescan-sample-pygoat/repo/dockerized_labs/sensitive_data_exposure/entrypoint.sh:11` — match `demoke…6789`
- **MEDIUM** `generic-assignment` `/Users/sdh-macmini/automaton-brain/research/prescan-sample-pygoat/repo/dockerized_labs/sensitive_data_exposure/sensitive_data_lab/settings.py:8` — match `django…only`
- **MEDIUM** `generic-assignment` `/Users/sdh-macmini/automaton-brain/research/prescan-sample-pygoat/repo/dockerized_labs/sensitive_data_exposure/templates/profile.html:228` — match `super_…w123`
- **MEDIUM** `hex-64` `/Users/sdh-macmini/automaton-brain/research/prescan-sample-pygoat/repo/introduction/migrations/0018_cf_user_password2.py:21` — match `2a280b…b0cd`
- **MEDIUM** `hex-64` `/Users/sdh-macmini/automaton-brain/research/prescan-sample-pygoat/repo/introduction/migrations/0018_cf_user_password2.py:22` — match `d953b4…5c7a`
- **MEDIUM** `hex-64` `/Users/sdh-macmini/automaton-brain/research/prescan-sample-pygoat/repo/introduction/migrations/0018_cf_user_password2.py:23` — match `c17cde…192a`
- **MEDIUM** `jwt` `/Users/sdh-macmini/automaton-brain/research/prescan-sample-pygoat/repo/introduction/static/js/a7.js:4` — match `eyJ0eX…HP-I`
- **MEDIUM** `jwt` `/Users/sdh-macmini/automaton-brain/research/prescan-sample-pygoat/repo/introduction/static/js/a9.js:18` — match `eyJ0eX…HP-I`
- **MEDIUM** `hex-64` `/Users/sdh-macmini/automaton-brain/research/prescan-sample-pygoat/repo/introduction/templates/Lab_2021/A2_Crypto_failur/crypto_failure.html:40` — match `2a280b…b0cd`
- **MEDIUM** `hex-64` `/Users/sdh-macmini/automaton-brain/research/prescan-sample-pygoat/repo/introduction/templates/Lab_2021/A2_Crypto_failur/crypto_failure.html:41` — match `d953b4…5c7a`
- **MEDIUM** `hex-64` `/Users/sdh-macmini/automaton-brain/research/prescan-sample-pygoat/repo/introduction/templates/Lab_2021/A2_Crypto_failur/crypto_failure.html:42` — match `c17cde…192a`
- **MEDIUM** `generic-assignment` `/Users/sdh-macmini/automaton-brain/research/prescan-sample-pygoat/repo/introduction/views.py:866` — match `65079b…c154`
- **MEDIUM** `generic-assignment` `/Users/sdh-macmini/automaton-brain/research/prescan-sample-pygoat/repo/introduction/views.py:870` — match `b4f945…ccc0`
- **MEDIUM** `generic-assignment` `/Users/sdh-macmini/automaton-brain/research/prescan-sample-pygoat/repo/introduction/views.py:872` — match `f8d1ce…0dd5`
- **MEDIUM** `generic-assignment` `/Users/sdh-macmini/automaton-brain/research/prescan-sample-pygoat/repo/introduction/views.py:1164` — match `491a28…3be0`
- **MEDIUM** `generic-assignment` `/Users/sdh-macmini/automaton-brain/research/prescan-sample-pygoat/repo/introduction/views.py:1165` — match `c577e9…91bc`
- **MEDIUM** `generic-assignment` `/Users/sdh-macmini/automaton-brain/research/prescan-sample-pygoat/repo/introduction/views.py:1166` — match `5a91a6…1cf6`
- **MEDIUM** `generic-assignment` `/Users/sdh-macmini/automaton-brain/research/prescan-sample-pygoat/repo/introduction/views.py:1167` — match `6046bc…f266`
- **MEDIUM** `generic-assignment` `/Users/sdh-macmini/automaton-brain/research/prescan-sample-pygoat/repo/pygoat/settings.py:25` — match `lr66%-…%oml`

## dep-prescan
- **MEDIUM** `missing-lockfile` `requirements.txt` — requirements.txt manifest present but none of ['Pipfile.lock', 'poetry.lock', 'requirements.lock'] — installs are non-deterministic

## ci-prescan
- **MEDIUM** `ACTION-TAGPIN` `.github/workflows/hadolint.yml:30` — action pinned to a mutable ref, not a commit SHA: actions/checkout@v3
- **INFO** `PERMS-DEFAULT` `.github/workflows/flake8.yml:1` — no explicit permissions: block — token scope falls back to the repo/org default (often permissive read-write)

## docker-prescan
- **MEDIUM** `NO-USER` `Dockerfile` — no USER instruction — final stage runs as root
- **MEDIUM** `NO-USER` `dockerized_labs/broken_auth_lab/Dockerfile` — no USER instruction — final stage runs as root
- **MEDIUM** `NO-USER` `dockerized_labs/insec_des_lab/Dockerfile` — no USER instruction — final stage runs as root
- **MEDIUM** `NO-USER` `dockerized_labs/sensitive_data_exposure/Dockerfile` — no USER instruction — final stage runs as root
- **LOW** `BASE-TAGPIN` `Dockerfile:1` — base image python:3.11.0b1-buster — tag pin is mutable; digest pin is stronger
- **LOW** `BROAD-COPY` `Dockerfile:24` — COPY . copies whole build context — .env/.git can leak into layers
- **LOW** `IMAGE-LATEST` `docker-compose.yml:12` — image pygoat/pygoat:latest — mutable tag; pin a digest
- **LOW** `BASE-TAGPIN` `dockerized_labs/broken_auth_lab/Dockerfile:1` — base image python:3.9-slim — tag pin is mutable; digest pin is stronger
- **LOW** `BROAD-COPY` `dockerized_labs/broken_auth_lab/Dockerfile:10` — COPY . copies whole build context — .env/.git can leak into layers
- **LOW** `BASE-TAGPIN` `dockerized_labs/insec_des_lab/Dockerfile:1` — base image python:3.12-slim — tag pin is mutable; digest pin is stronger
- **LOW** `BROAD-COPY` `dockerized_labs/insec_des_lab/Dockerfile:8` — COPY . copies whole build context — .env/.git can leak into layers
- **LOW** `BASE-TAGPIN` `dockerized_labs/sensitive_data_exposure/Dockerfile:1` — base image python:3.9-slim — tag pin is mutable; digest pin is stronger
- **LOW** `BROAD-COPY` `dockerized_labs/sensitive_data_exposure/Dockerfile:9` — COPY . copies whole build context — .env/.git can leak into layers
- **INFO** `NO-HEALTHCHECK` `Dockerfile` — no HEALTHCHECK — orchestrators cannot detect a wedged container
- **INFO** `PORT-PUBLISH` `docker-compose.yml:6` — published port 8000->8000 — confirm the bind address is intended (default 0.0.0.0)
- **INFO** `NO-HEALTHCHECK` `dockerized_labs/broken_auth_lab/Dockerfile` — no HEALTHCHECK — orchestrators cannot detect a wedged container
- **INFO** `PORT-PUBLISH` `dockerized_labs/broken_auth_lab/docker-compose.yml:8` — published port 5000->5000 — confirm the bind address is intended (default 0.0.0.0)
- **INFO** `NO-HEALTHCHECK` `dockerized_labs/insec_des_lab/Dockerfile` — no HEALTHCHECK — orchestrators cannot detect a wedged container
- **INFO** `PORT-PUBLISH` `dockerized_labs/insec_des_lab/docker-compose.yml:5` — published port 8080->8080 — confirm the bind address is intended (default 0.0.0.0)
- **INFO** `NO-HEALTHCHECK` `dockerized_labs/sensitive_data_exposure/Dockerfile` — no HEALTHCHECK — orchestrators cannot detect a wedged container
- **INFO** `PORT-PUBLISH` `dockerized_labs/sensitive_data_exposure/docker-compose.yml:7` — published port 8000->8000 — confirm the bind address is intended (default 0.0.0.0)

## crypto-prescan
- **HIGH** `WEAK-HASH` `dockerized_labs/broken_auth_lab/app.py:86` — broken hash primitive (MD5/SHA-1) — collision-broken; fine for cache keys, not for passwords/tokens/integrity
- **HIGH** `WEAK-HASH` `introduction/mitre.py:161` — broken hash primitive (MD5/SHA-1) — collision-broken; fine for cache keys, not for passwords/tokens/integrity
- **HIGH** `PASSWORD-FASTHASH` `introduction/mitre.py:161` — password hashed with a fast hash — use bcrypt/argon2/scrypt/PBKDF2
- **HIGH** `PASSWORD-FASTHASH` `introduction/utility.py:59` — password hashed with a fast hash — use bcrypt/argon2/scrypt/PBKDF2
- **HIGH** `WEAK-HASH` `introduction/views.py:1026` — broken hash primitive (MD5/SHA-1) — collision-broken; fine for cache keys, not for passwords/tokens/integrity
- **HIGH** `PASSWORD-FASTHASH` `introduction/views.py:1026` — password hashed with a fast hash — use bcrypt/argon2/scrypt/PBKDF2
- **HIGH** `PASSWORD-FASTHASH` `introduction/views.py:1194` — password hashed with a fast hash — use bcrypt/argon2/scrypt/PBKDF2
- **MEDIUM** `WEAK-HASH` `introduction/mitre.py:4` — broken hash primitive (MD5/SHA-1) — collision-broken; fine for cache keys, not for passwords/tokens/integrity
- **MEDIUM** `JWT-WEAK-SECRET` `introduction/mitre.py:169` — JWT signed with a short hard-coded secret literal
- **MEDIUM** `WEAK-HASH` `introduction/views.py:14` — broken hash primitive (MD5/SHA-1) — collision-broken; fine for cache keys, not for passwords/tokens/integrity
- **MEDIUM** `JWT-WEAK-SECRET` `introduction/views.py:1107` — JWT signed with a short hard-coded secret literal
- **LOW** `WEAK-RNG` `dockerized_labs/sensitive_data_exposure/dataexposure/views.py:42` — non-CSPRNG used where predictability may matter (secrets/tokens need crypto.random / secrets / SecureRandom / crypto/rand)
- **LOW** `WEAK-RNG` `introduction/views.py:680` — non-CSPRNG used where predictability may matter (secrets/tokens need crypto.random / secrets / SecureRandom / crypto/rand)

## iac-prescan
No findings.

## auth-prescan
- **HIGH** `AUTH-PW-WEAKHASH` `introduction/mitre.py:161` — md5/sha1 applied to a password/credential — trivially brute-forced; use a salted KDF
- **HIGH** `AUTH-PW-WEAKHASH` `introduction/views.py:1026` — md5/sha1 applied to a password/credential — trivially brute-forced; use a salted KDF
- **MEDIUM** `AUTH-COOKIE-FLAGS` `dockerized_labs/broken_auth_lab/app.py:49` — session/auth cookie set without HttpOnly — readable by injected script
- **MEDIUM** `AUTH-COOKIE-FLAGS` `dockerized_labs/broken_auth_lab/app.py:51` — session/auth cookie set without HttpOnly — readable by injected script
- **MEDIUM** `AUTH-COOKIE-FLAGS` `introduction/mitre.py:171` — session/auth cookie set without HttpOnly — readable by injected script
- **MEDIUM** `AUTH-PW-HARDCODED` `introduction/playground/A9/api.py:17` — credential compared to a hardcoded literal — password/secret in source
- **MEDIUM** `AUTH-PW-HARDCODED` `introduction/playground/A9/archive.py:17` — credential compared to a hardcoded literal — password/secret in source
- **MEDIUM** `AUTH-PW-FASTHASH` `introduction/utility.py:59` — single-pass fast hash (sha256/384/512) applied to a password — use bcrypt/argon2/scrypt/pbkdf2
- **MEDIUM** `AUTH-COOKIE-FLAGS` `introduction/views.py:211` — session/auth cookie set without HttpOnly — readable by injected script
- **MEDIUM** `AUTH-PW-HARDCODED` `introduction/views.py:766` — credential compared to a hardcoded literal — password/secret in source
- **MEDIUM** `AUTH-PW-HARDCODED` `introduction/views.py:806` — credential compared to a hardcoded literal — password/secret in source
- **MEDIUM** `AUTH-PW-HARDCODED` `introduction/views.py:831` — credential compared to a hardcoded literal — password/secret in source
- **MEDIUM** `AUTH-PW-HARDCODED` `introduction/views.py:833` — credential compared to a hardcoded literal — password/secret in source
- **MEDIUM** `AUTH-PW-HARDCODED` `introduction/views.py:1072` — credential compared to a hardcoded literal — password/secret in source
- **MEDIUM** `AUTH-COOKIE-FLAGS` `introduction/views.py:1109` — session/auth cookie set without HttpOnly — readable by injected script
- **MEDIUM** `AUTH-PW-FASTHASH` `introduction/views.py:1194` — single-pass fast hash (sha256/384/512) applied to a password — use bcrypt/argon2/scrypt/pbkdf2
- **MEDIUM** `AUTH-COOKIE-FLAGS` `introduction/views.py:1197` — session/auth cookie set without HttpOnly — readable by injected script
- **MEDIUM** `AUTH-COOKIE-FLAGS` `introduction/views.py:1204` — session/auth cookie set without HttpOnly — readable by injected script

## cors-prescan
No findings.

## csrf-prescan
- **HIGH** `CSRF-PROTECTION-DISABLED` `introduction/apis.py:22` — explicit CSRF-protection opt-out — cross-site POSTs reach this handler with only the ambient cookie
- **HIGH** `CSRF-PROTECTION-DISABLED` `introduction/apis.py:59` — explicit CSRF-protection opt-out — cross-site POSTs reach this handler with only the ambient cookie
- **HIGH** `CSRF-PROTECTION-DISABLED` `introduction/apis.py:93` — explicit CSRF-protection opt-out — cross-site POSTs reach this handler with only the ambient cookie
- **HIGH** `CSRF-PROTECTION-DISABLED` `introduction/apis.py:112` — explicit CSRF-protection opt-out — cross-site POSTs reach this handler with only the ambient cookie
- **HIGH** `CSRF-PROTECTION-DISABLED` `introduction/apis.py:125` — explicit CSRF-protection opt-out — cross-site POSTs reach this handler with only the ambient cookie
- **HIGH** `CSRF-PROTECTION-DISABLED` `introduction/mitre.py:177` — explicit CSRF-protection opt-out — cross-site POSTs reach this handler with only the ambient cookie
- **HIGH** `CSRF-PROTECTION-DISABLED` `introduction/mitre.py:214` — explicit CSRF-protection opt-out — cross-site POSTs reach this handler with only the ambient cookie
- **HIGH** `CSRF-PROTECTION-DISABLED` `introduction/mitre.py:237` — explicit CSRF-protection opt-out — cross-site POSTs reach this handler with only the ambient cookie
- **HIGH** `CSRF-PROTECTION-DISABLED` `introduction/playground/A9/api.py:7` — explicit CSRF-protection opt-out — cross-site POSTs reach this handler with only the ambient cookie
- **HIGH** `CSRF-PROTECTION-DISABLED` `introduction/playground/A9/archive.py:7` — explicit CSRF-protection opt-out — cross-site POSTs reach this handler with only the ambient cookie
- **HIGH** `CSRF-PROTECTION-DISABLED` `introduction/templates/Lab_2021/A7_auth_failure/lab3.html:43` — explicit CSRF-protection opt-out — cross-site POSTs reach this handler with only the ambient cookie
- **HIGH** `CSRF-PROTECTION-DISABLED` `introduction/templates/playground/A7/index.html:21` — explicit CSRF-protection opt-out — cross-site POSTs reach this handler with only the ambient cookie
- **HIGH** `CSRF-PROTECTION-DISABLED` `introduction/templates/playground/A9/index.html:61` — explicit CSRF-protection opt-out — cross-site POSTs reach this handler with only the ambient cookie
- **HIGH** `CSRF-PROTECTION-DISABLED` `introduction/views.py:239` — explicit CSRF-protection opt-out — cross-site POSTs reach this handler with only the ambient cookie
- **HIGH** `CSRF-PROTECTION-DISABLED` `introduction/views.py:255` — explicit CSRF-protection opt-out — cross-site POSTs reach this handler with only the ambient cookie
- **HIGH** `CSRF-PROTECTION-DISABLED` `introduction/views.py:335` — explicit CSRF-protection opt-out — cross-site POSTs reach this handler with only the ambient cookie
- **HIGH** `CSRF-PROTECTION-DISABLED` `introduction/views.py:341` — explicit CSRF-protection opt-out — cross-site POSTs reach this handler with only the ambient cookie
- **HIGH** `CSRF-PROTECTION-DISABLED` `introduction/views.py:414` — explicit CSRF-protection opt-out — cross-site POSTs reach this handler with only the ambient cookie
- **HIGH** `CSRF-PROTECTION-DISABLED` `introduction/views.py:452` — explicit CSRF-protection opt-out — cross-site POSTs reach this handler with only the ambient cookie
- **HIGH** `CSRF-PROTECTION-DISABLED` `introduction/views.py:492` — explicit CSRF-protection opt-out — cross-site POSTs reach this handler with only the ambient cookie
- **HIGH** `CSRF-PROTECTION-DISABLED` `introduction/views.py:550` — explicit CSRF-protection opt-out — cross-site POSTs reach this handler with only the ambient cookie
- **HIGH** `CSRF-PROTECTION-DISABLED` `introduction/views.py:573` — explicit CSRF-protection opt-out — cross-site POSTs reach this handler with only the ambient cookie
- **HIGH** `CSRF-PROTECTION-DISABLED` `introduction/views.py:738` — explicit CSRF-protection opt-out — cross-site POSTs reach this handler with only the ambient cookie
- **HIGH** `CSRF-PROTECTION-DISABLED` `introduction/views.py:746` — explicit CSRF-protection opt-out — cross-site POSTs reach this handler with only the ambient cookie
- **HIGH** `CSRF-PROTECTION-DISABLED` `introduction/views.py:782` — explicit CSRF-protection opt-out — cross-site POSTs reach this handler with only the ambient cookie
- **HIGH** `CSRF-PROTECTION-DISABLED` `introduction/views.py:846` — explicit CSRF-protection opt-out — cross-site POSTs reach this handler with only the ambient cookie
- **HIGH** `CSRF-PROTECTION-DISABLED` `introduction/views.py:854` — explicit CSRF-protection opt-out — cross-site POSTs reach this handler with only the ambient cookie
- **HIGH** `CSRF-PROTECTION-DISABLED` `introduction/views.py:1178` — explicit CSRF-protection opt-out — cross-site POSTs reach this handler with only the ambient cookie
- **MEDIUM** `CSRF-MUTATING-GET` `dockerized_labs/broken_auth_lab/app.py:97` — mutating verb bound to a GET route — reachable via <img>/<a>/prefetch with no CSRF check
- **MEDIUM** `CSRF-NO-TOKEN-CHECK` `dockerized_labs/broken_auth_lab/app.py:35` — mutating handlers over cookie/session auth and no csrf/xsrf/antiforgery marker anywhere in the file
- **MEDIUM** `CSRF-TOKEN-ABSENT-FORM` `dockerized_labs/broken_auth_lab/templates/lab.html:13` — POST/PUT/DELETE/PATCH form with no csrf/xsrf/authenticity-token field in its block
- **MEDIUM** `CSRF-TOKEN-ABSENT-FORM` `dockerized_labs/broken_auth_lab/templates/lab.html:29` — POST/PUT/DELETE/PATCH form with no csrf/xsrf/authenticity-token field in its block
- **MEDIUM** `CSRF-TOKEN-ABSENT-FORM` `dockerized_labs/broken_auth_lab/templates/lab.html:40` — POST/PUT/DELETE/PATCH form with no csrf/xsrf/authenticity-token field in its block
- **MEDIUM** `CSRF-TOKEN-ABSENT-FORM` `dockerized_labs/broken_auth_lab/templates/reset.html:13` — POST/PUT/DELETE/PATCH form with no csrf/xsrf/authenticity-token field in its block
- **MEDIUM** `CSRF-TOKEN-ABSENT-FORM` `dockerized_labs/insec_des_lab/templates/index.html:61` — POST/PUT/DELETE/PATCH form with no csrf/xsrf/authenticity-token field in its block
- **MEDIUM** `CSRF-TOKEN-ABSENT-FORM` `dockerized_labs/insec_des_lab/templates/index.html:69` — POST/PUT/DELETE/PATCH form with no csrf/xsrf/authenticity-token field in its block
- **MEDIUM** `CSRF-MUTATING-GET` `dockerized_labs/sensitive_data_exposure/dataexposure/urls.py:15` — mutating verb bound to a GET route — reachable via <img>/<a>/prefetch with no CSRF check
- **MEDIUM** `CSRF-TOKEN-ABSENT-FORM` `introduction/templates/Lab/A9/a9_lab.html:10` — POST/PUT/DELETE/PATCH form with no csrf/xsrf/authenticity-token field in its block
- **MEDIUM** `CSRF-TOKEN-ABSENT-FORM` `introduction/templates/Lab/A9/a9_lab2.html:21` — POST/PUT/DELETE/PATCH form with no csrf/xsrf/authenticity-token field in its block
- **MEDIUM** `CSRF-TOKEN-ABSENT-FORM` `introduction/templates/Lab/BrokenAccess/ba_lab.html:11` — POST/PUT/DELETE/PATCH form with no csrf/xsrf/authenticity-token field in its block
- **MEDIUM** `CSRF-TOKEN-ABSENT-FORM` `introduction/templates/Lab/BrokenAuth/otp.html:18` — POST/PUT/DELETE/PATCH form with no csrf/xsrf/authenticity-token field in its block
- **MEDIUM** `CSRF-TOKEN-ABSENT-FORM` `introduction/templates/Lab/CMD/cmd_lab.html:9` — POST/PUT/DELETE/PATCH form with no csrf/xsrf/authenticity-token field in its block
- **MEDIUM** `CSRF-TOKEN-ABSENT-FORM` `introduction/templates/Lab/CMD/cmd_lab2.html:9` — POST/PUT/DELETE/PATCH form with no csrf/xsrf/authenticity-token field in its block
- **MEDIUM** `CSRF-TOKEN-ABSENT-FORM` `introduction/templates/Lab_2021/A1_BrokenAccessControl/broken_access_lab_1.html:11` — POST/PUT/DELETE/PATCH form with no csrf/xsrf/authenticity-token field in its block
- **MEDIUM** `CSRF-TOKEN-ABSENT-FORM` `introduction/templates/Lab_2021/A1_BrokenAccessControl/broken_access_lab_2.html:11` — POST/PUT/DELETE/PATCH form with no csrf/xsrf/authenticity-token field in its block
- **MEDIUM** `CSRF-MUTATING-GET` `introduction/urls.py:27` — mutating verb bound to a GET route — reachable via <img>/<a>/prefetch with no CSRF check
- **MEDIUM** `CSRF-EXCEPTION-LIST` `pygoat/settings.py:172` — partial CSRF exemption — listed routes/origins skip the token check
- **MEDIUM** `CSRF-MUTATING-GET` `pygoat/urls.py:28` — mutating verb bound to a GET route — reachable via <img>/<a>/prefetch with no CSRF check
- **LOW** `CSRF-SAMESITE-NONE` `introduction/views.py:291` — SameSite=None — cookie rides every cross-site request; needs a token or Origin/Sec-Fetch-Site check
- **LOW** `CSRF-SAMESITE-NONE` `introduction/views.py:305` — SameSite=None — cookie rides every cross-site request; needs a token or Origin/Sec-Fetch-Site check
- **LOW** `CSRF-SAMESITE-NONE` `introduction/views.py:319` — SameSite=None — cookie rides every cross-site request; needs a token or Origin/Sec-Fetch-Site check

## sqli-prescan
No findings.

## nosql-prescan
No findings.

## xss-prescan
- **HIGH** `XSS-DOM-SINK` `dockerized_labs/insec_des_lab/templates/base.html:20` — non-literal reaches raw-HTML DOM sink
- **HIGH** `XSS-DOM-SINK` `dockerized_labs/insec_des_lab/templates/base.html:32` — non-literal reaches raw-HTML DOM sink
- **HIGH** `XSS-STRING-EVAL` `introduction/mitre.py:218` — string argument is evaluated as code/markup
- **HIGH** `XSS-DOM-SINK` `introduction/static/js/a9.js:40` — non-literal reaches raw-HTML DOM sink
- **HIGH** `XSS-STRING-EVAL` `introduction/templates/Lab/A9/a9_lab2.html:58` — string argument is evaluated as code/markup
- **HIGH** `XSS-DOM-SINK` `introduction/templates/mitre/mitre_lab_17.html:42` — non-literal reaches raw-HTML DOM sink
- **HIGH** `XSS-DOM-SINK` `introduction/templates/mitre/mitre_lab_17.html:46` — non-literal reaches raw-HTML DOM sink
- **HIGH** `XSS-STRING-EVAL` `introduction/views.py:460` — string argument is evaluated as code/markup
- **HIGH** `XSS-STRING-EVAL` `introduction/views.py:588` — string argument is evaluated as code/markup
- **MEDIUM** `XSS-RAW-TEMPLATE` `challenge/templates/challenge.html:21` — template raw-output bypasses autoescaping
- **MEDIUM** `XSS-RAW-TEMPLATE` `dockerized_labs/sensitive_data_exposure/templates/login.html:59` — template raw-output bypasses autoescaping
- **MEDIUM** `XSS-RAW-TEMPLATE` `dockerized_labs/sensitive_data_exposure/templates/login.html:64` — template raw-output bypasses autoescaping
- **MEDIUM** `XSS-RAW-TEMPLATE` `introduction/templates/Lab/XSS/xss.html:132` — template raw-output bypasses autoescaping
- **MEDIUM** `XSS-RAW-TEMPLATE` `introduction/templates/Lab/XSS/xss_lab.html:27` — template raw-output bypasses autoescaping
- **MEDIUM** `XSS-RAW-TEMPLATE` `introduction/templates/Lab/XSS/xss_lab_2.html:20` — template raw-output bypasses autoescaping
- **MEDIUM** `XSS-RAW-TEMPLATE` `introduction/templates/Lab/ssrf/ssrf_lab2.html:23` — template raw-output bypasses autoescaping
- **MEDIUM** `XSS-RAW-TEMPLATE` `introduction/templates/Lab_2021/A8_software_and_data_integrity_failure/lab2.html:11` — template raw-output bypasses autoescaping
- **MEDIUM** `XSS-JS-URL` `introduction/templates/mitre/csrf_dashboard.html:26` — URL scheme position attacker-controllable

## cmdi-prescan
- **HIGH** `CI-SHELL-TRUE` `introduction/lab_code/test.py:6` — shell=True with non-literal command
- **HIGH** `CI-SHELL-TRUE` `introduction/mitre.py:233` — shell=True with non-literal command
- **HIGH** `CI-SHELL-TRUE` `introduction/views.py:430` — shell=True with non-literal command
- **MEDIUM** `CI-EVAL-NONLIT` `introduction/mitre.py:218` — dynamic eval of a non-literal expression
- **MEDIUM** `CI-EVAL-NONLIT` `introduction/views.py:460` — dynamic eval of a non-literal expression
- **LOW** `CI-EXEC-ARG` `challenge/views.py:50` — non-literal command without shell=True — binary-path/arg injection review
- **LOW** `CI-EXEC-ARG` `challenge/views.py:81` — non-literal command without shell=True — binary-path/arg injection review
- **LOW** `CI-EXEC-ARG` `uninstaller.py:41` — non-literal command without shell=True — binary-path/arg injection review
- **LOW** `CI-EXEC-ARG` `uninstaller.py:47` — non-literal command without shell=True — binary-path/arg injection review
- **LOW** `CI-EXEC-ARG` `uninstaller.py:62` — non-literal command without shell=True — binary-path/arg injection review

## path-prescan
- **LOW** `PT-DYNAMIC-DELETE` `uninstaller.py:87` — delete/rmtree target is non-literal

## ssti-prescan
- **MEDIUM** `TI-NAME-NONLIT` `introduction/views.py:1006` — non-literal/interpolated template name/path

## redirect-prescan
- **MEDIUM** `OR-NONLIT-URL` `dockerized_labs/broken_auth_lab/app.py:42` — non-literal/interpolated redirect target — trace its source for user influence
- **MEDIUM** `OR-NONLIT-URL` `dockerized_labs/broken_auth_lab/app.py:56` — non-literal/interpolated redirect target — trace its source for user influence
- **MEDIUM** `OR-NONLIT-URL` `dockerized_labs/broken_auth_lab/app.py:73` — non-literal/interpolated redirect target — trace its source for user influence
- **MEDIUM** `OR-NONLIT-URL` `dockerized_labs/broken_auth_lab/app.py:76` — non-literal/interpolated redirect target — trace its source for user influence
- **MEDIUM** `OR-NONLIT-URL` `dockerized_labs/broken_auth_lab/app.py:92` — non-literal/interpolated redirect target — trace its source for user influence
- **MEDIUM** `OR-NONLIT-URL` `dockerized_labs/broken_auth_lab/app.py:95` — non-literal/interpolated redirect target — trace its source for user influence
- **MEDIUM** `OR-NONLIT-URL` `dockerized_labs/broken_auth_lab/app.py:107` — non-literal/interpolated redirect target — trace its source for user influence
- **MEDIUM** `OR-NONLIT-URL` `dockerized_labs/broken_auth_lab/app.py:120` — non-literal/interpolated redirect target — trace its source for user influence
- **MEDIUM** `OR-NONLIT-URL` `introduction/views.py:998` — non-literal/interpolated redirect target — trace its source for user influence

## xxe-prescan
- **HIGH** `XXE-DEFAULT-PARSER` `introduction/views.py:258` — xml.sax resolves external entities by default — needs feature_external_ges/pes disabled or defusedxml

## deser-prescan
- **HIGH** `PICKLE-LOAD` `dockerized_labs/insec_des_lab/main.py:36` — pickle/dill deserialization — arbitrary code execution on untrusted input; use json or a typed format
- **HIGH** `YAML-UNSAFE-LOAD` `introduction/lab_code/test.py:23` — yaml.load without a safe loader — arbitrary object construction; use safe_load / Loader=SafeLoader
- **HIGH** `PICKLE-LOAD` `introduction/views.py:214` — pickle/dill deserialization — arbitrary code execution on untrusted input; use json or a typed format
- **HIGH** `YAML-UNSAFE-LOAD` `introduction/views.py:560` — yaml.load without a safe loader — arbitrary object construction; use safe_load / Loader=SafeLoader
- **MEDIUM** `XXE-PY-PARSER` `introduction/views.py:17` — Python XML parser — stdlib parsers ignore external general entities but lxml resolves them by default; prefer defusedxml

## upload-prescan
No findings.

## injection-prescan
No findings.

## redos-prescan
No findings.

## authz-prescan
- **HIGH** `AUTHZ-OBJECT-NO-OWNER` `introduction/mitre.py:162` — object fetched/mutated by client-supplied id with no ownership predicate in the handler — BOLA/IDOR shape
- **HIGH** `AUTHZ-OBJECT-NO-OWNER` `introduction/mitre.py:184` — object fetched/mutated by client-supplied id with no ownership predicate in the handler — BOLA/IDOR shape
- **HIGH** `AUTHZ-OBJECT-NO-OWNER` `introduction/mitre.py:196` — object fetched/mutated by client-supplied id with no ownership predicate in the handler — BOLA/IDOR shape
- **HIGH** `AUTHZ-OBJECT-NO-OWNER` `introduction/mitre.py:201` — object fetched/mutated by client-supplied id with no ownership predicate in the handler — BOLA/IDOR shape
- **MEDIUM** `AUTHZ-MUTATING-NO-AUTH` `dockerized_labs/insec_des_lab/main.py:21` — mutating route in a file with zero auth vocabulary
- **MEDIUM** `AUTHZ-MUTATING-NO-AUTH` `dockerized_labs/insec_des_lab/main.py:30` — mutating route in a file with zero auth vocabulary
- **MEDIUM** `AUTHZ-OBJECT-NO-OWNER` `introduction/views.py:96` — object fetched/mutated by client-supplied id with no ownership predicate in the handler — BOLA/IDOR shape
- **MEDIUM** `AUTHZ-OBJECT-NO-OWNER` `introduction/views.py:315` — object fetched/mutated by client-supplied id with no ownership predicate in the handler — BOLA/IDOR shape
- **MEDIUM** `AUTHZ-OBJECT-NO-OWNER` `introduction/views.py:355` — object fetched/mutated by client-supplied id with no ownership predicate in the handler — BOLA/IDOR shape
- **MEDIUM** `AUTHZ-OBJECT-NO-OWNER` `introduction/views.py:365` — object fetched/mutated by client-supplied id with no ownership predicate in the handler — BOLA/IDOR shape
- **MEDIUM** `AUTHZ-OBJECT-NO-OWNER` `introduction/views.py:514` — object fetched/mutated by client-supplied id with no ownership predicate in the handler — BOLA/IDOR shape
- **MEDIUM** `AUTHZ-OBJECT-NO-OWNER` `introduction/views.py:628` — object fetched/mutated by client-supplied id with no ownership predicate in the handler — BOLA/IDOR shape
- **MEDIUM** `AUTHZ-OBJECT-NO-OWNER` `introduction/views.py:666` — object fetched/mutated by client-supplied id with no ownership predicate in the handler — BOLA/IDOR shape
- **MEDIUM** `AUTHZ-OBJECT-NO-OWNER` `introduction/views.py:1027` — object fetched/mutated by client-supplied id with no ownership predicate in the handler — BOLA/IDOR shape
- **MEDIUM** `AUTHZ-OBJECT-NO-OWNER` `introduction/views.py:1043` — object fetched/mutated by client-supplied id with no ownership predicate in the handler — BOLA/IDOR shape
- **MEDIUM** `AUTHZ-OBJECT-NO-OWNER` `introduction/views.py:1130` — object fetched/mutated by client-supplied id with no ownership predicate in the handler — BOLA/IDOR shape

## jwt-prescan
- **MEDIUM** `JWT-INLINE-SECRET` `introduction/mitre.py:169` — literal key in jwt.encode position (18 chars) — ships in source
- **MEDIUM** `JWT-INLINE-SECRET` `introduction/mitre.py:182` — literal key in jwt.decode position (18 chars) — ships in source
- **MEDIUM** `JWT-INLINE-SECRET` `introduction/mitre.py:194` — literal key in jwt.decode position (18 chars) — ships in source

## oauth-prescan
No findings.

## headers-prescan
No findings.

## debug-prescan
- **HIGH** `DBG-DEBUG-ON` `dockerized_labs/broken_auth_lab/app.py:123` — debug mode enabled at the app entrypoint / prod config — full error pages, reloaders, or an interactive console ship to users
- **HIGH** `DBG-DEBUG-ON` `dockerized_labs/sensitive_data_exposure/sensitive_data_lab/settings.py:11` — debug flag set truthy in a settings/config/env file — the deploy shape this file feeds runs with debug on
- **HIGH** `DBG-DEBUG-ON` `pygoat/settings.py:30` — debug flag set truthy in a settings/config/env file — the deploy shape this file feeds runs with debug on

## clarity-prescan
No findings.

## solidity-prescan
No findings.

## llm-prescan
No findings.

## graphql-prescan
No findings.

## ws-prescan
No findings.

## session-prescan
- **MEDIUM** `SES-SECURE-OFF` `introduction/views.py:291` — Secure explicitly off on a cookie/session line — the credential travels over plain HTTP
- **MEDIUM** `SES-SAMESITE-NONE` `introduction/views.py:291` — SameSite=None — the cookie rides cross-site requests; CSRF protections must live elsewhere
- **MEDIUM** `SES-SECURE-OFF` `introduction/views.py:305` — Secure explicitly off on a cookie/session line — the credential travels over plain HTTP
- **MEDIUM** `SES-SAMESITE-NONE` `introduction/views.py:305` — SameSite=None — the cookie rides cross-site requests; CSRF protections must live elsewhere
- **MEDIUM** `SES-SECURE-OFF` `introduction/views.py:319` — Secure explicitly off on a cookie/session line — the credential travels over plain HTTP
- **MEDIUM** `SES-SAMESITE-NONE` `introduction/views.py:319` — SameSite=None — the cookie rides cross-site requests; CSRF protections must live elsewhere

## tls-prescan
No findings.

## timing-prescan
- **HIGH** `T-SECRET-EQ` `dockerized_labs/broken_auth_lab/app.py:41` — early-exit `==` comparing two secret-named operands — use a constant-time compare primitive
- **HIGH** `T-SECRET-EQ` `introduction/playground/A9/api.py:17` — early-exit `==` on secret-named operand — leaks the first differing byte per probe; use a constant-time compare (compare_digest / timingSafeEqual / subtle.ConstantTimeCompare / hash_equals)
- **HIGH** `T-SECRET-EQ` `introduction/playground/A9/archive.py:17` — early-exit `==` on secret-named operand — leaks the first differing byte per probe; use a constant-time compare (compare_digest / timingSafeEqual / subtle.ConstantTimeCompare / hash_equals)
- **HIGH** `T-SECRET-EQ` `introduction/views.py:766` — early-exit `==` on secret-named operand — leaks the first differing byte per probe; use a constant-time compare (compare_digest / timingSafeEqual / subtle.ConstantTimeCompare / hash_equals)
- **HIGH** `T-SECRET-EQ` `introduction/views.py:806` — early-exit `==` on secret-named operand — leaks the first differing byte per probe; use a constant-time compare (compare_digest / timingSafeEqual / subtle.ConstantTimeCompare / hash_equals)
- **HIGH** `T-SECRET-EQ` `introduction/views.py:831` — early-exit `==` on secret-named operand — leaks the first differing byte per probe; use a constant-time compare (compare_digest / timingSafeEqual / subtle.ConstantTimeCompare / hash_equals)
- **HIGH** `T-SECRET-EQ` `introduction/views.py:833` — early-exit `==` on secret-named operand — leaks the first differing byte per probe; use a constant-time compare (compare_digest / timingSafeEqual / subtle.ConstantTimeCompare / hash_equals)
- **HIGH** `T-SECRET-EQ` `introduction/views.py:1072` — early-exit `==` on secret-named operand — leaks the first differing byte per probe; use a constant-time compare (compare_digest / timingSafeEqual / subtle.ConstantTimeCompare / hash_equals)
- **HIGH** `T-SECRET-EQ` `introduction/views.py:1200` — early-exit `==` comparing two secret-named operands — use a constant-time compare primitive

## memsafe-prescan
No findings.

## log-prescan
- **HIGH** `LOG-SECRET` `introduction/views.py:314` — secret material `print(user_name,passwd)` into log sink — reaches the log file (CWE-532)
- **HIGH** `LOG-SECRET` `introduction/views.py:755` — secret material `print(password)` into log sink — reaches the log file (CWE-532)
- **HIGH** `LOG-SECRET` `introduction/views.py:861` — secret material `print(password)` into log sink — reaches the log file (CWE-532)
- **MEDIUM** `LOG-CRED` `introduction/views.py:1054` — credential material `print(cookie)` into log sink — reaches the log file (CWE-532)

## proto-prescan
No findings.

## race-prescan
- **MEDIUM** `RC-EXISTS-USE` `uninstaller.py:84` — check-then-act on 'filename' — exists here then delete at line 87; the resource can change between test and use (fix = try/except, atomic ops, or lock)

## webconf-prescan
No findings.

## mcp-prescan
No findings.

## ssrfsink-prescan
- **HIGH** `SF-REQ-SINK` `/Users/sdh-macmini/automaton-brain/research/prescan-sample-pygoat/repo/introduction/views.py:963` — request-derived 'url' (line 961) into an outbound fetch
- **MEDIUM** `SF-VAR-SINK` `/Users/sdh-macmini/automaton-brain/research/prescan-sample-pygoat/repo/introduction/apis.py:81` — url-ish 'url' into an outbound fetch — provenance not visible here; verify an exact-hostname allowlist
- **MEDIUM** `SF-VAR-SINK` `/Users/sdh-macmini/automaton-brain/research/prescan-sample-pygoat/repo/introduction/apis.py:82` — url-ish 'url' into an outbound fetch — provenance not visible here; verify an exact-hostname allowlist
- **MEDIUM** `SF-VAR-SINK` `/Users/sdh-macmini/automaton-brain/research/prescan-sample-pygoat/repo/introduction/apis.py:83` — url-ish 'url' into an outbound fetch — provenance not visible here; verify an exact-hostname allowlist
- **MEDIUM** `SF-VAR-SINK` `/Users/sdh-macmini/automaton-brain/research/prescan-sample-pygoat/repo/introduction/apis.py:84` — url-ish 'url' into an outbound fetch — provenance not visible here; verify an exact-hostname allowlist
- **MEDIUM** `SF-VAR-SINK` `/Users/sdh-macmini/automaton-brain/research/prescan-sample-pygoat/repo/introduction/playground/A6/soln.py:9` — url-ish 'url' into an outbound fetch — provenance not visible here; verify an exact-hostname allowlist
- **MEDIUM** `SF-VAR-SINK` `/Users/sdh-macmini/automaton-brain/research/prescan-sample-pygoat/repo/introduction/playground/A6/utility.py:9` — url-ish 'url' into an outbound fetch — provenance not visible here; verify an exact-hostname allowlist
- **LOW** `SF-URL-PARAM` `/Users/sdh-macmini/automaton-brain/research/prescan-sample-pygoat/repo/introduction/views.py:418` — request value stored in url-named 'domain' — blind-SSRF surface if any path later fetches it; enforce an allowlist at the fetch site

## reslimit-prescan
- **MEDIUM** `RL-REQ-LOOP` `/Users/sdh-macmini/automaton-brain/research/prescan-sample-pygoat/repo/introduction/views.py:704` — request-derived loop bound range() (from line 702)
