# NoSQL injection / mass-assignment pre-scan (ARION service)

Deterministic scanner for NoSQL operator injection, mass-assignment /
overposting, and prototype-pollution-shaped request-object flows across
Python, JS/TS, PHP, Ruby, Go, Java/Kotlin and C# sources. No server, no
network — every check is a property of the source line 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 line places a request-shaped value on a
query/model path; review where the value comes from". Cross-line
dataflow, framework middleware, validators defined elsewhere and
runtime hardening are out of scope. A clean report is not proof the
codebase is injection-free.

## Coverage

- **`NQ-REQ-QUERY` (HIGH)** — a whole request object (`req.body`,
  `req.query`, `request.data`/`json`/`args`/`form`, `$request->all()`,
  `$_POST`, `params.to_unsafe_h`, `ctx.request.body`, `event.body` ...)
  used as the filter of `find`/`findOne`/`findById*`/`count*`/
  `distinct`/`aggregate`/`exists`/`deleteOne|Many`/`findOneAnd*`/
  `where`/`firstWhere`/`hydrate`/`mapReduce` + pymongo snake_case
  variants. `$gt`/`$ne`/`$where` operators in the body become the query.
- **`NQ-MASS-ASSIGN` (HIGH / MEDIUM)** — a whole request object written
  into a model: `create`/`insert*`/`update*`/`replaceOne`/
  `findOneAndUpdate`/`findByIdAndUpdate`/`bulkWrite`/`fill`/`forceFill`/
  `firstOrCreate`/`updateOrCreate`/`update_attributes`/`TryUpdateModel`,
  `new Model(req.body)`, `Model(**request.POST)`, `Model.new(params)`,
  Laravel `->fill`/`->update`/`::create($request->all())`. Attacker sets
  any column — `role`, `isAdmin`, `verified`. Generic helpers
  (`.set/.save/.push/.store`) tier MEDIUM; Rails `permit!` /
  `to_unsafe_h` standalone flags MEDIUM.
- **`NQ-FIELD-REQ` (MEDIUM)** — a scalar request value in field-value
  position of a query/update literal:
  `findOne({password: req.body.p})` accepts `{"$ne":""}` when the body
  is JSON — the classic login bypass. Also `find_by(email: params[:e])`,
  `where('k', $_POST['v'])`, `request.args.get()` field values.
- **`NQ-COMPUTED-KEY` (MEDIUM)** — a request-controlled key inside a
  query/update document: `{[req.body.k]: 1}`, or an operator name built
  by concat (`'$' + k`, `k + '$gt'`).
- **`NQ-WHERE-NONLIT` (HIGH / MEDIUM / LOW)** — server-side JS query
  operators `$where`/`$function`/`$accumulator`/`$expr`/`db.eval`/
  `mapReduce` fed request data (HIGH), a non-literal (MEDIUM), or a
  literal (LOW — review necessity).
- **`NQ-REGEX-REQ` (MEDIUM)** — request-sourced regex in query context:
  `new RegExp(req...)`, `$regex:` adjacent to a request marker,
  `re.compile(request...)`, `Regexp.new(params[...])`. Operator
  injection plus ReDoS surface.
- **`NQ-PROTO-MERGE` (MEDIUM)** — request object deep-merged into a
  server object: `_.merge`/`mergeDeep`/`defaultsDeep`/`deepmerge`/
  `Object.assign`/`$.extend(true, ...)`/`extend(true`. Prototype-
  pollution + field-takeover surface.
- **`NQ-PROTO-KEY` (MEDIUM)** — property write keyed by a request value
  (`store[req.body.key] = v`) or literal `__proto__`/`constructor`/
  `prototype` key access.
- **`NQ-FORM-ALL` (MEDIUM)** — wildcard form/serializer config: Django
  ModelForm / DRF `fields = '__all__'`, `exclude = ()`, Laravel
  `->with($request->all())` — every model column becomes writable.

## Severity context

- Same-line sanitising/whitelisting markers (`mongo-sanitize`,
  `sanitize()`, `.pick()`/`.omit()`, `params.permit(...)`,
  `->only()`, `->validated()`, schema `.parse()`, `Joi.`/`z.`/`yup.`,
  `escapeRegExp`/`re.escape`, allowlist/whitelist) downgrade one tier —
  still listed at LOW for review, never dropped.
- Test/spec/fixture paths downgrade one tier; comment lines and
  block-comment bodies are skipped; import/require lines cannot fire.
- Multi-line calls are followed up to 4 lines until parens balance, so
  `Model.create(\n req.body\n)` still flags.
- Deliberately silent: literal filters (`find({active: true})`), array
  `.filter()` without request args, `doc.save()` with no arg, computed
  keys with no request marker (`{[k]: v}`), `Object.assign(a, b)` with
  no request object.

## Usage

    python3 nosql_prescan.py <file-or-dir>          # markdown report
    python3 nosql_prescan.py <file-or-dir> --json   # CI-gate JSON
    python3 nosql_prescan.py --selftest             # 54 vectors

Exit 0 = no HIGH/MEDIUM findings, 1 = findings, 2 = usage.
Stdlib only, Python 3.8+, fully offline.

## Validation

54/54 selftest vectors pass. Live-validated on a real 253-file TS/JS
service repo: 0 HIGH/MEDIUM noise — 3 LOW findings, all literal
`__proto__`/`constructor` key references inside test paths (correctly
downgraded). Hostile fixture: every planted class caught at the
correct tier; sanitised routes (`_.pick`, `escapeRegExp`, named-field
updates) tier LOW or silent.

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