JSON and JSONL
Generate Noir scan results in JSON or JSONL format.
Noir supports two JSON-flavored output modes:
- JSON: Single JSON object containing all results
- JSONL: One JSON object per line, good for streaming and large datasets
JSON is the format every next stage reads. Pipe it into jq, a script, or an AI auditor's context.
JSON Output
Use -f json to get JSON. Adding --no-log suppresses log messages so only the JSON hits stdout, which keeps things clean when piping into other tools.
noir scan . -f json --no-log
The result is an object with an endpoints array, a passive_results array, and an errors array. Each endpoint has the URL, HTTP method, parameters (typed as cookie, form, header, json, etc.), source code location in details.code_paths, the analyzer that produced it in details.technology, and any security tags from taggers. The sample below was produced with taggers enabled (-T), which is what fills the tags arrays.
When more than one analyzer finds the same endpoint, for example a Go router and the project's OpenAPI document, Noir merges them into one entry. details.technology names the analyzer that won the merge, and details.technologies lists every analyzer that contributed, sorted and deduplicated. The list is only emitted when it holds two or more entries, so an endpoint seen by a single analyzer looks exactly as it did before. A consumer that wants every source should read technologies when present and fall back to [technology] when it is absent.
"details": {
"code_paths": [
{ "path": "routers/router.go", "line": 42 },
{ "path": "swagger/swagger.json", "line": 1300 }
],
"technology": "go_beego",
"technologies": ["go_beego", "oas2"]
}
{
"endpoints": [
{
"callees": [],
"url": "/query",
"method": "POST",
"internal": false,
"details": {
"code_paths": [
{
"path": "spec/functional_test/fixtures/crystal/kemal/src/testapp.cr",
"line": 17
}
],
"technology": "crystal_kemal"
},
"protocol": "http",
"kind": "",
"tags": [],
"params": [
{
"name": "my_auth",
"value": "",
"param_type": "cookie",
"tags": []
},
{
"name": "query",
"value": "",
"param_type": "form",
"tags": []
}
]
},
{
"callees": [],
"url": "/token",
"method": "GET",
"internal": false,
"details": {
"code_paths": [
{
"path": "spec/functional_test/fixtures/crystal/kemal/src/testapp.cr",
"line": 22
}
],
"technology": "crystal_kemal"
},
"protocol": "http",
"kind": "",
"tags": [
{
"name": "oauth",
"description": "Suspected OAuth endpoint for granting 3rd party access.",
"tagger": "Oauth"
}
],
"params": [
{
"name": "client_id",
"value": "",
"param_type": "form",
"tags": []
},
{
"name": "redirect_url",
"value": "",
"param_type": "form",
"tags": [
{
"name": "ssrf",
"description": "This parameter may be vulnerable to Server Side Request Forgery (SSRF) attacks.",
"tagger": "Hunt"
}
]
},
{
"name": "grant_type",
"value": "",
"param_type": "form",
"tags": []
}
]
}
],
"passive_results": [],
"errors": []
}
Analyzer Failures
A tech analyzer that raises is logged and skipped, and the scan continues with the rest. So is a single file the analyzer cannot read or parse (a file over the parse-time ceiling, for instance), which costs only itself rather than the run. errors records both, so an empty result for a framework can be told apart from a framework that was never analyzed, and a complete scan from one that quietly dropped files:
{
"endpoints": [],
"passive_results": [],
"errors": [
{ "tech": "go_gin", "message": "Index out of bounds" },
{ "tech": "rust_axum", "message": "skipped 2 files: src/gen.rs, src/vendor.rs; first error: ts_parser_parse_string returned null (timed out after 10000ms, or out of memory)" }
]
}
Skipped files are tallied per tech rather than listed one entry each, with up to five example paths, so a broken checkout cannot flood the report.
The key is always present. "errors": [] is the positive statement that every selected analyzer ran to completion over every file it was given.
-f yaml carries the same key, and -f sarif reports it as runs[0].invocations[0].executionSuccessful. Add --strict to make a degraded scan exit with code 2, after the report has been written:
noir scan . -f json --no-log --strict > endpoints.json
JSONL Output
JSON Lines prints one JSON object per line. Ideal for jq pipelines or processing large result sets line-by-line without loading everything into memory.
noir scan . -f jsonl --no-log
Each line is a self-contained endpoint object:
{"callees":[],"url":"/","method":"GET","internal":false,"details":{"code_paths":[{"path":"src/testapp.cr","line":3}],"technology":"crystal_kemal"},"protocol":"http","kind":"","tags":[],"params":[{"name":"x-api-key","value":"","param_type":"header","tags":[]}]}
{"callees":[],"url":"/query","method":"POST","internal":false,"details":{"code_paths":[{"path":"src/testapp.cr","line":17}],"technology":"crystal_kemal"},"protocol":"http","kind":"","tags":[],"params":[{"name":"my_auth","value":"","param_type":"cookie","tags":[]},{"name":"query","value":"","param_type":"form","tags":[]}]}