mirror of
https://github.com/elicpeter/nyx.git
synced 2026-07-03 20:41:00 +02:00
Phase 1 (#33)
* 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:
parent
19b578c5c4
commit
1bbe4b1cfb
456 changed files with 25628 additions and 1228 deletions
47
tests/fixtures/real_world/javascript/cfg/async_await_flow.expect.json
vendored
Normal file
47
tests/fixtures/real_world/javascript/cfg/async_await_flow.expect.json
vendored
Normal 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"
|
||||
}
|
||||
]
|
||||
}
|
||||
20
tests/fixtures/real_world/javascript/cfg/async_await_flow.js
vendored
Normal file
20
tests/fixtures/real_world/javascript/cfg/async_await_flow.js
vendored
Normal 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;
|
||||
});
|
||||
}
|
||||
36
tests/fixtures/real_world/javascript/cfg/callback_nesting.expect.json
vendored
Normal file
36
tests/fixtures/real_world/javascript/cfg/callback_nesting.expect.json
vendored
Normal 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"
|
||||
}
|
||||
]
|
||||
}
|
||||
13
tests/fixtures/real_world/javascript/cfg/callback_nesting.js
vendored
Normal file
13
tests/fixtures/real_world/javascript/cfg/callback_nesting.js
vendored
Normal 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);
|
||||
});
|
||||
});
|
||||
}
|
||||
58
tests/fixtures/real_world/javascript/cfg/switch_fallthrough.expect.json
vendored
Normal file
58
tests/fixtures/real_world/javascript/cfg/switch_fallthrough.expect.json
vendored
Normal 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"
|
||||
}
|
||||
]
|
||||
}
|
||||
19
tests/fixtures/real_world/javascript/cfg/switch_fallthrough.js
vendored
Normal file
19
tests/fixtures/real_world/javascript/cfg/switch_fallthrough.js
vendored
Normal 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;
|
||||
}
|
||||
}
|
||||
25
tests/fixtures/real_world/javascript/cfg/try_catch_finally.expect.json
vendored
Normal file
25
tests/fixtures/real_world/javascript/cfg/try_catch_finally.expect.json
vendored
Normal 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"
|
||||
}
|
||||
]
|
||||
}
|
||||
26
tests/fixtures/real_world/javascript/cfg/try_catch_finally.js
vendored
Normal file
26
tests/fixtures/real_world/javascript/cfg/try_catch_finally.js
vendored
Normal 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;
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue