12 KiB
Output Formats
Nyx supports three output formats, selected with --format or output.default_format in config.
Console (default)
Human-readable, color-coded output to stdout. Status messages go to stderr.
[HIGH] taint-unsanitised-flow (source 5:11) src/handler.rs:12:5 (Score: 76, Confidence: High)
Source: env::var("CMD") → Command::new("sh").arg("-c")
[MEDIUM] cfg-unguarded-sink src/handler.rs:12:5 (Score: 35, Confidence: Medium)
[LOW] rs.quality.unwrap src/lib.rs:88:5 (Score: 10, Confidence: High)
Severity indicators
| Tag | Color | Meaning |
|---|---|---|
[HIGH] |
Red, bold | Critical, likely exploitable |
[MEDIUM] |
Orange, bold | Important, may be exploitable |
[LOW] |
Muted blue-gray | Informational: code quality or weak signal |
Evidence fields
Taint and state findings include structured evidence:
| Label | Meaning |
|---|---|
| Source | Where tainted data originated (function name + location) |
| Sink | Where the dangerous operation happens |
| Path guard | Type of validation predicate protecting the path |
Score
When attack-surface ranking is enabled (default), each finding shows a Score value. Higher scores indicate greater exploitability. See Detector Overview for the scoring formula.
Rollup findings
High-frequency LOW Quality findings (e.g. rs.quality.unwrap) are grouped into rollup findings by (file, rule):
21:10 ● [LOW] rs.quality.unwrap
rs.quality.unwrap (38 occurrences)
Examples: 21:10, 50:10, 79:10, 105:10, 134:10
Run: nyx scan --show-instances rs.quality.unwrap
Rollups count as one finding for LOW budget enforcement. Use --show-instances <RULE> to expand a specific rule or --all to disable rollups entirely.
Suppression footer
When findings are suppressed by the prioritization pipeline, a footer is shown:
Suppressed 195 LOW/Quality findings.
Active filters:
include_quality = false
max_low = 20
max_low_per_file = 1
max_low_per_rule = 10
Use --include-quality, --max-low, or --all to adjust.
JSON
Machine-readable JSON object. The main keys are:
| Key | Type | Description |
|---|---|---|
findings |
array | Finding objects |
chains |
array | Composed exploit chains, when emitted |
dynamic_verification |
object | Count of attached dynamic verdicts |
verdict_diff |
object | Baseline comparison, only when --baseline is used |
{
"findings": [
{
"path": "src/handler.rs",
"line": 12,
"col": 5,
"severity": "High",
"id": "taint-unsanitised-flow (source 5:11)",
"path_validated": false,
"labels": [
["Source", "env::var(\"CMD\") at 5:11"],
["Sink", "Command::new(\"sh\").arg(\"-c\")"]
],
"confidence": "High",
"evidence": {
"source": {
"path": "src/handler.rs",
"line": 5,
"col": 11,
"kind": "source",
"snippet": "env::var(\"CMD\")"
},
"sink": {
"path": "src/handler.rs",
"line": 12,
"col": 5,
"kind": "sink",
"snippet": "Command::new(\"sh\")"
},
"notes": ["source_kind:EnvironmentConfig"],
"dynamic_verdict": {
"finding_id": "a3b12f0c91e04420",
"status": "Confirmed",
"triggered_payload": "cmdi-echo-marker"
}
},
"rank_score": 76.0,
"rank_reason": [
["severity_base", "60"],
["analysis_kind", "10"],
["source_kind", "5"],
["evidence_count", "1"]
]
}
],
"chains": [],
"dynamic_verification": {
"total": 1,
"confirmed": 1,
"partially_confirmed": 0,
"not_confirmed": 0,
"inconclusive": 0,
"unsupported": 0
}
}
Field descriptions
| Field | Type | Always present | Description |
|---|---|---|---|
path |
string | yes | File path relative to scan root |
line |
int | yes | 1-indexed line number |
col |
int | yes | 1-indexed column number |
severity |
string | yes | "High", "Medium", or "Low" |
id |
string | yes | Rule ID |
category |
string | yes | Finding category: "Security", "Reliability", or "Quality" |
path_validated |
bool | no | True if guarded by validation predicate |
guard_kind |
string | no | Predicate type (e.g. "NullCheck", "ValidationCall") |
message |
string | no | Human-readable context (state analysis findings) |
labels |
array | no | Array of [label, value] pairs for console display |
confidence |
string | no | Confidence level: "Low", "Medium", or "High" |
evidence |
object | no | Structured evidence (source/sink spans, state, notes) |
rank_score |
float | no | Attack-surface score (omitted when ranking disabled) |
rank_reason |
array | no | Score breakdown (omitted when ranking disabled) |
rollup |
object | no | Rollup data when findings are grouped (see below) |
chain_member_of |
int | no | Stable hash of the emitted chain this finding belongs to |
Fields marked "no" are omitted when empty/null/false to keep output compact.
Confidence levels
| Level | Meaning |
|---|---|
High |
Strong signal: taint-confirmed flow, definite state violation |
Medium |
Moderate signal: resource leak, path-validated taint, CFG structural |
Low |
Weak signal: AST pattern match, possible resource leak, degraded analysis |
Evidence object
The evidence field provides structured provenance data:
| Field | Type | Description |
|---|---|---|
source |
object | Source span (path, line, col, kind, snippet) |
sink |
object | Sink span (path, line, col, kind, snippet) |
guards |
array | Validation guard spans |
sanitizers |
array | Sanitizer spans |
state |
object | State-machine evidence (machine, subject, from_state, to_state) |
notes |
array | Free-form notes (e.g. "source_kind:UserInput", "path_validated") |
dynamic_verdict |
object | Dynamic verification result, when verification ran or was skipped for a typed reason |
All fields are omitted when empty/null.
Dynamic verdict object
evidence.dynamic_verdict uses this shape:
| Field | Type | Description |
|---|---|---|
finding_id |
string | Stable 16-character hex finding id |
status |
string | Confirmed, PartiallyConfirmed, NotConfirmed, Inconclusive, or Unsupported |
triggered_payload |
string | Payload label for Confirmed verdicts |
reason |
object/string | Typed reason for Unsupported |
inconclusive_reason |
object/string | Typed reason for Inconclusive |
detail |
string | Extra build, sandbox, or policy detail |
attempts |
array | Per-payload attempt summaries |
toolchain_match |
string | exact or drift |
differential |
object | Vulnerable versus benign control result, when both ran |
hardening_outcome |
object | Process-backend hardening result, when recorded |
The top-level dynamic_verification object counts verdict statuses across the emitted findings:
{
"total": 4,
"confirmed": 2,
"partially_confirmed": 0,
"not_confirmed": 1,
"inconclusive": 0,
"unsupported": 1
}
Rollup object
When a finding is a rollup (grouped from multiple occurrences), the rollup field is present:
{
"rollup": {
"count": 38,
"occurrences": [
{ "line": 21, "col": 10 },
{ "line": 50, "col": 10 },
{ "line": 79, "col": 10 }
]
}
}
| Field | Type | Description |
|---|---|---|
count |
int | Total number of occurrences |
occurrences |
array | First N example locations (controlled by rollup_examples) |
SARIF (Static Analysis Results Interchange Format)
SARIF 2.1.0 JSON, suitable for GitHub Code Scanning and other SARIF-compatible tools.
nyx scan . --format sarif > results.sarif
The SARIF output includes:
- Tool metadata: Nyx name and version
- Rules: Rule ID, description, severity mapping
- Results: One result per finding with location, message, and properties
- Properties: Each result includes
categoryand optionallyconfidence,rollup.count, andnyx_dynamic_verdict - Fingerprints: Dynamic verdict status is added as
partialFingerprints.dynamic_verdict_statuswhen present - Related locations: Rollup findings include example locations in
relatedLocations - Artifacts: File paths referenced by findings
GitHub Code Scanning integration
- name: Run Nyx
run: nyx scan . --format sarif > results.sarif
- name: Upload SARIF
uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: results.sarif
Exit Codes
| Code | Meaning |
|---|---|
0 |
Scan completed successfully; no findings matched --fail-on threshold |
1 |
--fail-on threshold breached (at least one finding meets or exceeds the specified severity) |
2 |
--gate policy tripped (e.g. no-new-confirmed saw a new Confirmed finding, or resolve-all-confirmed saw a previously Confirmed finding still open) |
| Other non-zero | Error (I/O, config, database, parse error) |
Without --fail-on or --gate, Nyx always exits 0 on a successful scan regardless of findings count.
Severity Levels
| Level | Description | Typical rules |
|---|---|---|
| High | Critical vulnerabilities, likely exploitable | Command injection, unsafe deserialization, banned C functions, taint-confirmed flows with user input sources |
| Medium | Important issues, may be exploitable with additional context | SQL concatenation, XSS sinks, reflection, unguarded sinks, resource leaks |
| Low | Informational: code quality or weak signals | Weak crypto algorithms, insecure randomness, unwrap()/panic!(), type-safety escapes |
Non-production severity downgrade
By default, findings in paths matching common non-production patterns (tests/, test/, vendor/, build/, examples/, benchmarks/) are downgraded by one tier:
- High → Medium
- Medium → Low
- Low → Low (unchanged)
Use --keep-nonprod-severity to disable this behavior.
Inline Suppressions
Suppress specific findings directly in source code using nyx:ignore comments. Suppressed findings are excluded from output, severity counts, and --fail-on checks by default.
Comment syntax
| Language | Comment styles |
|---|---|
| Rust, C, C++, Java, Go, JS, TS | // nyx:ignore ... or /* nyx:ignore ... */ |
| Python, Ruby | # nyx:ignore ... |
| PHP | // nyx:ignore ..., # nyx:ignore ..., or /* nyx:ignore ... */ |
Directive forms
x = dangerous() # nyx:ignore taint-unsanitised-flow (suppresses this line)
# nyx:ignore-next-line taint-unsanitised-flow
x = dangerous() (suppressed by the comment above)
nyx:ignore <RULE_ID>: suppresses findings on the same line as the comment.nyx:ignore-next-line <RULE_ID>: suppresses findings on the next line.- For taint findings, the primary line is the sink line (the
linefield in output).
Rule ID matching
- Case-sensitive, exact match after canonicalization.
- Comma-separated:
nyx:ignore rule-a, rule-b - Wildcard suffix:
nyx:ignore rs.quality.*matches any ID starting withrs.quality. - Taint IDs are canonicalized:
nyx:ignore taint-unsanitised-flowmatchestaint-unsanitised-flow (source 5:1)(parenthetical suffix stripped).
Console behavior
- Default: suppressed findings are hidden entirely.
--show-suppressed: suppressed findings appear dimmed with[SUPPRESSED]tag. Summary shows"N issues (M suppressed)".
JSON / SARIF behavior
- Default: suppressed findings are excluded from JSON/SARIF output.
--show-suppressed: suppressed findings are included with additional fields:
{
"suppressed": true,
"suppression": {
"kind": "SameLine",
"matched_pattern": "taint-unsanitised-flow",
"directive_line": 42
}
}
Exit code
Suppressed findings do not trigger --fail-on. A scan with only suppressed findings exits 0.
Rule ID Format
| Prefix | Detector | Example |
|---|---|---|
taint-* |
Taint analysis | taint-unsanitised-flow (source 5:11) |
cfg-* |
CFG structural | cfg-unguarded-sink, cfg-auth-gap |
state-* |
State model | state-use-after-close, state-resource-leak |
<lang>.*.* |
AST patterns | rs.memory.transmute, js.code_exec.eval |
See the Rule Reference for a complete listing.