feat: add --min-severity (#193)

This commit is contained in:
William Woodruff 2024-11-24 13:11:07 -05:00 committed by GitHub
parent 423fe7c4a0
commit 93b866fea4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 74 additions and 30 deletions

View file

@ -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

View file

@ -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.

View file

@ -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,

View file

@ -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())

View file

@ -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),