# REPRO-2026-00298: Horilla protected_media composed chain: unauthenticated outside-root file read, with necessity controls for each component defect ## Summary Status: published Severity: high CVSS: Unknown CWE: Unknown Type: security Confidence: high ## Identifiers REPRO ID: REPRO-2026-00298 ## Package Name: horilla/horilla-hr Ecosystem: github Affected: Unknown Fixed: Unknown ## Root Cause # Root Cause Analysis: Horilla Protected Media Unauthenticated Outside-Root Read (Composed Chain) ## Summary Horilla HRMS's `protected_media()` view in `base/views.py` composes two independently necessary defects: (1) a **Referer-based authorization bypass** that treats any request whose `Referer` path matches a public page (e.g., `/login`) as authenticated, and (2) an **uncontained `os.path.join()` path traversal** that allows the requested media path to escape `MEDIA_ROOT` via dot-segment traversal (`../`). A single HTTP request carrying no cookie, no session, no JWT, and no bearer token — only a crafted `Referer: http://attacker.invalid/login` header and a traversal path `/media/../canary_outside.txt` — returns the contents of a file stored outside the media directory. This reproduction establishes the composition by showing that removing either defect breaks the chain while the other remains independently exploitable. ## Impact - **Package/component affected**: `base/views.py` — `protected_media()` view, routed via `base/urls.py` as `re_path(r"^media/(?P.*)$", views.protected_media, name="protected_media")`. - **Affected versions**: Horilla HRMS `1.5.0` (commit `61bd5173220d19925ad8220db9152a75c881ea73`). Both defects are present. The traversal fix landed in commit `67ac2056813ee95d4c4a0bfe7c0124a361cb6c48` (2026-02-23) and the authorization fix landed in commit `b6eaec1386d8b8741a42fe7c78f318f073375791` (2026-05-20), both included in tag `1.6.0` (commit `b3bd29d15819cbece45c58e6268ddd0614e387d6`). - **Risk level and consequences**: High. An unauthenticated remote attacker can read arbitrary files accessible to the Horilla process by combining the Referer bypass with the path traversal. Impact is bounded to file read (info_leak); no code execution, privilege escalation, or account takeover is claimed. ## Impact Parity - **Disclosed/claimed maximum impact**: Unauthenticated arbitrary file read (info_leak) via the real `/media/` HTTP route. - **Reproduced impact from this run**: Unauthenticated read of a canary file stored outside `MEDIA_ROOT` via a single HTTP request with `Referer: http://attacker.invalid/login` and path `/media/../canary_outside.txt`. Response body exactly equals the outside-root canary. No credential of any kind was sent. - **Parity**: `full`. - **Not demonstrated**: No code execution, privilege escalation, session forgery, or secret extraction. The oracle is a random canary, not a real secret. ## Root Cause ### Defect 1: Referer Authorization Bypass (GHSA-9wjx-4j4r-ff8w) The `protected_media()` view on `1.5.0` checks whether the URL path extracted from the `Referer` header is in a `public_pages` list: ```python referer_path = urlparse(request.META.get("HTTP_REFERER", "")).path if referer_path not in public_pages and not any( path.startswith(f) for f in exempted_folders ): if not request.user.is_authenticated and not jwt_user: # ... redirect to login ``` Since `/login` is in `public_pages`, any request with `Referer: http://attacker.invalid/login` bypasses the authentication check entirely — regardless of whether the request carries a cookie, session, or token. The attacker controls the `Referer` header, so this is a trivial authorization bypass. ### Defect 2: Uncontained `os.path.join()` Path Traversal (GHSA-x52c-5hrq-76pq) The view constructs the file path with: ```python media_path = os.path.join(settings.MEDIA_ROOT, path) ``` Python's `os.path.join()` does not contain the result within `MEDIA_ROOT`. If `path` contains `..` segments (e.g., `../canary_outside.txt`), the resulting path escapes the media directory. The `os.path.exists()` check and `FileResponse(open(media_path, "rb"))` call then operate on a file outside `MEDIA_ROOT`. ### Composition A single unauthenticated request with both `Referer: http://attacker.invalid/login` and path `/media/../canary_outside.txt`: 1. The Referer bypass skips the authentication gate. 2. The `os.path.join()` traversal escapes `MEDIA_ROOT`. 3. `FileResponse` returns the outside-root file. ### Fixes - **Traversal fix** (commit `67ac2056`): Replaced `os.path.join()` with Django's `safe_join()`, which raises `ValueError` if the path escapes the base directory. Also added `os.path.isfile()` check. - **Authorization fix** (commit `b6eaec13`, included in `1.6.0`): Removed the `Referer`-based authorization entirely. Replaced with `public_media_prefixes` that only exempt specific asset directories (e.g., `base/icon/`), requiring authentication for all other media. ## Reproduction Steps 1. **Script**: `bundle/repro/reproduction_steps.sh` 2. **What the script does**: - Clones (or reuses cached) the Horilla HRMS repository. - For each of three upstream refs (`1.5.0`, `67ac2056`, `1.6.0`), checks out the ref, installs its `requirements.txt`, runs migrations on a fresh SQLite database, creates two random canary files (one outside `MEDIA_ROOT`, one inside at a nonpublic path), and starts the real Django `runserver`. - Sends real HTTP requests via `curl --path-as-is` (preserving dot segments) from a separate attacker process to the running Horilla server. - **1.5.0 (vulnerable oracle)**: Sends the escaping request without Referer (expect 302 denied), with Referer (expect 200 + outside canary), and as an authenticated low-priv user (expect 200 + outside canary). Also sends an in-root request with Referer (expect 200 + in-root canary). - **67ac2056 (containment necessity control)**: Sends the escaping chain request with Referer (expect 404 — safe_join blocks traversal) and an in-root request with Referer (expect 200 + in-root canary — Referer bypass still works). - **1.6.0 (fixed control)**: Sends the chain request with Referer (expect 404), in-root request with Referer (expect 302 — no bypass), and a legitimate authenticated in-root request (expect 200 — service works). - Runs the complete three-ref matrix **twice** from clean targets with new canaries. 3. **Expected evidence**: All tests pass (exit 0). Evidence files under `bundle/repro/evidence/` include request/response captures, HTTP headers, status codes, server logs with server-observed paths, and canary verification for every request. ## Evidence - **Main log**: `bundle/logs/reproduction_steps.log` — complete execution output. - **Run logs**: `bundle/logs/reproduction_run1.log`, `bundle/logs/reproduction_run2.log` (if separate). - **Evidence directory**: `bundle/repro/evidence/run{1,2}/{1.5.0,67ac2056,1.6.0}/` - `request_*.txt` — request metadata (method, URL, path, cookie/auth status, headers) - `response_*.txt` — response body - `headers_*.txt` — response headers - `status_*.txt` — HTTP status code - `server.log` — Django dev server log with server-observed paths - `server_observed_paths.txt` — extracted server-observed paths - `checkout.log` — git checkout and resolved commit - `migrate.log` — migration output - `modified_files.txt` — startup accommodation inventory - `media_root_check.txt` — MEDIA_ROOT and outside-canary path verification - `outside_canary_value.txt` / `inroot_canary_value.txt` — canary values - `create_user.log` — low-priv user creation (is_staff=False, is_superuser=False) - `cookies.txt` — session cookies for authenticated requests - **Runtime manifest**: `bundle/repro/runtime_manifest.json` - **Validation verdict**: `bundle/repro/validation_verdict.json` ### Key evidence excerpts (from initial validation) On `1.5.0`: - `GET /media/../canary_outside.txt` without Referer → HTTP 302 (redirect to login), no canary in body. - `GET /media/../canary_outside.txt` with `Referer: http://attacker.invalid/login` → HTTP 200, body = outside-root canary. - Server log: `"GET /media/../canary_outside.txt HTTP/1.1" 302 0` then `"GET /media/../canary_outside.txt HTTP/1.1" 200 21`. On `67ac2056`: - `GET /media/../canary_outside.txt` with Referer → HTTP 404 (safe_join blocks traversal). - `GET /media/private_inroot/canary_inroot.txt` with Referer → HTTP 200, body = in-root canary. - Server log: `"GET /media/../canary_outside.txt HTTP/1.1" 404 52013` then `"GET /media/private_inroot/canary_inroot.txt HTTP/1.1" 200 47`. On `1.6.0`: - `GET /media/../canary_outside.txt` with Referer → HTTP 404 (safe_join blocks traversal). - `GET /media/private_inroot/canary_inroot.txt` with Referer → HTTP 302 (no Referer bypass, redirect to login). - Authenticated `GET /media/private_inroot/canary_inroot.txt` → HTTP 200, body = in-root canary (service works). - Server log: `"GET /media/../canary_outside.txt HTTP/1.1" 404 52013`, `"GET /media/private_inroot/canary_inroot.txt HTTP/1.1" 302 0`, `"GET /media/private_inroot/canary_inroot.txt HTTP/1.1" 200 47`. ## Recommendations / Next Steps - **Upgrade to 1.6.0 or later**: Both defects are fixed in tag `1.6.0`. The `safe_join()` containment and the removal of the Referer-based authorization together close the composed chain. - **Do not restore Referer-based authorization**: The Referer header is attacker-controlled and must not be used as an authorization mechanism. The `public_media_prefixes` approach in `1.6.0` is the correct pattern. - **Always use `safe_join()` for file path containment**: Never use `os.path.join()` to construct paths that must stay within a base directory. - **Testing recommendations**: Add integration tests that send requests with attacker-controlled `Referer` headers and traversal paths to `protected_media()`. Verify that unauthenticated requests with any `Referer` value are denied for non-public media. ## Additional Notes - **Idempotency**: The script is idempotent — it cleans up generated files (canaries, SQLite DB) before each ref checkout and uses fresh random canaries for each run. Running it multiple times produces the same pass/fail results. - **Canary approach**: The oracle is a random canary file, never a real secret. No configuration files, databases, `/proc` entries, password material, credentials, or employee records are read or retained. - **HTTP client**: `curl --path-as-is` is used to preserve dot segments verbatim. Redirects are not followed automatically (`-L` not used), so 302 responses are recorded as-is. - **Modified files**: The only startup accommodations are creating canary files and a SQLite database, which are untracked data files. No source files (`base/views.py`, `base/urls.py`, routing, middleware, authentication, or file-open behavior) are modified. - **Dependency note**: `PyMuPDF==1.24.5` (pinned in `requirements.txt`) does not build on Python 3.14. A newer `PyMuPDF` with pre-built wheels is installed instead. This does not affect the `protected_media()` code path. - **Out of scope**: The `1.6.0` dot-segment prefix-normalization bypass (tracked by `HORILLA-PROTECTED-MEDIA-PREFIX-NORMALIZATION-BYPASS`) is not tested here and is not used to satisfy or fail any control. ## Reproduction Details Reproduced: 2026-07-27T00:57:45.360Z Duration: 3282 seconds Tool calls: 254 Turns: Unknown Handoffs: 2 ## Quick Verification Run one of these commands to verify locally: pruva-verify REPRO-2026-00298 Or open in GitHub Codespaces (zero-friction, auto-runs): https://github.com/codespaces/new?ref=repro/REPRO-2026-00298&repo=N3mes1s/pruva-sandbox Or download and run the script manually: curl -O https://api.pruva.dev/v1/reproductions/REPRO-2026-00298/artifacts/bundle/repro/reproduction_steps.sh chmod +x reproduction_steps.sh ./reproduction_steps.sh WARNING: Run in a sandboxed environment. This exploits a real vulnerability. ## References - Source: https://github.com/horilla/horilla-hr ## Artifacts - bundle/repro/reproduction_steps.sh (reproduction_script, 21063 bytes) - bundle/repro/rca_report.md (analysis, 10925 bytes) - bundle/repro/runtime_manifest.json (other, 8746 bytes) - bundle/logs/reproduction_steps.log (log, 5413 bytes) - bundle/repro/evidence/run2/1.6.0/checkout.log (log, 356 bytes) - bundle/repro/evidence/run2/1.6.0/cookies.txt (other, 458 bytes) - bundle/repro/evidence/run2/1.6.0/create_user.log (log, 85 bytes) - bundle/repro/evidence/run2/1.6.0/headers_auth_inroot.txt (other, 539 bytes) - bundle/repro/evidence/run2/1.6.0/headers_chain_referer.txt (other, 504 bytes) - bundle/repro/evidence/run2/1.6.0/headers_inroot_referer.txt (other, 747 bytes) - bundle/repro/evidence/run2/1.6.0/inroot_canary_value.txt (other, 46 bytes) - bundle/repro/evidence/run2/1.6.0/login_response.txt (other, 0 bytes) - bundle/repro/evidence/run2/1.6.0/login_status.txt (other, 4 bytes) - bundle/repro/evidence/run2/1.6.0/makemigrations.log (log, 20 bytes) - bundle/repro/evidence/run2/1.6.0/media_root_check.txt (other, 133 bytes) - bundle/repro/evidence/run2/1.6.0/migrate.log (log, 3931 bytes) - bundle/repro/evidence/run2/1.6.0/modified_files.txt (other, 22 bytes) - bundle/repro/evidence/run2/1.6.0/outside_canary_value.txt (other, 52 bytes) - bundle/repro/evidence/run2/1.6.0/pip_install.log (log, 278 bytes) - bundle/repro/evidence/run2/1.6.0/request_auth_inroot.txt (other, 202 bytes) - bundle/repro/evidence/run2/1.6.0/request_chain_referer.txt (other, 229 bytes) - bundle/repro/evidence/run2/1.6.0/request_inroot_referer.txt (other, 247 bytes) - bundle/repro/evidence/run2/1.6.0/response_auth_inroot.txt (other, 47 bytes) - bundle/repro/evidence/run2/1.6.0/response_chain_referer.txt (other, 52013 bytes) - bundle/repro/evidence/run2/1.6.0/response_inroot_referer.txt (other, 0 bytes) - bundle/repro/evidence/run2/1.6.0/server.log (log, 449 bytes) - bundle/repro/evidence/run2/1.6.0/server_observed_paths.txt (other, 125 bytes) - bundle/repro/evidence/run2/1.6.0/status_auth_inroot.txt (other, 4 bytes) - bundle/repro/evidence/run2/1.6.0/status_chain_referer.txt (other, 4 bytes) - bundle/repro/evidence/run2/1.6.0/status_inroot_referer.txt (other, 4 bytes) - bundle/repro/evidence/run2/67ac2056/checkout.log (log, 356 bytes) - bundle/repro/evidence/run2/67ac2056/headers_chain_referer.txt (other, 504 bytes) - bundle/repro/evidence/run2/67ac2056/headers_inroot_referer.txt (other, 539 bytes) - bundle/repro/evidence/run2/67ac2056/inroot_canary_value.txt (other, 46 bytes) - bundle/repro/evidence/run2/67ac2056/makemigrations.log (log, 20 bytes) - bundle/repro/evidence/run2/67ac2056/media_root_check.txt (other, 133 bytes) - bundle/repro/evidence/run2/67ac2056/migrate.log (log, 3931 bytes) - bundle/repro/evidence/run2/67ac2056/modified_files.txt (other, 22 bytes) - bundle/repro/evidence/run2/67ac2056/outside_canary_value.txt (other, 52 bytes) - bundle/repro/evidence/run2/67ac2056/pip_install.log (log, 949 bytes) - bundle/repro/evidence/run2/67ac2056/request_chain_referer.txt (other, 229 bytes) - bundle/repro/evidence/run2/67ac2056/request_inroot_referer.txt (other, 248 bytes) - bundle/repro/evidence/run2/67ac2056/response_chain_referer.txt (other, 52013 bytes) - bundle/repro/evidence/run2/67ac2056/response_inroot_referer.txt (other, 47 bytes) - bundle/repro/evidence/run2/67ac2056/server.log (log, 257 bytes) - bundle/repro/evidence/run2/67ac2056/server_observed_paths.txt (other, 77 bytes) - bundle/repro/evidence/run2/67ac2056/status_chain_referer.txt (other, 4 bytes) - bundle/repro/evidence/run2/67ac2056/status_inroot_referer.txt (other, 4 bytes) - bundle/repro/evidence/run2/1.5.0/checkout.log (log, 182 bytes) - bundle/repro/evidence/run2/1.5.0/cookies.txt (other, 458 bytes) - bundle/repro/evidence/run2/1.5.0/create_user.log (log, 85 bytes) - bundle/repro/evidence/run2/1.5.0/headers_auth_escape.txt (other, 540 bytes) - bundle/repro/evidence/run2/1.5.0/headers_chain_no_referer.txt (other, 747 bytes) - bundle/repro/evidence/run2/1.5.0/headers_chain_with_referer.txt (other, 540 bytes) - bundle/repro/evidence/run2/1.5.0/headers_inroot_referer.txt (other, 539 bytes) - bundle/repro/evidence/run2/1.5.0/inroot_canary_value.txt (other, 46 bytes) - bundle/repro/evidence/run2/1.5.0/login_response.txt (other, 0 bytes) - bundle/repro/evidence/run2/1.5.0/login_status.txt (other, 4 bytes) - bundle/repro/evidence/run2/1.5.0/makemigrations.log (log, 20 bytes) - bundle/repro/evidence/run2/1.5.0/media_root_check.txt (other, 133 bytes) - bundle/repro/evidence/run2/1.5.0/migrate.log (log, 3931 bytes) - bundle/repro/evidence/run2/1.5.0/modified_files.txt (other, 22 bytes) - bundle/repro/evidence/run2/1.5.0/outside_canary_value.txt (other, 52 bytes) - bundle/repro/evidence/run2/1.5.0/pip_install.log (log, 278 bytes) - bundle/repro/evidence/run2/1.5.0/request_auth_escape.txt (other, 180 bytes) - bundle/repro/evidence/run2/1.5.0/request_chain_no_referer.txt (other, 169 bytes) - bundle/repro/evidence/run2/1.5.0/request_chain_with_referer.txt (other, 226 bytes) - bundle/repro/evidence/run2/1.5.0/request_inroot_referer.txt (other, 248 bytes) - bundle/repro/evidence/run2/1.5.0/response_auth_escape.txt (other, 53 bytes) - bundle/repro/evidence/run2/1.5.0/response_chain_no_referer.txt (other, 0 bytes) - bundle/repro/evidence/run2/1.5.0/response_chain_with_referer.txt (other, 53 bytes) - bundle/repro/evidence/run2/1.5.0/response_inroot_referer.txt (other, 47 bytes) - bundle/repro/evidence/run2/1.5.0/server.log (log, 469 bytes) - bundle/repro/evidence/run2/1.5.0/server_observed_paths.txt (other, 143 bytes) - bundle/repro/evidence/run2/1.5.0/status_auth_escape.txt (other, 4 bytes) - bundle/repro/evidence/run2/1.5.0/status_chain_no_referer.txt (other, 4 bytes) - bundle/repro/evidence/run2/1.5.0/status_chain_with_referer.txt (other, 4 bytes) - bundle/repro/evidence/run2/1.5.0/status_inroot_referer.txt (other, 4 bytes) - bundle/repro/evidence/run1/1.6.0/checkout.log (log, 356 bytes) - bundle/repro/evidence/run1/1.6.0/cookies.txt (other, 458 bytes) - bundle/repro/evidence/run1/1.6.0/create_user.log (log, 85 bytes) - bundle/repro/evidence/run1/1.6.0/headers_auth_inroot.txt (other, 539 bytes) - bundle/repro/evidence/run1/1.6.0/headers_chain_referer.txt (other, 504 bytes) - bundle/repro/evidence/run1/1.6.0/headers_inroot_referer.txt (other, 747 bytes) - bundle/repro/evidence/run1/1.6.0/inroot_canary_value.txt (other, 46 bytes) - bundle/repro/evidence/run1/1.6.0/login_response.txt (other, 0 bytes) - bundle/repro/evidence/run1/1.6.0/login_status.txt (other, 4 bytes) - bundle/repro/evidence/run1/1.6.0/makemigrations.log (log, 20 bytes) - bundle/repro/evidence/run1/1.6.0/media_root_check.txt (other, 133 bytes) - bundle/repro/evidence/run1/1.6.0/migrate.log (log, 3931 bytes) - bundle/repro/evidence/run1/1.6.0/modified_files.txt (other, 22 bytes) - bundle/repro/evidence/run1/1.6.0/outside_canary_value.txt (other, 52 bytes) - bundle/repro/evidence/run1/1.6.0/pip_install.log (log, 278 bytes) - bundle/repro/evidence/run1/1.6.0/request_auth_inroot.txt (other, 202 bytes) - bundle/repro/evidence/run1/1.6.0/request_chain_referer.txt (other, 229 bytes) - bundle/repro/evidence/run1/1.6.0/request_inroot_referer.txt (other, 247 bytes) - bundle/repro/evidence/run1/1.6.0/response_auth_inroot.txt (other, 47 bytes) - bundle/repro/evidence/run1/1.6.0/response_chain_referer.txt (other, 52013 bytes) - bundle/repro/evidence/run1/1.6.0/response_inroot_referer.txt (other, 0 bytes) - bundle/repro/evidence/run1/1.6.0/server.log (log, 449 bytes) - bundle/repro/evidence/run1/1.6.0/server_observed_paths.txt (other, 125 bytes) - bundle/repro/evidence/run1/1.6.0/status_auth_inroot.txt (other, 4 bytes) - bundle/repro/evidence/run1/1.6.0/status_chain_referer.txt (other, 4 bytes) - bundle/repro/evidence/run1/1.6.0/status_inroot_referer.txt (other, 4 bytes) - bundle/repro/evidence/run1/67ac2056/checkout.log (log, 356 bytes) - bundle/repro/evidence/run1/67ac2056/headers_chain_referer.txt (other, 504 bytes) - bundle/repro/evidence/run1/67ac2056/headers_inroot_referer.txt (other, 539 bytes) - bundle/repro/evidence/run1/67ac2056/inroot_canary_value.txt (other, 46 bytes) - bundle/repro/evidence/run1/67ac2056/makemigrations.log (log, 20 bytes) - bundle/repro/evidence/run1/67ac2056/media_root_check.txt (other, 133 bytes) - bundle/repro/evidence/run1/67ac2056/migrate.log (log, 3931 bytes) - bundle/repro/evidence/run1/67ac2056/modified_files.txt (other, 22 bytes) - bundle/repro/evidence/run1/67ac2056/outside_canary_value.txt (other, 52 bytes) - bundle/repro/evidence/run1/67ac2056/pip_install.log (log, 949 bytes) - bundle/repro/evidence/run1/67ac2056/request_chain_referer.txt (other, 229 bytes) - bundle/repro/evidence/run1/67ac2056/request_inroot_referer.txt (other, 248 bytes) - bundle/repro/evidence/run1/67ac2056/response_chain_referer.txt (other, 52013 bytes) - bundle/repro/evidence/run1/67ac2056/response_inroot_referer.txt (other, 47 bytes) - bundle/repro/evidence/run1/67ac2056/server.log (log, 257 bytes) - bundle/repro/evidence/run1/67ac2056/server_observed_paths.txt (other, 77 bytes) - bundle/repro/evidence/run1/67ac2056/status_chain_referer.txt (other, 4 bytes) - bundle/repro/evidence/run1/67ac2056/status_inroot_referer.txt (other, 4 bytes) - bundle/repro/evidence/run1/1.5.0/checkout.log (log, 182 bytes) - bundle/repro/evidence/run1/1.5.0/cookies.txt (other, 458 bytes) - bundle/repro/evidence/run1/1.5.0/create_user.log (log, 85 bytes) - bundle/repro/evidence/run1/1.5.0/headers_auth_escape.txt (other, 540 bytes) - bundle/repro/evidence/run1/1.5.0/headers_chain_no_referer.txt (other, 747 bytes) - bundle/repro/evidence/run1/1.5.0/headers_chain_with_referer.txt (other, 540 bytes) - bundle/repro/evidence/run1/1.5.0/headers_inroot_referer.txt (other, 539 bytes) - bundle/repro/evidence/run1/1.5.0/inroot_canary_value.txt (other, 46 bytes) - bundle/repro/evidence/run1/1.5.0/login_response.txt (other, 0 bytes) - bundle/repro/evidence/run1/1.5.0/login_status.txt (other, 4 bytes) - bundle/repro/evidence/run1/1.5.0/makemigrations.log (log, 20 bytes) - bundle/repro/evidence/run1/1.5.0/media_root_check.txt (other, 133 bytes) - bundle/repro/evidence/run1/1.5.0/migrate.log (log, 3931 bytes) - bundle/repro/evidence/run1/1.5.0/modified_files.txt (other, 22 bytes) - bundle/repro/evidence/run1/1.5.0/outside_canary_value.txt (other, 52 bytes) - bundle/repro/evidence/run1/1.5.0/pip_install.log (log, 278 bytes) - bundle/repro/evidence/run1/1.5.0/request_auth_escape.txt (other, 180 bytes) - bundle/repro/evidence/run1/1.5.0/request_chain_no_referer.txt (other, 169 bytes) - bundle/repro/evidence/run1/1.5.0/request_chain_with_referer.txt (other, 226 bytes) - bundle/repro/evidence/run1/1.5.0/request_inroot_referer.txt (other, 248 bytes) - bundle/repro/evidence/run1/1.5.0/response_auth_escape.txt (other, 53 bytes) - bundle/repro/evidence/run1/1.5.0/response_chain_no_referer.txt (other, 0 bytes) - bundle/repro/evidence/run1/1.5.0/response_chain_with_referer.txt (other, 53 bytes) - bundle/repro/evidence/run1/1.5.0/response_inroot_referer.txt (other, 47 bytes) - bundle/repro/evidence/run1/1.5.0/server.log (log, 469 bytes) - bundle/repro/evidence/run1/1.5.0/server_observed_paths.txt (other, 143 bytes) - bundle/repro/evidence/run1/1.5.0/status_auth_escape.txt (other, 4 bytes) - bundle/repro/evidence/run1/1.5.0/status_chain_no_referer.txt (other, 4 bytes) - bundle/repro/evidence/run1/1.5.0/status_chain_with_referer.txt (other, 4 bytes) - bundle/repro/evidence/run1/1.5.0/status_inroot_referer.txt (other, 4 bytes) - bundle/repro/validation_verdict.json (other, 720 bytes) ## API Access - JSON: https://api.pruva.dev/v1/reproductions/REPRO-2026-00298 - Script: https://api.pruva.dev/v1/reproductions/REPRO-2026-00298/artifacts/bundle/repro/reproduction_steps.sh - Web: https://pruva.dev/reproductions/REPRO-2026-00298 ## For AI Assistants To help users with this reproduction: 1. The `pruva-verify` command is the simplest way to run the reproduction 2. The script downloads and runs automatically in an isolated temp directory 3. Always recommend running in a VM, container, or disposable environment 4. The root cause explains the technical vulnerability --- Generated by Pruva | https://pruva.dev