# webconf_prescan.py — nginx + Apache config misconfiguration pre-scan

Deterministic, line- and block-aware, fully offline pre-scan of
web-server configuration files — `nginx.conf`, included
server/location snippets, `httpd.conf` / `apache2.conf`, `.vhost`
files, `conf.d` / `sites-enabled` drops, and `.htaccess` — for the
directive shapes that repeatedly produce real exposure
(CWE-16: configuration).

ARION service artifact. Stdlib only. Honest scope: this is a
**pre-scan for human review**, not an audit — it flags directive
shapes; it does not model include-file merge order outside the
scanned tree, and a clean report is not proof the vhost is safe.

## Codes

| code | sev | shape |
|---|---|---|
| `NG-ALIAS-TRAV` | HIGH | `alias`/`location` trailing-slash parity break (`location /i { alias /x/; }` → `/i../` escapes), alias inside a regex location, `..` in alias path |
| `NG-SATISFY-ANY` | HIGH/MED | `satisfy any` inside a block also carrying auth/allow/deny vocabulary — one satisfied leg bypasses the rest |
| `NG-DAV` | HIGH/MED | `dav_methods` PUT/DELETE/MKCOL/COPY/MOVE/PROPFIND, unguarded vs guarded |
| `NG-STATUS` | MED/LOW | `stub_status` exposed without internal/auth guard |
| `NG-AUTOINDEX` | MED/LOW | `autoindex on` |
| `NG-SSI` | MED | `ssi on` — `#exec`/`#include` in user-reachable content |
| `NG-PROXY-VAR` | MED | `proxy_pass` with a `$variable` upstream — SSRF-shaped |
| `NG-HEADER-REFLECT` | MED | `add_header`/`return`/`rewrite` reflecting `$http_`/`$arg_`/`$cookie_` values |
| `NG-REDIRECT-VAR` | MED | redirect target built from client-controlled variables |
| `NG-HDR-SPOOF` | MED | `proxy_set_header X-Real-IP/X-Forwarded-*` set from the client's own `$http_` header |
| `NG-HDR-SMUGGLE` | MED | `underscores_in_headers on` / `ignore_invalid_headers off` |
| `NG-SSL-WEAK` | MED | `ssl_protocols` TLSv1/TLSv1.1/SSLv3; weak `ssl_ciphers` tokens |
| `NG-CORS-WILD` | MED/LOW | `Access-Control-Allow-Origin: *` (LOW when scoped to static assets) |
| `NG-BODY-SIZE` | LOW | `client_max_body_size` ≥ 50m |
| `NG-MERGE-SLASHES` | LOW | `merge_slashes off` |
| `NG-IF-LOC` | INFO | `if (…)` inside a location ("if is evil") |
| `AP-OPEN-PROXY` | HIGH | `ProxyRequests On` — open forward proxy |
| `AP-REQUIRE-ALL` | HIGH/LOW | `Require all granted`/`Allow from all` — HIGH on filesystem-root `<Directory />`, LOW elsewhere |
| `AP-OVERRIDE` | HIGH/MED | `AllowOverride All` — HIGH on root dirs |
| `AP-DAV` | HIGH/MED | `Dav On`, unguarded vs guarded |
| `AP-CGI` | HIGH/MED | `Options ExecCGI` / `AddHandler|SetHandler cgi-script` — HIGH under upload/tmp/files/media/userdir paths |
| `AP-STATUS` | MED/LOW | `SetHandler server-status|server-info|balancer-manager` unguarded |
| `AP-INDEXES` | MED/LOW | `Options Indexes` |
| `AP-OPTIONS-ALL` | MED | `Options All` (Indexes+ExecCGI+Includes+MultiViews) |
| `AP-SSI` | MED | `Options Includes` (with `#exec`; `IncludesNOEXEC` silent) |
| `AP-TRACE` | MED | `TraceEnable On` |
| `AP-SSL-WEAK` | MED | `SSLProtocol` weak/`all` without `-TLSv1/-TLSv1.1/-SSLv3`; weak `SSLCipherSuite` tokens |
| `AP-CORS-WILD` | MED/LOW | `Access-Control-Allow-Origin: *` (LOW when FilesMatch-scoped to static assets) |
| `AP-HTACCESS-PHP` | MED | `php_flag engine/display_errors on`, PHP/CGI handler bound in `.htaccess` |
| `AP-TOKENS` | LOW | `ServerTokens Full` / `ServerSignature On` |
| `AP-USERDIR` | LOW | `UserDir enabled/public_html` |
| `AP-LOGLEVEL` | LOW | `LogLevel debug/trace*` |
| `AP-MULTIVIEWS` | LOW | `Options MultiViews` |
| `AP-PROXY-INTERNAL` | LOW | `ProxyPass`/`RewriteRule [P]` to loopback/private targets |

## Guard model

Exposure findings are evaluated against the whole text of their
enclosing blocks — `location`/`server`/`http` for nginx,
`<Directory>`/`<Location>`/`<VirtualHost>`/`<FilesMatch>` for
Apache — **excluding sibling subtrees** (a guard inside one
location never leaks into the evaluation of another via shared
parents). `auth_basic` / `AuthType` / `Require valid-user` /
`deny all` / `allow <private-net>` / `internal;` / `satisfy all`
present in an enclosing block downgrades or silences the finding.
Comments (`#` outside quotes) are stripped; test/fixture paths
downgrade one tier (INFO drops).

## Live calibration

- nginx upstream `conf/nginx.conf` (115 ln): **0 findings**
- h5bp `server-configs-nginx` (192 ln): **0 findings**
- Apache httpd trunk default `httpd.conf` (418 ln): **1 MEDIUM**
  (`Options Indexes` on the docroot — genuinely ships enabled) +
  2 LOW (`Require all granted` docroot/cgi-bin notes)
- h5bp `server-configs-apache` `.htaccess` (1,292 ln): **2 LOW**
  (wildcard CORS scoped to static font/image `FilesMatch` —
  informational, correctly not MEDIUM)
- Hostile fixture (this directory, `sample-repo/`): every class
  fires at its tier; the hardened sibling produces only
  guarded-MEDIUM + LOW notes.

## Usage

```
python3 webconf_prescan.py <file-or-dir> [--json]
python3 webconf_prescan.py --selftest     # 64 vectors
```

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

Built by **ARION** (autonomous agent; machine-produced,
self-verified). Pre-scan, not an audit.
