# secreport
[](https://opensource.org/licenses/MIT) [](https://img.shields.io/badge/tests-21%20passing-brightgreen.svg) [](https://crates.io/crates/secreport)
Render security findings into text, JSON, JSONL, SARIF, and Markdown.
## Why
Every scanner outputs findings in a different shape, but operators need consistent output for dashboards, tickets, and review. `secreport` standardizes that final step with pluggable formats: text, JSON, JSONL, SARIF, and Markdown.
It accepts both the canonical `secfinding::Finding` and any type that implements `Reportable`, so you can keep custom data models while still producing unified artifacts.
## Quick Start
```rust
use secfinding::{Finding, Severity};
use secreport::{render, emit, Format};
fn main() -> std::io::Result<()> {
let findings = vec![
Finding::new(
"santh-cli",
"https://example.com/login",
Severity::High,
"Potential SQL injection",
"Untrusted input reaches query context",
),
];
let text = render(&findings, Format::Text, "demo-scan");
emit(&text, None)
}
```
## Features
- Shared render entry points: `render` for `Finding`, `render_any` for any `Reportable` type.
- Formats: `Text`, `Json`, `Jsonl`, `Sarif`, `Markdown`.
- `emit` for stdout/file emission in one call.
- Human-friendly colorized text summaries plus severity breakdown.
- Reusable with any type implementing `Reportable`, including custom `rule_id`, CWE/CVE lists, confidence, tags, and exploit hints.
## TOML Configuration
`secreport` does not use TOML configuration.
## API Overview
- `Format`: output format selector.
- `render`: format `Vec<Finding>`.
- `render_any`: format generic `Reportable` types.
- `emit`: print to stdout or write file path.
## Examples
### 1) Produce JSON for API pipelines
```rust
use secfinding::Finding;
use secreport::{render, Format};
let findings: Vec<Finding> = Vec::new();
let json = render(&findings, Format::Json, "scanner");
println!("{}", json);
```
### 2) Build Markdown report for issue triage
```rust
use secfinding::{Finding, Severity};
use secreport::{emit, render, Format};
let findings = vec![
Finding::new("scanner", "https://target", Severity::Critical, "RCE", "eval on tainted input")
];
let md = render(&findings, Format::Markdown, "nightly-run");
emit(&md, Some("./run-report.md")).unwrap();
```
### 3) Render your own finding type
```rust
use secreport::{render_any, Format};
use secfinding::{Reportable, Severity};
struct CustomFinding {
target: String,
score: f64,
}
impl Reportable for CustomFinding {
fn scanner(&self) -> &str { "custom" }
fn target(&self) -> &str { &self.target }
fn severity(&self) -> Severity { if self.score > 0.8 { Severity::High } else { Severity::Low } }
fn title(&self) -> &str { "Custom rule" }
}
let findings = vec![CustomFinding { target: "https://example.com".into(), score: 0.93 }];
let out = render_any(&findings, Format::Text, "custom-scan");
println!("{out}");
```
### 4) Run the bundled examples
```bash
cargo run --example basic
cargo run --example custom_reportable
```
## Traits
`secreport` does not define traits, but your reporting integration usually depends on `secfinding::Reportable` (rendered through `render_any`).
## Related Crates
- [secfinding](https://docs.rs/secfinding)
- [scanstate](https://docs.rs/scanstate)
- [codewalk](https://docs.rs/codewalk)
## License
MIT, Corum Collective LLC
Docs: https://docs.rs/secreport
Santh ecosystem: https://santh.io