Skip to content

Commit 3a3a5c8

Browse files
authored
feat: remote auditing (#230)
1 parent b8cfd38 commit 3a3a5c8

25 files changed

+586
-187
lines changed

Diff for: Cargo.lock

+10
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Diff for: Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ rust-version = "1.80.1"
1616
annotate-snippets = "0.11.4"
1717
anstream = "0.6.18"
1818
anyhow = "1.0.93"
19+
camino = { version = "1.1.9", features = ["serde1"] }
1920
clap = { version = "4.5.21", features = ["derive", "env"] }
2021
clap-verbosity-flag = "3.0.0"
2122
env_logger = "0.11.5"

Diff for: Makefile

+1-1
Original file line numberDiff line numberDiff line change
@@ -19,4 +19,4 @@ $(VENV): site-requirements.txt
1919

2020
.PHONY: snippets
2121
snippets:
22-
cargo run -- --help > docs/snippets/help.txt
22+
cargo run -- -h > docs/snippets/help.txt

Diff for: docs/quickstart.md

+57-21
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,73 @@
11
# Quickstart
22

3-
First, run `zizmor --help` to make sure your installation succeeded.
3+
First, run `zizmor -h` to make sure your installation succeeded.
44

55
You should see something like this:
66

77
```console
88
--8<-- "help.txt"
99
```
1010

11+
!!! tip
12+
13+
Run `zizmor --help` for a longer and more detailed version of `zizmor -h`.
14+
1115
## Running `zizmor`
1216

13-
You can run `zizmor` on any file(s) you have locally:
17+
Here are some different ways you can run `zizmor` locally:
1418

15-
```bash
16-
# audit a specific workflow
17-
zizmor my-workflow.yml
18-
# discovers .github/workflows/*.yml automatically
19-
zizmor path/to/repo
20-
```
19+
=== "On one or more files"
2120

22-
By default, `zizmor` will emit Rust-style diagnostics, e.g.:
21+
You can run `zizmor` on one or more workflows as explicit inputs:
2322

24-
```console
25-
error[pull-request-target]: use of fundamentally insecure workflow trigger
26-
--> /home/william/devel/gha-hazmat/.github/workflows/pull-request-target.yml:20:1
27-
|
28-
20 | / on:
29-
21 | | # NOT OK: pull_request_target should almost never be used
30-
22 | | pull_request_target:
31-
| |______________________^ triggers include pull_request_target, which is almost always used insecurely
32-
|
33-
34-
1 findings (0 unknown, 0 informational, 0 low, 0 medium, 1 high)
35-
```
23+
```bash
24+
zizmor ci.yml tests.yml lint.yml
25+
```
26+
27+
These can be in any directory as well:
28+
29+
```
30+
zizmor ./subdir/ci.yml ../sibling/tests.yml
31+
```
32+
33+
=== "On one or more directories"
34+
35+
If you have multiple workflows in a single directory, `zizmor` will
36+
discover them:
37+
38+
```bash
39+
# somewhere/ contains ci.yml and tests.yml
40+
zizmor somewhere/
41+
```
42+
43+
Moreover, if the specified directory contains a `.github/workflows`
44+
subdirectory, `zizmor` will discover workflows there:
45+
46+
```bash
47+
# my-local-repo/ contains .github/workflows/{ci,tests}.yml
48+
zizmor my-local-repo/
49+
```
50+
51+
=== "On one or more remote repositories"
52+
53+
!!! tip
54+
55+
Private repositories can also be audited remotely, as long
56+
as your GitHub API token has sufficient permissions.
57+
58+
`zizmor` can also fetch workflows directly from GitHub, if given a
59+
GitHub API token via `GH_TOKEN` or `--gh-token`:
60+
61+
```bash
62+
# audit all workflows in woodruffw/zizmor
63+
# assumes you have `gh` installed
64+
zizmor --gh-token=$(gh auth token) woodruffw/zizmor
65+
```
66+
67+
Multiple repositories will also work:
68+
69+
```bash
70+
zizmor --gh-token=$(gh auth token) woodruffw/zizmor woodruffw/gha-hazmat
71+
```
3672

3773
See [Usage](./usage.md) for more examples, including examples of configuration.

Diff for: docs/snippets/help.txt

+12-43
Original file line numberDiff line numberDiff line change
@@ -3,69 +3,38 @@ Static analysis for GitHub Actions
33
Usage: zizmor [OPTIONS] <INPUTS>...
44

55
Arguments:
6-
<INPUTS>...
7-
The workflow filenames or directories to audit
6+
<INPUTS>... The inputs to audit
87

98
Options:
109
-p, --pedantic
11-
Emit 'pedantic' findings.
12-
13-
This is an alias for --persona=pedantic.
14-
10+
Emit 'pedantic' findings
1511
--persona <PERSONA>
16-
The persona to use while auditing
17-
18-
[default: regular]
19-
20-
Possible values:
21-
- auditor: The "auditor" persona (false positives OK)
22-
- pedantic: The "pedantic" persona (code smells OK)
23-
- regular: The "regular" persona (minimal false positives)
24-
12+
The persona to use while auditing [default: regular] [possible values: auditor, pedantic, regular]
2513
-o, --offline
26-
Only perform audits that don't require network access
27-
14+
Perform only offline operations [env: ZIZMOR_OFFLINE=]
15+
--gh-token <GH_TOKEN>
16+
The GitHub API token to use [env: GH_TOKEN=]
17+
--no-online-audits
18+
Perform only offline audits [env: ZIZMOR_NO_ONLINE_AUDITS=]
2819
-v, --verbose...
2920
Increase logging verbosity
30-
3121
-q, --quiet...
3222
Decrease logging verbosity
33-
3423
-n, --no-progress
3524
Disable the progress bar. This is useful primarily when running with a high verbosity level, as the two will fight for stderr
36-
37-
--gh-token <GH_TOKEN>
38-
The GitHub API token to use
39-
40-
[env: GH_TOKEN=]
41-
4225
--format <FORMAT>
43-
The output format to emit. By default, plain text will be emitted
44-
45-
[default: plain]
46-
[possible values: plain, json, sarif]
47-
26+
The output format to emit. By default, plain text will be emitted [default: plain] [possible values: plain, json, sarif]
4827
-c, --config <CONFIG>
4928
The configuration file to load. By default, any config will be discovered relative to $CWD
50-
5129
--no-config
5230
Disable all configuration loading
53-
5431
--no-exit-codes
5532
Disable all error codes besides success and tool failure
56-
5733
--min-severity <MIN_SEVERITY>
58-
Filter all results below this severity
59-
60-
[possible values: unknown, informational, low, medium, high]
61-
34+
Filter all results below this severity [possible values: unknown, informational, low, medium, high]
6235
--min-confidence <MIN_CONFIDENCE>
63-
Filter all results below this confidence
64-
65-
[possible values: unknown, low, medium, high]
66-
36+
Filter all results below this confidence [possible values: unknown, low, medium, high]
6737
-h, --help
68-
Print help (see a summary with '-h')
69-
38+
Print help (see more with '--help')
7039
-V, --version
7140
Print version

Diff for: docs/usage.md

+8-3
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,15 @@ Both of these can be made explicit through their respective command-line flags:
1515

1616
```bash
1717
# force offline, even if a GH_TOKEN is present
18+
# this disables all online actions, including repository fetches
1819
zizmor --offline workflow.yml
1920

20-
# passing a token explicitly will forcefully enable online mode
21+
# passing a token explicitly will enable online mode
2122
zizmor --gh-token ghp-... workflow.yml
23+
24+
# online for the purpose of fetching the input (example/example),
25+
# but all audits themselves are offline
26+
zizmor --no-online-audits --gh-token ghp-... example/example
2227
```
2328

2429
## Output formats
@@ -317,7 +322,7 @@ jobs:
317322
1. Optional: Remove the `env:` block to only run `zizmor`'s offline audits.
318323

319324
For more inspiration, see `zizmor`'s own [repository workflow scan], as well
320-
as GitHub's example of [running ESLint] as a security workflow.
325+
as GitHub's example of [running ESLint] as a security workflow.
321326

322327
[SARIF]: https://sarifweb.azurewebsites.net/
323328

@@ -336,7 +341,7 @@ To do so, add the following to your `.pre-commit-config.yaml` `repos` section:
336341

337342
```yaml
338343
- repo: https://github.com/woodruffw/zizmor
339-
rev: v0.3.0 # (1)!
344+
rev: v0.7.0 # (1)!
340345
hooks:
341346
- id: zizmor
342347
```

Diff for: src/audit/github_env.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -172,7 +172,7 @@ mod tests {
172172
("something | tee \"${$OTHER_ENV}\" # not $GITHUB_ENV", false),
173173
] {
174174
let audit_state = AuditState {
175-
offline: false,
175+
no_online_audits: false,
176176
gh_token: None,
177177
caches: Caches::new(),
178178
};

Diff for: src/audit/impostor_commit.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ impl ImpostorCommit {
116116

117117
impl WorkflowAudit for ImpostorCommit {
118118
fn new(state: AuditState) -> Result<Self> {
119-
if state.offline {
119+
if state.no_online_audits {
120120
return Err(anyhow!("offline audits only requested"));
121121
}
122122

Diff for: src/audit/known_vulnerable_actions.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,7 @@ impl WorkflowAudit for KnownVulnerableActions {
125125
where
126126
Self: Sized,
127127
{
128-
if state.offline {
128+
if state.no_online_audits {
129129
return Err(anyhow!("offline audits only requested"));
130130
}
131131

Diff for: src/audit/ref_confusion.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ impl WorkflowAudit for RefConfusion {
6161
where
6262
Self: Sized,
6363
{
64-
if state.offline {
64+
if state.no_online_audits {
6565
return Err(anyhow!("offline audits only requested"));
6666
}
6767

Diff for: src/config.rs

+4-1
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,10 @@ impl Config {
123123
// multiple files, as the first location is the one a user will
124124
// typically ignore, suppressing the rest in the process.
125125
for loc in &finding.locations {
126-
for rule in ignores.iter().filter(|i| i.filename == loc.symbolic.name) {
126+
for rule in ignores
127+
.iter()
128+
.filter(|i| i.filename == loc.symbolic.key.filename())
129+
{
127130
match rule {
128131
// Rule has a line and (maybe) a column.
129132
WorkflowRule {

Diff for: src/finding/mod.rs

+7-4
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,10 @@ use regex::Regex;
99
use serde::Serialize;
1010
use terminal_link::Link;
1111

12-
use crate::models::{Job, Step, Workflow};
12+
use crate::{
13+
models::{Job, Step, Workflow},
14+
registry::WorkflowKey,
15+
};
1316

1417
pub(crate) mod locate;
1518

@@ -120,8 +123,8 @@ impl<'w> Route<'w> {
120123
/// Represents a symbolic workflow location.
121124
#[derive(Serialize, Clone, Debug)]
122125
pub(crate) struct SymbolicLocation<'w> {
123-
/// The name of the workflow, as it appears in the workflow registry.
124-
pub(crate) name: &'w str,
126+
/// The unique ID of the workflow, as it appears in the workflow registry.
127+
pub(crate) key: &'w WorkflowKey,
125128

126129
/// An annotation for this location.
127130
pub(crate) annotation: String,
@@ -139,7 +142,7 @@ pub(crate) struct SymbolicLocation<'w> {
139142
impl<'w> SymbolicLocation<'w> {
140143
pub(crate) fn with_keys(&self, keys: &[RouteComponent<'w>]) -> SymbolicLocation<'w> {
141144
SymbolicLocation {
142-
name: self.name,
145+
key: self.key,
143146
annotation: self.annotation.clone(),
144147
link: None,
145148
route: self.route.with_keys(keys),

0 commit comments

Comments
 (0)