# race_prescan.py — TOCTOU / check-then-act race pre-scan

Deterministic, line-wise, fully offline pre-scan for the CWE-367 shape:
a check (`exists`, `access`, `stat`, `lstat`, `islink`, `File.Exists`,
`[ -e ]`) followed within ~12 lines by an action on the **same**
identifier (`open`, `read`, `write`, `unlink`, `mkdir`, `rename`,
`include`, `>` redirect). Two actors — threads, processes, or requests —
can interleave between the test and the use.

ARION service artifact. Stdlib only. Honest scope: this is a **pre-scan
for human review**, not an audit — it flags shapes, it does not prove a
second writer exists, and a clean report is not a race-free guarantee.

## Codes

| code | sev | shape |
|---|---|---|
| `RC-EXISTS-USE` | MEDIUM | exists/access/stat check then file op on the same identifier |
| `RC-SYMLINK-USE` | MEDIUM | islink/lstat/isSymbolicLink/-L check then use — symlink swap race |
| `RC-LOCKFILE-RACE` | MEDIUM | exists-check then create/write on a lock/pid-named file |
| `RC-DIR-CREATE-RACE` | LOW | exists/isdir check then non-atomic mkdir |
| `RC-DB-CHECK-WRITE` | LOW | read -> `if result` -> insert/save on the same entity (non-atomic uniqueness check) |

## Languages

Python, JS/TS (`.js .jsx .ts .tsx .mjs .cjs`), PHP, Ruby, Go,
Java/Kotlin/Groovy, C#, POSIX shell (`.sh`).

## Suppression & downgrade rules

- **Atomic fixes on the use line suppress the pair entirely** —
  `'x'`/`'wx'`/`O_EXCL` open modes, `exist_ok=True`,
  `{recursive: true}`, `mkdir -p`, `deleteIfExists`,
  `File.createNewFile`, `Directory.CreateDirectory`,
  `Files.createDirectories`, `os.MkdirAll`, `FileUtils.mkdir_p`,
  `set -o noclobber`.
- **Reassignment of the identifier between check and use** breaks the
  pair (fresh binding — not the same race).
- **Lock context** (`with lock`, `.acquire(`, `mutex`, `synchronized`,
  `flock`) within 4 lines before the check or between check and use
  downgrades one tier.
- **Literal-path pairs** downgrade to LOW.
- **Upsert/transaction markers** on the write line
  (`upsert`, `ON CONFLICT`, `INSERT OR IGNORE`, `INSERT IGNORE`,
  `ON DUPLICATE`, `replace_one`, `get_or_create`, `MERGE`) silence the
  DB heuristic.
- Test/spec/fixture paths downgrade one tier.
- Comment lines and `/* */`/`<!-- -->` block bodies are skipped.
- `self`/`this` receiver pairs are ignored; `Path`-receiver checks
  (`p.exists()`, `f.exists()`) require path vocabulary in the file.

## Usage

```sh
python3 race_prescan.py <file-or-dir>          # text report
python3 race_prescan.py <file-or-dir> --json   # JSON findings
python3 race_prescan.py --selftest             # 60-vector fixture suite
```

Exit codes: 0 = no HIGH/MEDIUM findings, 1 = findings, 2 = usage.

## Sample report

`sample-report.md` scans `sample-repo/` — hostile fixtures covering
every code, the atomic-fix suppressions, the reassignment guard, the
lock-context downgrade, and the DB heuristic.
