* chore: Exclude CLAUDE.md from Cargo.toml

* feat: add callgraph module and integrate into main analysis flow

* feat: enhance CLI with new severity filtering and analysis modes

* feat: update CHANGELOG with recent enhancements and fixes to severity filtering and output handling

* feat: implement state-model dataflow analysis for resource lifecycle and auth state

* feat: enhance diagnostic output formatting and add evidence structure

* feat: implement attack surface ranking for diagnostics with scoring and sorting

* feat: add comprehensive documentation for installation, usage, and rules reference

* feat: add multiple language support for command execution and evaluation endpoints

* feat: implement inline suppression for findings using `nyx:ignore` comments

* feat: add confidence levels to AST patterns and update output structure

* feat: implement low-noise prioritization system with category filtering, rollup grouping, and configurable budgets

* feat: bump version to 0.4.0 and update changelog with new features and improvements

* feat: add dead code allowances to various functions in mod.rs and real_world_tests.rs
This commit is contained in:
Eli Peter 2026-02-25 21:16:36 -05:00 committed by GitHub
parent 19b578c5c4
commit 1bbe4b1cfb
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
456 changed files with 25628 additions and 1228 deletions

View file

@ -0,0 +1,47 @@
{
"description": "Async/await control flow with promisified exec and fetch-to-exec pipeline. Tests CFG handling of async patterns.",
"tags": [
"cfg",
"async",
"cmdi",
"fetch"
],
"modes": [
"full"
],
"expected": [
{
"rule_id": "taint-unsanitised-flow",
"severity": null,
"must_match": false,
"line_range": [
5,
9
],
"evidence_contains": [],
"notes": "execAsync is a promisified wrapper; scanner cannot trace through util.promisify to recognize it as a sink"
},
{
"rule_id": "taint-unsanitised-flow",
"severity": null,
"must_match": false,
"line_range": [
13,
19
],
"evidence_contains": [],
"notes": "fetch response flows to child_process.exec but fetch is not a recognized source"
},
{
"rule_id": "cfg-unguarded-sink",
"severity": null,
"must_match": false,
"line_range": [
15,
19
],
"evidence_contains": [],
"notes": "child_process.exec called without validation of data.command"
}
]
}

View file

@ -0,0 +1,20 @@
var child_process = require('child_process');
var util = require('util');
var execAsync = util.promisify(child_process.exec);
async function runCommand(userCmd) {
try {
var result = await execAsync(userCmd);
return result.stdout;
} catch (err) {
return err.message;
}
}
async function fetchAndExec(url) {
var response = await fetch(url);
var data = await response.json();
child_process.exec(data.command, function(err, stdout) {
return stdout;
});
}

View file

@ -0,0 +1,36 @@
{
"description": "Nested callbacks where fs.readFile data flows into child_process.exec. Tests CFG handling of callback nesting and data flow across callback boundaries.",
"tags": [
"cfg",
"callbacks",
"cmdi",
"fs"
],
"modes": [
"full"
],
"expected": [
{
"rule_id": "taint-unsanitised-flow",
"severity": null,
"must_match": false,
"line_range": [
3,
11
],
"evidence_contains": [],
"notes": "Data read from fs.readFile flows into child_process.exec but data is a callback param, not a recognized taint source"
},
{
"rule_id": "cfg-unguarded-sink",
"severity": null,
"must_match": false,
"line_range": [
7,
11
],
"evidence_contains": [],
"notes": "child_process.exec receives unchecked file contents"
}
]
}

View file

@ -0,0 +1,13 @@
var child_process = require('child_process');
var fs = require('fs');
function processInput(input, callback) {
fs.readFile(input.path, 'utf8', function(err, data) {
if (err) {
return callback(err);
}
child_process.exec(data, function(execErr, stdout) {
callback(execErr, stdout);
});
});
}

View file

@ -0,0 +1,58 @@
{
"description": "Switch statement with fallthrough from exec case to safe case (missing break). eval and execSync with function parameter.",
"tags": [
"cfg",
"switch",
"fallthrough",
"code-exec"
],
"modes": [
"full"
],
"expected": [
{
"rule_id": "js.code_exec.eval",
"severity": null,
"must_match": true,
"line_range": [
4,
8
],
"evidence_contains": [],
"notes": "AST pattern matches eval() call"
},
{
"rule_id": "taint-unsanitised-flow",
"severity": null,
"must_match": false,
"line_range": [
4,
8
],
"evidence_contains": [],
"notes": "userInput is a function param, not a recognized source; requires interprocedural analysis"
},
{
"rule_id": "taint-unsanitised-flow",
"severity": null,
"must_match": false,
"line_range": [
10,
14
],
"evidence_contains": [],
"notes": "userInput flows to child_process.execSync but userInput is a param, not a recognized source"
},
{
"rule_id": "cfg-error-fallthrough",
"severity": null,
"must_match": false,
"line_range": [
9,
16
],
"evidence_contains": [],
"notes": "Missing break after exec case causes fallthrough to safe case"
}
]
}

View file

@ -0,0 +1,19 @@
var child_process = require('child_process');
function handleAction(action, userInput) {
switch (action) {
case 'eval':
eval(userInput);
break;
case 'log':
console.log(userInput);
break;
case 'exec':
child_process.execSync(userInput);
case 'safe':
console.log('safe action');
break;
default:
break;
}
}

View file

@ -0,0 +1,25 @@
{
"description": "Try-catch-finally resource handling. processFile properly closes fd in finally block. leakyProcess leaks fd when throw occurs before closeSync.",
"tags": [
"cfg",
"resource-leak",
"try-catch",
"fs"
],
"modes": [
"full"
],
"expected": [
{
"rule_id": "cfg-resource-leak",
"severity": null,
"must_match": false,
"line_range": [
17,
25
],
"evidence_contains": [],
"notes": "fd leaks when throw fires at line 22 before closeSync at line 24; scanner may not track fd lifecycle in JS"
}
]
}

View file

@ -0,0 +1,26 @@
var fs = require('fs');
function processFile(path) {
var fd;
try {
fd = fs.openSync(path, 'r');
var data = fs.readFileSync(fd, 'utf8');
return data;
} catch (e) {
console.error(e);
} finally {
if (fd !== undefined) {
fs.closeSync(fd);
}
}
}
function leakyProcess(path) {
var fd = fs.openSync(path, 'r');
var data = fs.readFileSync(fd, 'utf8');
if (data.length === 0) {
throw new Error('empty');
}
fs.closeSync(fd);
return data;
}