Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
97a9fa6
Plan cached file purposes in a dry run
tauanbinato Sep 26, 2026
b7af8e0
Ask about deserializers in every language that names one
tauanbinato Sep 26, 2026
3afc68e
Write counts in the singular or plural instead of (s)
tauanbinato Sep 26, 2026
465b933
Show the errors a function's callees raise in its error-detail trace
tauanbinato Sep 26, 2026
a587480
Describe deserializer checks and callee errors on the site's how-it-w…
tauanbinato Sep 26, 2026
f551756
Keep file-organization findings for files long enough to split
tauanbinato Sep 26, 2026
8bb9ffd
Name rules by their ID without the group
tauanbinato Sep 26, 2026
b194f8f
Read JVM packages named example or demo as source, not examples
tauanbinato Sep 26, 2026
62b314e
Ask about XML parsed with external entities
tauanbinato Sep 26, 2026
2cf43a4
Share test setup and name the steps JevGate's review flagged
tauanbinato Sep 26, 2026
83607d7
Let a Go file reach the files of its package
tauanbinato Sep 26, 2026
2986d89
Judge files with a few small syntax errors apart from them
tauanbinato Sep 26, 2026
b3f2a80
Leave the comments of a Laravel app's published configuration out
tauanbinato Sep 26, 2026
be6dad7
Report copies in test support code at most as a consider
tauanbinato Sep 26, 2026
541a5de
Bump the shared-logic and comments rule versions
tauanbinato Sep 26, 2026
5710be1
Ask whether an error's text describes an internal failure
tauanbinato Sep 26, 2026
2071a9f
Settle undecided workflow jobs with recheck Choices
tauanbinato Sep 26, 2026
ba6d46d
Count a secret token a SECURITY DEFINER function checks as the caller's
tauanbinato Sep 26, 2026
e57f0ec
List deserializers and XML entities among the injections the site des…
tauanbinato Sep 26, 2026
5076fcf
Skip a leading status code when reading an error's message
tauanbinato Sep 26, 2026
3c5c149
Name the constant a finding on a file's constants is about
tauanbinato Sep 26, 2026
a2d0a37
Ask a redundant test pair whether each test checks something else
tauanbinato Sep 26, 2026
021b49b
Leave copies in deprecated code out of shared logic
tauanbinato Sep 26, 2026
5f92d25
Count a callee's own error text as the program's in the messages Choice
tauanbinato Sep 26, 2026
9ca43b4
Share locate requests and split what JevGate's review flagged
tauanbinato Sep 26, 2026
79fc9e1
Report a foreign error message's undecided lean as a note
tauanbinato Sep 26, 2026
8cd00db
Word value, security and job findings in helpers of their own
tauanbinato Sep 26, 2026
99efd47
Merge main into audit-fixes
tauanbinato Sep 26, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,24 @@ Notable changes to JevGate. Versions follow [Semantic Versioning](https://semver

## [Unreleased]

- Injection: code that names a deserializer that can build any object is asked whether it loads data another party sends, in Python, Ruby, Java, JavaScript and TypeScript: Python's `pickle`, `marshal`, `shelve`, `jsonpickle` and `yaml.load`, Ruby's `Marshal.load` and `YAML.load`, Java's `ObjectInputStream`, `XMLDecoder`, XStream and SnakeYAML, and node-serialize. Only Django views and PHP `unserialize` were asked before, so a Flask route passing `pickle.loads(request.get_data())` was clear; it is now a review (CWE-502). Other requests are unchanged, so cached answers stay valid.
- File organization: a file of fewer than 250 lines gets at most a note, and a group holding three quarters or more of a file's members is no longer named as the part to move (a consider left naming no group is a note; a review says to split the whole file). Checked by hand on 25 projects, 3 of 32 findings on shorter files were right against 21 of 29 on longer ones; review precision went from 50% to 67% and consider precision from 46% to 71%. No request changes, so cached answers stay valid.
- Sensitive data: an error-detail trace lists the errors the functions it calls create, with their messages, and asks whether the text a response carries describes an internal failure (a database, network, file or library error) or explains invalid input, instead of who raised it. A handler that returns the message its own service raised to explain a missing record (`except LookupError as exc: raise HTTPException(404, detail=str(exc))`) is no longer a review, while one that returns the text of every exception it catches still is. The Choice over which message a function creates carries another error's text is told the same, so passing on such an error's text is the program's own. On 71 projects, 11 of one FastAPI project's 12 wrong reviews became notes or considers and its two wrong considers became notes, as did a local tool's consider about the text of an API error it returns to the user's own agent; only error-detail traces are asked again.
- Sensitive data: an error-detail answer leaning toward a client in a function whose error message carries another error's text is a note ("puts the text of a library or database error into an error message, which may reach a remote client") instead of a consider. Labeled by hand, 1 of 28 such considers outside example code was right: a central handler replaced the text with a generic message, the error was one written for users (Supabase Auth's "Invalid login credentials"), or no remote client read it (a CLI, a desktop app). On 71 projects, 33 considers became notes, 27 of them wrong; five of the right ones were example files whose leaks their route handlers still report. Nothing is asked again.
- Test redundancy: a pair that would be a review ("one adds nothing") is first asked whether each test checks something the other does not, as Ruby pairs already were; if so, it is a consider. Tests of two overloads with equivalent inputs (`writeTo(Path)` and `writeTo(File)`) were reviews. Pairs that read the same apart from their names are not asked. On 31 labeled projects, test-redundancy reviews went from 4 right and 6 wrong to 4 right and none wrong.
- A finding on a file's constants names the constant it is about ("The constant is `WAGTAILADMIN_BASE_URL`.") and points at it, from a Choice asked after the finding, instead of listing every constant of the file: 20 of 23 such findings across 71 projects are now named.
- An error's message is not its leading status code: `HTTPException(500, f"engine error: {e}")` was quoted as "The message is 500.", and the trace asked which message carries another error's text about `500`.
- Access control: a SECURITY DEFINER function that acts only for whoever holds a secret token it looks up by value, such as an invitation or reset token, is not "skipping the caller check": basejump's `accept_invitation` and `lookup_invitation` are no longer reviews.
- Workflows: a job left undecided is asked, in a recheck, which expression of its `run` scripts holds text outsiders write and what code it runs, as Choices that can only clear it. Undecided jobs went from 14 to 7 across 65 projects, with no finding changed.
- Shared logic: copies in a function or type marked deprecated (a `@deprecated` tag, annotation or decorator, `#[deprecated]`, `[Obsolete]`, or a `Deprecated:` comment above it) are not compared, since they go with the next major version: flysystem's deprecated phpseclib 2 adapter was paired with the phpseclib 3 adapter replacing it in 7 reviews and a consider, all wrong. No other project's findings changed across 71 projects.
- Shared logic: copies in test code outside its cases (fixtures, helpers, setup) are at most a consider. Labeled by hand on 25 projects, 13 of 19 such reviews were a level too strong, while 11 of 12 considers were right as they were; reviews are now for duplicated application code.
- Comments: a Laravel application's `config/*.php` files (an `artisan` script at the root) are not read for comments, since the framework and its packages publish them with their documentation: on two Laravel apps all six comment considers there were the publisher's text, and 14 files' comments stayed undecided.
- A file whose parse holds a few small syntax errors (at most three regions, an eighth of the source) is judged apart from the definitions that hold them, instead of being skipped: tree-sitter's grammars miss some valid code, which left four of zustand's store files and govwa's `user/user.go` unjudged (its MD5 password hashing is now a review). Generator templates (under `templates/`, or with ERB tags or `//#if` conditions) are still skipped.
- Go: a file reaches the other files of its package (its directory) and the packages its `import ( … )` block names, as Java files reach their package. Callers, callees and the files that use a file were missed in Go: an injection's recheck with its callers was never asked, so govwa's SQL injection through a query helper stayed a consider; it is now a review, and callers cleared two notes elsewhere. Only Go projects' requests change.
- Injection: XML parsed with a parser that can resolve external entities (CWE-611) is asked about where the code names such a parser (lxml, SAX, pulldom, DocumentBuilderFactory, XmlDocument, SimpleXML, libxmljs, Nokogiri) or its file imports one and it calls a parse method. pygoat's XXE lab went from a note to a review; across 65 projects only 6 requests changed.
- Java, Kotlin, Scala and Groovy packages named `example` or `demo`, such as Spring Initializr's default `com.example.demo`, are source, not example code: every finding of such a project was capped (injection and sensitive data at a consider, the rest at a note) and its hardcoded values were not judged. Directories above the source root, such as `examples/`, still mark example code.
- A rule can be named by its ID without the group (`--rule file-organization`, `jevgate: allow(sensitive-data) …`, `[rules]` in `jevgate.toml`), besides its full ID and its key; `jevgate.schema.json` lists these names, and an unknown name points to `jevgate rules`.
- Counts in messages are singular or plural ("1 new review finding", "2 files") instead of "finding(s)", including the gate's reasons in the JSON report.
- `--dry-run` plans the files whose purpose the cache already answers, as a run does, so a warm cache's estimate matches the run: on a Rails project it counted 106 of 1,532 requests as new while the run sent none.

## [0.19.0] - 2026-09-25
Expand Down
77 changes: 67 additions & 10 deletions docs/classification-cascade.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,14 @@ signatures, or one candidate pair.
Astro, Vue and
Svelte files are parsed as their scripts: Astro frontmatter and `<script>`
contents, with every other byte a space, so lines stay the file's.
A file whose parse holds syntax errors is not judged, unless they are few
and small (at most three regions, an eighth of the source in all), since
grammars miss some valid code: tree-sitter-typescript reads a call
signature starting with `<T>` on the line after another as its
continuation, which left four of zustand's source files unjudged. The
definitions that hold an error are then left out. Generator templates
(under `templates/`, or holding ERB tags or `//#if` conditions) keep the
strict rule, since their placeholders are not the language's syntax.
2. **Local analysis** (`src/analysis/`). Units with signatures, calls, references
and control-flow nesting; callbacks registered through calls, including
module-level route handlers named by their registration
Expand Down Expand Up @@ -86,7 +94,11 @@ signatures, or one candidate pair.
values. Java `equals` and `hashCode` overrides offer no copies or
values, and neither does an initial capacity (`new ArrayList<>(4)`) or the
number a method returns whole (`int cost() { return 7; }`), which the
method's name already names.
method's name already names. Neither does a function or type marked
deprecated (a `@deprecated` tag, annotation or decorator,
`#[deprecated]`, `[Obsolete]`, or a `Deprecated:` comment above it): it
goes with the next major version, and flysystem's deprecated phpseclib 2
adapter was paired with the adapter replacing it in seven reviews.
3. **First pass** (`src/units/`). One dispatch of every unit request. Functions,
for simplification, hardcoded values and security, are packed eight per
request within runs of functions, a run ending after a function whose name
Expand Down Expand Up @@ -144,7 +156,11 @@ signatures, or one candidate pair.
undecided. Ruby test pairs carry their groups and hooks when these differ,
and are also asked whether each test checks something the other does not
(another method, matcher, attribute, option or code path); "one adds
nothing" is a review only when that is ruled out at 0.80. Copied RSpec
nothing" is a review only when that is ruled out at 0.80. Pairs in other
languages are asked it after the fact, only when they would be a review
and do not read the same apart from their names: tests of two overloads
(`writeTo(Path)` and `writeTo(File)`) and of two public methods were
reviews, and six of ten labeled reviews were wrong. Copied RSpec
examples for an alias and its original (`each` and `each_pair`) or for two
predicates of one record were otherwise reviews.
A controller method a test reaches through a request carries that route.
Expand Down Expand Up @@ -177,7 +193,18 @@ signatures, or one candidate pair.
message argument of each error the function creates and asks which one,
if any, carries another error's text: the response is often written by an
error handler in another file, and adding the handler to every unit also
cleared real leaks. Each registered error handler (`.onError(…)`,
cleared real leaks. The trace also lists the errors that the functions it
calls create, two calls deep in its own file or files it imports, with
their messages, and then asks the exception check about whose text a
response carries rather than who raised it: FastAPI handlers returning
`str(exc)` for the `LookupError` their service raised with the program's
own text ("Imóvel não encontrado") were twelve reviews in one project,
since the handler "did not raise it itself"; with the service's raise in
view, ten became notes or considers, and tools that return the text of
every exception they catch became reviews. The Choice over the messages
it creates is told the same, since two of those considers remained
because passing on the service's error read as "another error's text".
Each registered error handler (`.onError(…)`,
`.setErrorHandler(…)`, Express four-parameter `.use(…)` middleware, Flask
and FastAPI decorators, NestJS `@Catch` filters, axum `IntoResponse` and
actix-web `ResponseError` for an error type, Rocket catchers, ASP.NET Core
Expand Down Expand Up @@ -300,7 +327,22 @@ signatures, or one candidate pair.
identifiers quoted by doubling embedded quotes as handled (identifiers
cannot be bound), and the URL check excludes requests a web page sends from
the user's browser; on fresh repositories both had flagged such code, while
the SQL and SSRF advisory functions kept their answers.
the SQL and SSRF advisory functions kept their answers. Code whose source
names a deserializer that can build any object (Python's `pickle`,
`marshal`, `shelve`, `jsonpickle` or `yaml.load`; Ruby's `Marshal.load`
or `YAML.load`; Java's `ObjectInputStream`, `XMLDecoder`, XStream or
SnakeYAML; node-serialize) is asked about loading data with it in the
presence question, and its trace asks that language's deserialize check,
as Django views and PHP pages naming `unserialize` are: a Flask route
passing `pickle.loads(request.get_data())` was asked only about query,
command, code and markup text, and was clear. Code that parses XML with a
parser able to resolve external entities (it names lxml, SAX, pulldom,
DocumentBuilderFactory, XmlDocument, SimpleXML, libxmljs or Nokogiri, or
its file imports one and it calls a parse method) is asked the same way
about XML with external entities (CWE-611): pygoat's lab calling
`make_parser()` with external entities turned on, and a Spring controller
parsing its body with a default DocumentBuilderFactory, were clear. Only
the requests of such functions change.
PHP units read the presence questions and checks in PHP's own terms
(`src/units/questions/php.rs`), naming its functions (`echo`,
`shell_exec` and backticks, `mysqli_real_escape_string`, `password_hash`,
Expand Down Expand Up @@ -496,7 +538,11 @@ signatures, or one candidate pair.
whether one can hold text outside people write: one question over the
whole job scored obvious injections 0.57 to 0.79. Jobs of workflows that
run on `pull_request_target` or `workflow_run` are asked whether they run
pull request code with secrets.
pull request code with secrets. A job left undecided is asked, apart,
which expression holds outside text and what code it runs (the base
branch's, the pull request's, or none), as Choices that can only clear:
a release job's tag names and a job uploading a pull request's coverage
report stayed between 0.2 and 0.6, and 7 of 14 such jobs were settled.
5. **Composition** (`src/units/compose.rs`). Pure. On a Score whose top level is
the actionable concern: review at 0.80 on the top level, consider at 0.80 on
middle-or-top, clear when the top level is ruled out at 0.80, otherwise
Expand All @@ -505,9 +551,12 @@ signatures, or one candidate pair.
undecided after its follow-up is a note when it leans toward the concern
(0.50, the leading probability) and stays uncertain otherwise: undecided
answers leaning away were almost all acceptable code, and leaning toward
held both real positives of the labeled set. When the own-messages check
finds another error's text in an error message, an error-detail answer
leaning toward a client is a consider. Instruction sections are
held both real positives of the labeled set. So is an error-detail
answer leaning toward a client when the own-messages check finds another
error's text in an error message: as a consider, 1 of 28 such findings
outside example code was right, since a central handler replaced the
text with a generic message, the error was one written for users, or no
remote client read it. Instruction sections are
cleanups, so their findings are at most a consider. Comments are cleanups
too: at most a consider, and documentation that only repeats the
declaration it documents is at most a note, since documentation tools and docstring
Expand All @@ -531,7 +580,8 @@ signatures, or one candidate pair.
labeled set, no living document leaned past 0.50. Questions ask whether a change would help a reader ("would splitting
it make it easier to understand?"), not how many tasks or purposes there are:
Jev does not count reliably and reads "tasks" literally. Copies inside test
cases are one level lower. Copies of three lines or fewer are at most a
cases are one level lower, and copies in their fixtures, helpers and setup
at most a consider. Copies of three lines or fewer are at most a
consider: in Java such a copy was as often an idiom, a pooled builder
borrowed and released around one call, as a missing helper. A test that
checks several unrelated behaviors is at most a note: on labeled tests,
Expand All @@ -540,7 +590,14 @@ signatures, or one candidate pair.
for three or more tests only when the pairs connect them: two pairs that
share no test stay two pairs (a pair of redirect tests and a pair of deny
tests of `get` are not four overlapping tests). A review always carries a
finding.
finding. A file-organization finding on a file of fewer than 250 lines is
a note: of 32 such findings labeled by hand on 25 projects, 3 were right,
and splitting a 138-line module or a 175-line test helper file only
scatters it, while 21 of 29 on longer files were right. A group that
holds three quarters or more of the outline's members is not named:
moving 14 of a file's 15 members, or 9 of its 11 tests, moves the file
rather than splitting it, so a consider left naming no group is a note
and a review says to split the whole file.
6. **Gate.** `--fail-on`, `[[scope]]` levels per path and the baseline act on
composed findings only. Baseline entries can carry a reason (`intended`,
`later`, `wrong`) that survives rewrites; `baseline stats` counts them.
Expand Down
Loading
Loading