mirror of
https://github.com/norandom/OpenUltraSAST.git
synced 2026-10-16 03:49:26 +00:00
feat(access-control): model the collection read, by argument shape rather than by name
NodeGoat's documented insecure direct object reference reads with allocationsCol.find(searchCriteria()), and `find` was absent from an operation table holding findOne, findById, findAll and findByPk. The obligation was missing at the commonest read there is, so that defect raised nothing at any point in the pipeline. It was absent for a good reason, and the reason is the argument, not the name. Measured on wp-statistics, a WordPress plugin that ships jQuery: its assets hold 88 calls named find, of which 70 pass a jQuery selector, 13 pass a predicate to Array.prototype.find and 5 pass a selector built at run time by a template literal or a concatenation. None is a collection read. So the obligation is claimed only where the first argument has the shape of a query document. The operation fact states which argument with query_document_arg, and the arbiter that can see argument shapes decides it: no arguments at all, an object literal, or a builder call are claimed; a method reference, a literal, an operator call and a bare identifier are not. Each exclusion is one of the measured classes above rather than a guess. Zero of those 88 calls became an obligated operation, and the zero is measured rather than empty: the same run's census reports 60 files and 4,552 methods, and the files holding those calls are in it. On NodeGoat all three find calls are collection reads and all three are claimed, which raises the obligation at four more handlers -- the allocations handler that takes userId from req.params on an authenticated route, the benefits pages whose isAdmin guard is commented out in the route table, and the memo listing. Seven of fifteen handlers now reach a verdict, all corroborated, none reported without a judge, and the census is unchanged on both subjects. A bare identifier is deliberately not claimed: col.find(query) and arr.find(pred) are the same shape and neither repository measured holds one, so this is a stated false negative rather than an unmeasured false-positive risk. 1,415 tests pass, sixteen skipped; twelve Joern-gated query tests pass in the image. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]> Claude-Session: https://claude.ai/code/session_01EmM1j46qgKMh3zqD1QMwEo
This commit is contained in:
co-authored by
Claude Opus 5
parent
102a21fc4e
commit
c0427e6ceb
@@ -167,6 +167,13 @@ corroborated and none is reported without a judge, which is the honest rung: ent
|
||||
obligation reported a signup handler that is public by design, because its authenticated
|
||||
siblings share the same data module. VAmPI is unchanged.
|
||||
|
||||
The commonest collection read now carries the obligation too, so NodeGoat's documented insecure
|
||||
direct object reference raises one where it never had. `find` had been left out because the name
|
||||
is shared with array searches and jQuery selectors, so it is claimed only in the argument shape
|
||||
of a query document. Measured on a plugin that ships jQuery, all 88 of its `find` calls stay
|
||||
unclaimed. Seven NodeGoat handlers now reach a verdict, all corroborated; the census is
|
||||
unchanged, because the corroborated band needs a judge it does not configure.
|
||||
|
||||
Closing the second gap surfaced four defects, three of which were false positives it would
|
||||
otherwise have shipped: a permissive-value set shared across settings whose booleans have
|
||||
opposite polarity, a dotted setting matched on its trailing segment alone, a flow query asked of
|
||||
@@ -178,4 +185,5 @@ permissive setting; task 14.6 carries it.
|
||||
Evidence: [family detection census](../../../benchmarks/measurements/2026-09-19-family-detection-census.json),
|
||||
[untrusted destination vocabulary](../../../benchmarks/measurements/2026-09-19-untrusted-destination-vocabulary.json),
|
||||
[output encoding family](../../../benchmarks/measurements/2026-09-19-output-encoding-family.json),
|
||||
[access control across the module split](../../../benchmarks/measurements/2026-09-20-access-control-module-split.json).
|
||||
[access control across the module split](../../../benchmarks/measurements/2026-09-20-access-control-module-split.json),
|
||||
[collection read obligation](../../../benchmarks/measurements/2026-09-20-collection-read-obligation.json).
|
||||
|
||||
@@ -518,10 +518,14 @@ Preserve all completed task evidence below.
|
||||
- _Boundary: contributor-scan dominance, shipped obligation facts_
|
||||
- _Depends: 14.4_
|
||||
- _Requirements: 6.2, 6.3, 8.1_
|
||||
- [ ] 14.8 Model the collection read an application actually writes
|
||||
- [x] 14.8 Model the collection read an application actually writes
|
||||
- **Named by the 14.4 measurement.** NodeGoat's documented insecure direct object reference reads with `allocationsCol.find(searchCriteria())`, and `find` is absent from an operation vocabulary holding `findOne`, `findAll` and `findById`. It is the commonest read there is, so the family's obligation is missing at exactly the shape it exists for.
|
||||
- It is absent for a reason: `Array.prototype.find` is everywhere in JavaScript and jQuery's `.find` is everywhere in a vendored bundle. A collection read takes a query document where those take a closure or a selector, so the discriminator is the argument's shape rather than the name.
|
||||
- Done when a collection read raises the obligation, an array search and a selector do not, and the false-positive count is measured on a repository with vendored bundles rather than argued from the shape alone.
|
||||
- _Verified 2026-09-20:_ 1,415 tests passed, sixteen skipped; twelve Joern-gated query tests pass in the image; Ruff/format/mypy passed. The obligation is claimed where the first argument has the shape of a query document, which the operation fact states as `query_document_arg` and the arbiter decides: no arguments at all, an object literal, or a builder call. Not claimed: a method reference, a literal, an operator call, and a bare identifier.
|
||||
- _The false-positive count is measured, on wp-statistics, a WordPress plugin that ships jQuery._ Its assets hold 88 calls named `find` — 70 jQuery selectors, 13 array predicates and 5 selectors built at run time by a template literal or a concatenation, and no collection read at all. Zero became an obligated operation. The zero is a measured one and not an empty graph: the same run's census reports 60 files and 4,552 methods, and the files holding those 88 calls are in it.
|
||||
- On NodeGoat all three `find` calls are collection reads and all three are claimed, which raises the obligation at four more handlers including `app/routes/allocations.js:displayAllocations` — the documented insecure direct object reference, which had raised nothing at any point in the pipeline before. Seven of fifteen handlers now reach a verdict, all corroborated, none reported without a judge, and the census is unchanged on both subjects. Evidence: `benchmarks/measurements/2026-09-20-collection-read-obligation.json`.
|
||||
- _A bare identifier is deliberately not claimed._ `col.find(query)` and `arr.find(pred)` are the same shape and neither repository measured holds one, so this is a stated false negative rather than an unmeasured false-positive risk.
|
||||
- _Boundary: shipped obligation facts, contributor-scan dominance_
|
||||
- _Depends: 14.4_
|
||||
- _Requirements: 6.2, 8.1_
|
||||
|
||||
Reference in New Issue
Block a user