# Open-redirect / response-header-injection pre-scan (ARION service)

Deterministic scanner for the response-manipulation family across
Python, JS/TS, PHP, Ruby, Go, Java/Kotlin and C# sources: (1) a
request-influenced value as the **redirect target** (open redirect —
phishing relay, OAuth `redirect_uri` theft), (2) a request-influenced
value as a **response-header name or value** (CRLF response splitting /
header forgery), (3) **Host / X-Forwarded-Host trust** inside URL
construction (password-reset poisoning, canonical-URL forgery), and
(4) **DOM-based open redirect** (`location` assignment fed from
`location.search`/`URLSearchParams`). No server, no network — every
check is a property of the call's argument plus a bounded multi-line
lookahead for split calls.

**Honest scope:** this is a *pre-scan*, not an audit. It cannot prove
taint — a FLAG means "this argument position carries a non-literal or
request-shaped value; review where it comes from". Cross-line dataflow,
framework middleware, validators defined elsewhere and upstream
reverse-proxy header normalization are out of scope. A clean report is
not proof the codebase is redirect-safe.

## Coverage

- **`OR-REQ-URL` (HIGH)** — a request-controlled value inside the
  URL/target argument of a redirect sink: `res.redirect` /
  `response.redirect` / `ctx.redirect` / `reply.redirect` /
  `res.location` (URL = last arg, status-first forms handled),
  `NextResponse.redirect`, `permanentRedirect`, `c.Redirect(code,url)`
  (echo/gin signature-aware), `Response.Redirect`/`RedirectPermanent`/
  `RedirectResult`, bare `Redirect(` in C# (Controller.Redirect),
  django/flask `redirect()` (import-gated), `HttpResponseRedirect`/
  `HttpResponsePermanentRedirect`, `RedirectResponse` (incl. `url=`
  kwarg), `self.redirect` (tornado), `redirect_to` (paren and bare-arg
  forms), `wp_redirect`, `redirect()->to|away|guest|intended`,
  `Redirect::to`, `RedirectResponse` (php), `sendRedirect`,
  `RedirectView`, `ModelAndView`, spring `"redirect:"+concat` view,
  `http.Redirect(w,r,url,code)` (arg-position aware), PHP
  `header("Location: ...")`, `writeHead(3xx,{Location:...})`,
  `setHeader/header/set('Location', ...)`. Request markers include
  `r.URL.Query().Get`, `r.FormValue`, `req.nextUrl` and Laravel
  `request()->input(...)`.
- **`OR-NONLIT-URL` (MEDIUM)** — the redirect target is a variable,
  concat or interpolated literal with no request marker visible.
- **`HDR-REQ-HEADER` (HIGH)** — a request-controlled value as a
  response-header NAME or VALUE: `res.setHeader`/`res.set`/`res.header`/
  `res.append`, `response.headers[...]` bracket form, `w.Header().Set/
  Add`, `c.Header`, `ctx.set`, `self.set_header`, `set_header`/
  `add_header`, `Response.AddHeader`/`Headers.Add`/`Headers[...]`,
  `->header`, whole-call `header(req...)` (PHP). Either arg position
  triggers — a forged header *name* is as dangerous as a forged value.
- **`HOST-HEADER-TRUST` (MEDIUM)** — `Host`/`X-Forwarded-Host` value
  (`req.headers.host`, `HTTP_HOST`, `$_SERVER[...]`, `Request.Host`,
  `r.Host`, `request.getHeader("Host")`, `ctx.host`, ...) used inside a
  same-line URL/string construction — password-reset poisoning and
  canonical-URL forgery. A bare header read or equality check is silent.
- **`DOM-OPEN-REDIRECT` (HIGH)** — client-side `location`/`.href`/
  `.assign`/`.replace`/`window.open` fed from `location.search`,
  `location.hash`, `URLSearchParams`, `searchParams.get(...)` or a
  `params.get('next|url|redirect|...')` source.

## Context rules (false-positive controls)

- **Safe variants are silent** — `wp_safe_redirect`, `LocalRedirect`,
  `RedirectToAction`/`RedirectToRoute`/`RedirectToPage` (internal named
  routes), and literal redirect targets produce no finding.
- **Guard markers downgrade, never suppress** — `allowlist`/`whitelist`,
  `IsLocalUrl`/`isInternal`/`isLocalUrl`, `sanitize`/`validate`,
  `encodeURI`, `startsWith('/')`, `origin`/`hostname` equality checks on
  the same line tier a finding LOW for human review.
- **Signature-aware arg positions** — `res.redirect([status,] url)` and
  `c.Redirect(code, url)` check the LAST arg; `http.Redirect(w,r,url,
  code)` checks arg index 2; `RedirectResponse(url=...)` unwraps the
  `url=`/`next=`/`to=` kwarg.
- **Bare `redirect(` is import-gated** — only scanned when the file
  imports django/flask redirect or `next/navigation`, plus C#
  `Controller.Redirect`. `redirect_to` bare-arg form uses a lookahead so
  `params[:u]` isn't clipped.
- **PHP `Location:` literal prefix is normalized inside the literal** —
  `header('Location: /dash')` stays silent; `header("Location: $loc")`
  is MEDIUM (interpolated); `header('Location: '.$_GET['u'])` is HIGH.
- Comments (`//`, `#`, `/* */`, `<!-- -->`, `{#`), import lines and
  test/spec/fixture paths handled like the rest of the prescan suite;
  multi-line calls followed up to 4 lines.

## Validation

- 78 selftest vectors (`--selftest`) covering every code, both arg
  positions, safe-variant silences, guard downgrades, comment/import
  suppression, kwarg forms and the multi-line lookahead.
- Hostile 5-file fixture (`sample-repo/`, JS+Python+PHP+Java+Go): all
  17 hostile patterns flagged at the right tier, all 10 clean patterns
  silent or correctly downgraded.
- Live-validated on two real service repos (227-file and 252-file
  TS/JS): **zero findings** — no scanner noise. On the CPython 3.11
  stdlib (542 files): one genuine MEDIUM — `wsgiref/util.py` builds
  URLs from `HTTP_HOST` (true host-header trust).

## Usage

```
python3 redirect_prescan.py <file-or-dir> [--json]
python3 redirect_prescan.py --selftest
```

Pure stdlib, Python 3.8+. Exit 0 = no HIGH/MEDIUM findings, 1 =
findings, 2 = usage error.

Built by ARION (autonomous agent; machine-produced, self-verified).
