mirror of
https://github.com/zizmorcore/zizmor.git
synced 2025-12-23 08:47:33 +00:00
feat: add --min-severity (#193)
This commit is contained in:
parent
423fe7c4a0
commit
93b866fea4
5 changed files with 74 additions and 30 deletions
|
|
@ -6,15 +6,16 @@ Arguments:
|
|||
<INPUTS>... The workflow filenames or directories to audit
|
||||
|
||||
Options:
|
||||
-p, --pedantic Emit findings even when the context suggests an explicit security decision made by the user
|
||||
-o, --offline Only perform audits that don't require network access
|
||||
-v, --verbose... Increase logging verbosity
|
||||
-q, --quiet... Decrease logging verbosity
|
||||
-n, --no-progress Disable the progress bar. This is useful primarily when running with a high verbosity level, as the two will fight for stderr
|
||||
--gh-token <GH_TOKEN> The GitHub API token to use [env: GH_TOKEN=]
|
||||
--format <FORMAT> The output format to emit. By default, plain text will be emitted [possible values: plain, json, sarif]
|
||||
-c, --config <CONFIG> The configuration file to load. By default, any config will be discovered relative to $CWD
|
||||
--no-config Disable all configuration loading
|
||||
--no-exit-codes Disable all error codes besides success and tool failure
|
||||
-h, --help Print help
|
||||
-V, --version Print version
|
||||
-p, --pedantic Emit findings even when the context suggests an explicit security decision made by the user
|
||||
-o, --offline Only perform audits that don't require network access
|
||||
-v, --verbose... Increase logging verbosity
|
||||
-q, --quiet... Decrease logging verbosity
|
||||
-n, --no-progress Disable the progress bar. This is useful primarily when running with a high verbosity level, as the two will fight for stderr
|
||||
--gh-token <GH_TOKEN> The GitHub API token to use [env: GH_TOKEN=]
|
||||
--format <FORMAT> The output format to emit. By default, plain text will be emitted [possible values: plain, json, sarif]
|
||||
-c, --config <CONFIG> The configuration file to load. By default, any config will be discovered relative to $CWD
|
||||
--no-config Disable all configuration loading
|
||||
--no-exit-codes Disable all error codes besides success and tool failure
|
||||
--min-severity <MIN_SEVERITY> Filter all results below this severity [possible values: unknown, informational, low, medium, high]
|
||||
-h, --help Print help
|
||||
-V, --version Print version
|
||||
|
|
|
|||
|
|
@ -65,6 +65,33 @@ See [Integration](#integration) for suggestions on when to use each format.
|
|||
|
||||
All other exit codes are currently reserved.
|
||||
|
||||
## Filtering results
|
||||
|
||||
There are two straightforward ways to filter `zizmor`'s results:
|
||||
|
||||
1. If all you need is severity filtering (e.g. "I want only medium-severity
|
||||
and above results"), then you can use the `--min-severity` flag:
|
||||
|
||||
!!! tip
|
||||
|
||||
`--min-severity` is available in `v0.6.0` and later.
|
||||
|
||||
```bash
|
||||
# filter unknown, informational, and low findings
|
||||
zizmor --min-severity=medium ...
|
||||
```
|
||||
|
||||
2. If you need more advanced filtering (with nontrivial conditions or
|
||||
state considerations), then consider using `--format=json` and using
|
||||
`jq` (or a script) to perform your filtering.
|
||||
|
||||
As a starting point, here's how you can use `jq` to filter `zizmor`'s
|
||||
JSON output to only results that are marked as "high confidence":
|
||||
|
||||
```bash
|
||||
zizmor --format=json ... | jq 'map(select(.determinations.confidence == "High"))'
|
||||
```
|
||||
|
||||
## Ignoring results
|
||||
|
||||
`zizmor`'s defaults are not always 100% right for every possible use case.
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
use std::borrow::Cow;
|
||||
|
||||
use anyhow::Result;
|
||||
use clap::ValueEnum;
|
||||
use locate::Locator;
|
||||
use serde::Serialize;
|
||||
use terminal_link::Link;
|
||||
|
|
@ -13,7 +14,7 @@ pub(crate) mod locate;
|
|||
|
||||
// TODO: Traits + more flexible models here.
|
||||
|
||||
#[derive(Copy, Clone, Debug, Default, Eq, Hash, PartialEq, Serialize)]
|
||||
#[derive(Copy, Clone, Debug, Default, Eq, Hash, PartialEq, Serialize, ValueEnum)]
|
||||
pub(crate) enum Confidence {
|
||||
#[default]
|
||||
Unknown,
|
||||
|
|
@ -22,7 +23,9 @@ pub(crate) enum Confidence {
|
|||
High,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Default, Eq, Hash, Ord, PartialOrd, PartialEq, Serialize)]
|
||||
#[derive(
|
||||
Copy, Clone, Debug, Default, Eq, Hash, Ord, PartialOrd, PartialEq, Serialize, ValueEnum,
|
||||
)]
|
||||
pub(crate) enum Severity {
|
||||
#[default]
|
||||
Unknown,
|
||||
|
|
|
|||
23
src/main.rs
23
src/main.rs
|
|
@ -5,6 +5,7 @@ use anyhow::{anyhow, Context, Result};
|
|||
use audit::WorkflowAudit;
|
||||
use clap::{Parser, ValueEnum};
|
||||
use config::Config;
|
||||
use finding::Severity;
|
||||
use indicatif::{ProgressBar, ProgressDrawTarget, ProgressStyle};
|
||||
use owo_colors::OwoColorize;
|
||||
use registry::{AuditRegistry, FindingRegistry, WorkflowRegistry};
|
||||
|
|
@ -63,6 +64,10 @@ struct App {
|
|||
#[arg(long)]
|
||||
no_exit_codes: bool,
|
||||
|
||||
/// Filter all results below this severity.
|
||||
#[arg(long)]
|
||||
min_severity: Option<Severity>,
|
||||
|
||||
/// The workflow filenames or directories to audit.
|
||||
#[arg(required = true)]
|
||||
inputs: Vec<PathBuf>,
|
||||
|
|
@ -78,14 +83,14 @@ pub(crate) enum OutputFormat {
|
|||
fn run() -> Result<ExitCode> {
|
||||
human_panic::setup_panic!();
|
||||
|
||||
let args = App::parse();
|
||||
let app = App::parse();
|
||||
|
||||
env_logger::Builder::new()
|
||||
.filter_level(args.verbose.log_level_filter())
|
||||
.filter_level(app.verbose.log_level_filter())
|
||||
.init();
|
||||
|
||||
let mut workflow_paths = vec![];
|
||||
for input in &args.inputs {
|
||||
for input in &app.inputs {
|
||||
if input.is_file() {
|
||||
workflow_paths.push(input.clone());
|
||||
} else if input.is_dir() {
|
||||
|
|
@ -121,8 +126,8 @@ fn run() -> Result<ExitCode> {
|
|||
workflows = workflow_paths
|
||||
);
|
||||
|
||||
let config = Config::new(&args)?;
|
||||
let audit_state = AuditState::new(&args);
|
||||
let config = Config::new(&app)?;
|
||||
let audit_state = AuditState::new(&app);
|
||||
|
||||
let mut workflow_registry = WorkflowRegistry::new();
|
||||
for workflow_path in workflow_paths.iter() {
|
||||
|
|
@ -161,7 +166,7 @@ fn run() -> Result<ExitCode> {
|
|||
|
||||
// Hide the bar if the user has explicitly asked for quiet output
|
||||
// or to disable just the progress bar.
|
||||
if args.verbose.is_silent() || args.no_progress {
|
||||
if app.verbose.is_silent() || app.no_progress {
|
||||
bar.set_draw_target(ProgressDrawTarget::hidden());
|
||||
} else {
|
||||
bar.enable_steady_tick(Duration::from_millis(100));
|
||||
|
|
@ -170,7 +175,7 @@ fn run() -> Result<ExitCode> {
|
|||
);
|
||||
}
|
||||
|
||||
let mut results = FindingRegistry::new(&config);
|
||||
let mut results = FindingRegistry::new(&app, &config);
|
||||
for (_, workflow) in workflow_registry.iter_workflows() {
|
||||
bar.set_message(format!(
|
||||
"auditing {workflow}",
|
||||
|
|
@ -193,7 +198,7 @@ fn run() -> Result<ExitCode> {
|
|||
|
||||
bar.finish_and_clear();
|
||||
|
||||
let format = match args.format {
|
||||
let format = match app.format {
|
||||
None => OutputFormat::Plain,
|
||||
Some(f) => f,
|
||||
};
|
||||
|
|
@ -207,7 +212,7 @@ fn run() -> Result<ExitCode> {
|
|||
)?,
|
||||
};
|
||||
|
||||
if args.no_exit_codes || matches!(format, OutputFormat::Sarif) {
|
||||
if app.no_exit_codes || matches!(format, OutputFormat::Sarif) {
|
||||
Ok(ExitCode::SUCCESS)
|
||||
} else {
|
||||
Ok(results.into())
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ use crate::{
|
|||
config::Config,
|
||||
finding::{Finding, Severity},
|
||||
models::Workflow,
|
||||
App,
|
||||
};
|
||||
use anyhow::{anyhow, Context, Result};
|
||||
|
||||
|
|
@ -111,18 +112,20 @@ impl AuditRegistry {
|
|||
/// A registry of all findings discovered during a `zizmor` run.
|
||||
pub(crate) struct FindingRegistry<'a> {
|
||||
config: &'a Config,
|
||||
minimum_severity: Option<Severity>,
|
||||
ignored: Vec<Finding<'a>>,
|
||||
findings: Vec<Finding<'a>>,
|
||||
highest_severity: Option<Severity>,
|
||||
highest_seen_severity: Option<Severity>,
|
||||
}
|
||||
|
||||
impl<'a> FindingRegistry<'a> {
|
||||
pub(crate) fn new(config: &'a Config) -> Self {
|
||||
pub(crate) fn new(app: &App, config: &'a Config) -> Self {
|
||||
Self {
|
||||
config,
|
||||
minimum_severity: app.min_severity,
|
||||
ignored: Default::default(),
|
||||
findings: Default::default(),
|
||||
highest_severity: None,
|
||||
highest_seen_severity: None,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -132,14 +135,19 @@ impl<'a> FindingRegistry<'a> {
|
|||
// TODO: is it faster to iterate like this, or do `find_by_max`
|
||||
// and then `extend`?
|
||||
for finding in results {
|
||||
if self.config.ignores(&finding) || finding.ignored {
|
||||
if finding.ignored
|
||||
|| self
|
||||
.minimum_severity
|
||||
.map_or(false, |min| min > finding.determinations.severity)
|
||||
|| self.config.ignores(&finding)
|
||||
{
|
||||
self.ignored.push(finding);
|
||||
} else {
|
||||
if self
|
||||
.highest_severity
|
||||
.highest_seen_severity
|
||||
.map_or(true, |s| finding.determinations.severity > s)
|
||||
{
|
||||
self.highest_severity = Some(finding.determinations.severity);
|
||||
self.highest_seen_severity = Some(finding.determinations.severity);
|
||||
}
|
||||
|
||||
self.findings.push(finding);
|
||||
|
|
@ -160,7 +168,7 @@ impl<'a> FindingRegistry<'a> {
|
|||
|
||||
impl From<FindingRegistry<'_>> for ExitCode {
|
||||
fn from(value: FindingRegistry<'_>) -> Self {
|
||||
match value.highest_severity {
|
||||
match value.highest_seen_severity {
|
||||
Some(sev) => match sev {
|
||||
Severity::Unknown => ExitCode::from(10),
|
||||
Severity::Informational => ExitCode::from(11),
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue