Repository: woodruffw/zizmor (rust)

Issue:
[BUG]: couldn't parse expression
### Pre-submission checks

- [x] I am **not** filing a feature request. These should be filed via the feature request form instead.
- [x] I have looked through the [open issues](https://github.com/woodruffw/zizmor/issues?q=is%3Aissue+is%3Aopen+) for a duplicate report.

### Expected behavior

I expect zizmor not to give the following parse warnings on our admittedly-complex workflow files.

### Actual behavior

```console
❯ zizmor .
 INFO zizmor: skipping impostor-commit: can't run without a GitHub API token
 INFO zizmor: skipping ref-confusion: can't run without a GitHub API token
 INFO zizmor: skipping known-vulnerable-actions: can't run without a GitHub API token
 INFO audit: zizmor: 🌈 completed .github/workflows/reusable-windows.yml
 INFO audit: zizmor: 🌈 completed .github/workflows/reusable-ubuntu.yml
 INFO audit: zizmor: 🌈 completed .github/workflows/require-pr-label.yml
 INFO audit: zizmor: 🌈 completed .github/workflows/new-bugs-announce-notifier.yml
 INFO audit: zizmor: 🌈 completed .github/workflows/lint.yml
 INFO audit: zizmor: 🌈 completed .github/workflows/tail-call.yml
 INFO audit: zizmor: 🌈 completed .github/workflows/verify-ensurepip-wheels.yml
 INFO audit: zizmor: 🌈 completed .github/workflows/jit.yml
 INFO audit: zizmor: 🌈 completed .github/workflows/reusable-macos.yml
 INFO audit: zizmor: 🌈 completed .github/workflows/add-issue-header.yml
 INFO audit: zizmor: 🌈 completed .github/workflows/stale.yml
 WARN audit:audit{input=Workflow(file://./.github/workflows/reusable-context.yml)}: zizmor::audit::overprovisioned_secrets: couldn't parse expression: #        fromJSON(needs.build-context.outputs.run-tests)
      #        && 'truthy-branch'
      #        || 'falsy-branch'
      #
 WARN audit:audit{input=Workflow(file://./.github/workflows/reusable-context.yml)}: zizmor::audit::unredacted_secrets: couldn't parse expression: #        fromJSON(needs.build-context.outputs.run-tests)
      #        && 'truthy-branch'
      #        || 'falsy-branch'
      #
 INFO audit: zizmor: 🌈 completed .github/workflows/reusable-context.yml
 INFO audit: zizmor: 🌈 completed .github/workflows/reusable-docs.yml
 INFO audit: zizmor: 🌈 completed .github/workflows/reusable-tsan.yml
 INFO audit: zizmor: 🌈 completed .github/workflows/mypy.yml
 INFO audit: zizmor: 🌈 completed .github/workflows/reusable-windows-msi.yml
 INFO audit: zizmor: 🌈 completed .github/workflows/documentation-links.yml
 INFO audit: zizmor: 🌈 completed .github/workflows/reusable-wasi.yml
 INFO audit: zizmor: 🌈 completed .github/workflows/project-updater.yml
 WARN audit:audit{input=Workflow(file://./.github/workflows/build.yml)}: zizmor::audit::overprovisioned_secrets: couldn't parse expression: #        fromJSON(needs.build-context.outputs.run-tests)
    #        && 'truthy-branch'
    #        || 'falsy-branch'
    #
 WARN audit:audit{input=Workflow(file://./.github/workflows/build.yml)}: zizmor::audit::overprovisioned_secrets: couldn't parse expression: '' } is a hack to nest jobs under the same sidebar category
      Windows MSI${{ ''
 WARN audit:audit{input=Workflow(file://./.github/workflows/build.yml)}: zizmor::audit::unredacted_secrets: couldn't parse expression: #        fromJSON(needs.build-context.outputs.run-tests)
    #        && 'truthy-branch'
    #        || 'falsy-branch'
    #
 WARN audit:audit{input=Workflow(file://./.github/workflows/build.yml)}: zizmor::audit::unredacted_secrets: couldn't parse expression: '' } is a hack to nest jobs under the same sidebar category
      Windows MSI${{ ''
 INFO audit: zizmor: 🌈 completed .github/workflows/build.yml
No findings to report. Good job! (1 ignored, 103 suppressed)
```


### Reproduction steps

```
git clone https://github.com/python/cpython # currently f963239ff1f986742d4c6bab2ab7b73f5a4047f6
cd cpython
zizmor .
```


### Logs

```plain text
❯ zizmor . --verbose
DEBUG zizmor::state: using cache directory: "/Users/hugo/.cache/zizmor"
DEBUG zizmor::config: loaded config: Config { rules: {"dangerous-triggers": AuditRuleConfig { ignore: [WorkflowRule { filename: "documentation-links.yml", line: None, column: None }] }} }
 INFO zizmor: skipping impostor-commit: can't run without a GitHub API token
 INFO zizmor: skipping ref-confusion: can't run without a GitHub API token
 INFO zizmor: skipping known-vulnerable-actions: can't run without a GitHub API token
 INFO audit: zizmor: 🌈 completed .github/workflows/reusable-windows.yml
 INFO audit: zizmor: 🌈 completed .github/workflows/reusable-ubuntu.yml
 INFO audit: zizmor: 🌈 completed .github/workflows/require-pr-label.yml
 INFO audit: zizmor: 🌈 completed .github/workflows/new-bugs-announce-notifier.yml
 INFO audit: zizmor: 🌈 completed .github/workflows/lint.yml
 INFO audit: zizmor: 🌈 completed .github/workflows/tail-call.yml
 INFO audit: zizmor: 🌈 completed .github/workflows/verify-ensurepip-wheels.yml
 INFO audit: zizmor: 🌈 completed .github/workflows/jit.yml
 INFO audit: zizmor: 🌈 completed .github/workflows/reusable-macos.yml
 INFO audit: zizmor: 🌈 completed .github/workflows/add-issue-header.yml
 INFO audit: zizmor: 🌈 completed .github/workflows/stale.yml
 WARN audit:audit{input=Workflow(file://./.github/workflows/reusable-context.yml)}: zizmor::audit::overprovisioned_secrets: couldn't parse expression: #        fromJSON(needs.build-context.outputs.run-tests)
      #        && 'truthy-branch'
      #        || 'falsy-branch'
      #
 WARN audit:audit{input=Workflow(file://./.github/workflows/reusable-context.yml)}: zizmor::audit::unredacted_secrets: couldn't parse expression: #        fromJSON(needs.build-context.outputs.run-tests)
      #        && 'truthy-branch'
      #        || 'falsy-branch'
      #
 INFO audit: zizmor: 🌈 completed .github/workflows/reusable-context.yml
 INFO audit: zizmor: 🌈 completed .github/workflows/reusable-docs.yml
 INFO audit: zizmor: 🌈 completed .github/workflows/reusable-tsan.yml
 INFO audit: zizmor: 🌈 completed .github/workflows/mypy.yml
 INFO audit: zizmor: 🌈 completed .github/workflows/reusable-windows-msi.yml
 INFO audit: zizmor: 🌈 completed .github/workflows/documentation-links.yml
 INFO audit: zizmor: 🌈 completed .github/workflows/reusable-wasi.yml
 INFO audit: zizmor: 🌈 completed .github/workflows/project-updater.yml
 WARN audit:audit{input=Workflow(file://./.github/workflows/build.yml)}: zizmor::audit::overprovisioned_secrets: couldn't parse expression: #        fromJSON(needs.build-context.outputs.run-tests)
    #        && 'truthy-branch'
    #        || 'falsy-branch'
    #
 WARN audit:audit{input=Workflow(file://./.github/workflows/build.yml)}: zizmor::audit::overprovisioned_secrets: couldn't parse expression: '' } is a hack to nest jobs under the same sidebar category
      Windows MSI${{ ''
 WARN audit:audit{input=Workflow(file://./.github/workflows/build.yml)}: zizmor::audit::unredacted_secrets: couldn't parse expression: #        fromJSON(needs.build-context.outputs.run-tests)
    #        && 'truthy-branch'
    #        || 'falsy-branch'
    #
 WARN audit:audit{input=Workflow(file://./.github/workflows/build.yml)}: zizmor::audit::unredacted_secrets: couldn't parse expression: '' } is a hack to nest jobs under the same sidebar category
      Windows MSI${{ ''
 INFO audit: zizmor: 🌈 completed .github/workflows/build.yml
No findings to report. Good job! (1 ignored, 103 suppressed)
```

### Additional context

_No response_

Pull request:
bugfix: skip commented-out expressions
~~WIP; I need to think about the right way to approach this.~~

Fixes #569.

Patch:
diff --git a/Cargo.lock b/Cargo.lock
index 2e29f71..57bf7b6 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1860,6 +1860,7 @@ version = "1.0.140"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373"
 dependencies = [
+ "indexmap",
  "itoa",
  "memchr",
  "ryu",
@@ -2463,13 +2464,14 @@ dependencies = [
 
 [[package]]
 name = "tree-sitter"
-version = "0.24.7"
+version = "0.25.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a5387dffa7ffc7d2dae12b50c6f7aab8ff79d6210147c6613561fc3d474c6f75"
+checksum = "b9ac5ea5e7f2f1700842ec071401010b9c59bf735295f6e9fa079c3dc035b167"
 dependencies = [
  "cc",
  "regex",
  "regex-syntax 0.8.5",
+ "serde_json",
  "streaming-iterator",
  "tree-sitter-language",
 ]
@@ -2492,12 +2494,12 @@ checksum = "e8ddffe35a0e5eeeadf13ff7350af564c6e73993a24db62caee1822b185c2600"
 
 [[package]]
 name = "tree-sitter-powershell"
-version = "0.24.5"
+version = "0.25.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1b6e2ce7339aafe36e69428d91d295cdf4ef6ba1ee5cc5dc8715dcbbc417cbc3"
+checksum = "377974a9bbd11ef11aa298d60def669f78b579d11745066a59bc4167e53d360b"
 dependencies = [
  "cc",
- "tree-sitter",
+ "tree-sitter-language",
 ]
 
 [[package]]
@@ -3021,9 +3023,9 @@ checksum = "6a5cbf750400958819fb6178eaa83bee5cd9c29a26a40cc241df8c70fdd46984"
 
 [[package]]
 name = "yamlpath"
-version = "0.14.0"
+version = "0.15.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e41a4009f1260c7a08b722f096be598c00e3e0aa13f9bed662282acb5d2dbfa5"
+checksum = "796a3f441fd5a8d00a2dac6ca0ce0f0b07b7e1997e014a32d4f17a9d39fbdc9f"
 dependencies = [
  "thiserror 2.0.9",
  "tree-sitter",
diff --git a/Cargo.toml b/Cargo.toml
index 26ff121..a1f9ea3 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -61,10 +61,10 @@ tokio = { version = "1.44.0", features = ["rt-multi-thread"] }
 tracing = "0.1.41"
 tracing-indicatif = "0.3.9"
 tracing-subscriber = { version = "0.3.19", features = ["env-filter"] }
-tree-sitter = "0.24.7"
+tree-sitter = "0.25.2"
 tree-sitter-bash = "0.23.3"
-tree-sitter-powershell = "0.24.4"
-yamlpath = "0.14.0"
+tree-sitter-powershell = "0.25.2"
+yamlpath = "0.15.0"
 
 [profile.dev.package]
 insta.opt-level = 3
diff --git a/docs/release-notes.md b/docs/release-notes.md
index b158000..565fe0a 100644
--- a/docs/release-notes.md
+++ b/docs/release-notes.md
@@ -9,6 +9,11 @@ of `zizmor`.
 
 ## Next (UNRELEASED)
 
+### Bug Fixes 🐛
+
+* Fixed a bug where `zizmor` would over-eagerly parse invalid and
+  commented-out expressions, resulting in spurious warnings (#570)
+
 ## v1.5.1
 
 ### Bug Fixes 🐛
diff --git a/src/audit/github_env.rs b/src/audit/github_env.rs
index 43b4d0c..9a86922 100644
--- a/src/audit/github_env.rs
+++ b/src/audit/github_env.rs
@@ -349,7 +349,7 @@ impl Audit for GitHubEnv {
             .set_language(&bash)
             .context("failed to load bash parser")?;
 
-        let pwsh = tree_sitter_powershell::language();
+        let pwsh = tree_sitter_powershell::LANGUAGE.into();
         let mut pwsh_parser = Parser::new();
         pwsh_parser
             .set_language(&pwsh)
diff --git a/src/audit/overprovisioned_secrets.rs b/src/audit/overprovisioned_secrets.rs
index e2bd62d..a7dd9fe 100644
--- a/src/audit/overprovisioned_secrets.rs
+++ b/src/audit/overprovisioned_secrets.rs
@@ -1,7 +1,7 @@
 use crate::{
     expr::Expr,
     finding::{Confidence, Feature, Location, Severity},
-    utils::extract_expressions,
+    utils::parse_expressions_from_input,
 };
 
 use super::{Audit, AuditInput, audit_meta};
@@ -24,9 +24,8 @@ impl Audit for OverprovisionedSecrets {
 
     fn audit_raw<'w>(&self, input: &'w AuditInput) -> anyhow::Result<Vec<super::Finding<'w>>> {
         let mut findings = vec![];
-        let raw = input.document().source();
 
-        for (expr, span) in extract_expressions(raw) {
+        for (expr, span) in parse_expressions_from_input(input) {
             let Ok(parsed) = Expr::parse(expr.as_bare()) else {
                 tracing::warn!("couldn't parse expression: {expr}", expr = expr.as_bare());
                 continue;
diff --git a/src/audit/unredacted_secrets.rs b/src/audit/unredacted_secrets.rs
index e1548ff..dfcfeb4 100644
--- a/src/audit/unredacted_secrets.rs
+++ b/src/audit/unredacted_secrets.rs
@@ -2,7 +2,7 @@ use crate::{
     Confidence, Severity,
     expr::{Context, Expr},
     finding::{Feature, Location},
-    utils::extract_expressions,
+    utils::parse_expressions_from_input,
 };
 
 use super::{Audit, audit_meta};
@@ -28,9 +28,8 @@ impl Audit for UnredactedSecrets {
         input: &'w super::AuditInput,
     ) -> anyhow::Result<Vec<crate::finding::Finding<'w>>> {
         let mut findings = vec![];
-        let raw = input.document().source();
 
-        for (expr, span) in extract_expressions(raw) {
+        for (expr, span) in parse_expressions_from_input(input) {
             let Ok(parsed) = Expr::parse(expr.as_bare()) else {
                 tracing::warn!("couldn't parse expression: {expr}", expr = expr.as_bare());
                 continue;
diff --git a/src/utils.rs b/src/utils.rs
index 33fc70a..450b15f 100644
--- a/src/utils.rs
+++ b/src/utils.rs
@@ -8,6 +8,8 @@ use github_actions_models::common::{
     expr::{ExplicitExpr, LoE},
 };
 
+use crate::audit::AuditInput;
+
 /// Convenience trait for inline transformations of `Self`.
 ///
 /// This is similar to the `tap` crate's `Pipe` trait, except that
@@ -89,6 +91,43 @@ pub(crate) fn extract_expressions(text: &str) -> Vec<(ExplicitExpr, Range<usize>
     exprs
 }
 
+/// Like `extract_expressions`, but over an entire audit input (e.g. workflow
+/// or action definition).
+///
+/// Unlike `extract_expressions`, this function performs some semantic
+/// filtering over the raw input. For example, it skip ignore expressions
+/// that are inside comments.
+pub(crate) fn parse_expressions_from_input(
+    input: &AuditInput,
+) -> Vec<(ExplicitExpr, Range<usize>)> {
+    let text = input.document().source();
+    let doc = input.document();
+
+    let mut exprs = vec![];
+    let mut offset = 0;
+
+    while let Some((expr, span)) = extract_expression(text, offset) {
+        // Ignore expressions that are inside comments.
+        if doc.offset_inside_comment(span.start) {
+            // Don't jump the entire span, since we might have an
+            // actual expression accidentally captured within it.
+            // Instead, just resume searching from the next character.
+            offset = span.start + 1;
+            continue;
+        }
+
+        exprs.push((expr, (span.start..span.end)));
+
+        if span.end >= text.len() {
+            break;
+        } else {
+            offset = span.end;
+        }
+    }
+
+    exprs
+}
+
 /// Returns whether the given `env.name` environment access is "static,"
 /// i.e. is not influenced by another expression.
 pub(crate) fn env_is_static(name: &str, envs: &[&LoE<Env>]) -> bool {
@@ -127,7 +166,16 @@ pub(crate) fn normalize_shell(shell: &str) -> &str {
 
 #[cfg(test)]
 mod tests {
-    use crate::utils::{extract_expression, extract_expressions, normalize_shell};
+    use anyhow::Result;
+
+    use crate::{
+        Action,
+        models::Workflow,
+        registry::InputKey,
+        utils::{
+            extract_expression, extract_expressions, normalize_shell, parse_expressions_from_input,
+        },
+    };
 
     #[test]
     fn split_patterns() {
@@ -162,7 +210,7 @@ mod tests {
     }
 
     #[test]
-    fn test_parse_expression() {
+    fn test_extract_expression() {
         let exprs = &[
             ("${{ foo }}", "foo", 0..10),
             ("${{ foo }}${{ bar }}", "foo", 0..10),
@@ -183,21 +231,85 @@ mod tests {
     }
 
     #[test]
-    fn test_parse_expressions() {
-        let expressions = r#"echo "OSSL_PATH=${{ github.workspace }}/osslcache/${{ matrix.PYTHON.OPENSSL.TYPE }}-${{ matrix.PYTHON.OPENSSL.VERSION }}-${OPENSSL_HASH}" >> $GITHUB_ENV"#;
-        let exprs = extract_expressions(expressions)
-            .into_iter()
-            .map(|(e, _)| e.as_curly().to_string())
-            .collect::<Vec<_>>();
+    fn test_extract_expressions() {
+        let multiple = r#"echo "OSSL_PATH=${{ github.workspace }}/osslcache/${{ matrix.PYTHON.OPENSSL.TYPE }}-${{ matrix.PYTHON.OPENSSL.VERSION }}-${OPENSSL_HASH}" >> $GITHUB_ENV"#;
 
-        assert_eq!(
-            exprs,
-            &[
+        for (raw, expected) in &[(
+            multiple,
+            [
                 "${{ github.workspace }}",
                 "${{ matrix.PYTHON.OPENSSL.TYPE }}",
                 "${{ matrix.PYTHON.OPENSSL.VERSION }}",
             ]
-        )
+            .as_slice(),
+        )] {
+            let exprs = extract_expressions(raw)
+                .into_iter()
+                .map(|(e, _)| e.as_curly().to_string())
+                .collect::<Vec<_>>();
+
+            assert_eq!(exprs, *expected)
+        }
+    }
+
+    #[test]
+    fn test_extract_expressions_from_input() -> Result<()> {
+        // Repro cases for #569; ensures we handle broken expressions that
+        // are commented out. Observe that the commented expression isn't
+        // terminated correctly, so the naive parse continues to the next
+        // expression.
+        let action = r#"
+name: >-  # ${{ '' } is a hack to nest jobs under the same sidebar category
+  Windows MSI${{ '' }}
+
+description: test
+
+runs:
+  using: composite
+  steps:
+    - name: foo
+      run: echo hello
+      shell: bash
+"#;
+
+        let action = Action::from_string(action.into(), InputKey::local("fake", None)?)?;
+
+        let exprs = parse_expressions_from_input(&action.into());
+        assert_eq!(exprs.len(), 1);
+        assert_eq!(exprs[0].0.as_curly().to_string(), "${{ '' }}");
+
+        let workflow = r#"
+# ${{ 'don''t parse me' }}
+
+# Observe that the expression in the comment below is invalid:
+# it's missing a closing brace. This should not interfere with
+# parsing the rest of the file's expressions
+name: >- # ${{ 'oops' }
+  custom-name-${{ github.sha }}
+
+on:
+  push:
+
+permissions: {}
+
+jobs:
+  whops:
+    runs-on: ubuntu-latest
+
+    steps:
+      - run: echo hello from ${{ github.actor }}
+"#;
+
+        let workflow = Workflow::from_string(workflow.into(), InputKey::local("fake", None)?)?;
+
+        let exprs = parse_expressions_from_input(&workflow.into())
+            .into_iter()
+            .map(|(e, _)| e.as_raw().to_string())
+            .collect::<Vec<_>>();
+
+        assert_eq!(exprs, &["${{ github.sha }}", "${{ github.actor }}",]);
+
+        Ok(())
     }
 
     #[test]


Test patch:
diff --git a/tests/integration/e2e.rs b/tests/integration/e2e.rs
index 4dbaf09..90b99b7 100644
--- a/tests/integration/e2e.rs
+++ b/tests/integration/e2e.rs
@@ -21,6 +21,23 @@ fn gha_hazmat() -> Result<()> {
     Ok(())
 }
 
+#[cfg_attr(not(feature = "gh-token-tests"), ignore)]
+#[test]
+fn issue_569() -> Result<()> {
+    // Regression test for #569.
+    // Ensures that we don't produce spurious warnings for unreachable
+    // expressions (i.e. inside comments).
+    insta::assert_snapshot!(
+        zizmor()
+            .offline(false)
+            .output(OutputMode::Both)
+            .args(["--no-online-audits", "--collect=workflows-only"])
+            .input("python/cpython@f963239ff1f986742d4c6bab2ab7b73f5a4047f6")
+            .run()?
+    );
+    Ok(())
+}
+
 #[test]
 fn menagerie() -> Result<()> {
     // Respects .gitignore by default.
diff --git a/tests/integration/main.rs b/tests/integration/main.rs
index 9670bf2..db78bf6 100644
--- a/tests/integration/main.rs
+++ b/tests/integration/main.rs
@@ -1,4 +1,11 @@
+// TODO: These modules could stand to be cleaned up a bit.
+
+/// Basic acceptance tests.
 mod acceptance;
+/// Helpers.
 mod common;
+/// "Big picture" end-to-end tests, i.e. tests that typically exercise
+/// more than one audit or complex CLI functionality.
 mod e2e;
+/// General snapshot tests, including repro cases for specific audits.
 mod snapshot;
diff --git a/tests/integration/snapshots/integration__e2e__gha_hazmat.snap b/tests/integration/snapshots/integration__e2e__gha_hazmat.snap
index 709495b..12f23b3 100644
--- a/tests/integration/snapshots/integration__e2e__gha_hazmat.snap
+++ b/tests/integration/snapshots/integration__e2e__gha_hazmat.snap
@@ -22,8 +22,6 @@ snapshot_kind: text
  INFO audit: zizmor: 🌈 completed .github/workflows/ref-confusion.yml
  INFO audit: zizmor: 🌈 completed .github/workflows/secrets-inherit.yml
  INFO audit: zizmor: 🌈 completed .github/workflows/self-hosted.yml
- WARN audit:audit{input=Workflow(https://github.com/woodruffw/gha-hazmat/blob/42064a9533f401a493c3599e56f144918f8eacfd/.github/workflows/template-injection.yml)}: zizmor::audit::overprovisioned_secrets: couldn't parse expression: ...
- WARN audit:audit{input=Workflow(https://github.com/woodruffw/gha-hazmat/blob/42064a9533f401a493c3599e56f144918f8eacfd/.github/workflows/template-injection.yml)}: zizmor::audit::unredacted_secrets: couldn't parse expression: ...
  INFO audit: zizmor: 🌈 completed .github/workflows/template-injection.yml
  INFO audit: zizmor: 🌈 completed .github/workflows/unpinned.yml
  INFO audit: zizmor: 🌈 completed .github/workflows/unredacted-secrets.yml
diff --git a/tests/integration/snapshots/integration__e2e__issue_569.snap b/tests/integration/snapshots/integration__e2e__issue_569.snap
new file mode 100644
index 0000000..91b35c9
--- /dev/null
+++ b/tests/integration/snapshots/integration__e2e__issue_569.snap
@@ -0,0 +1,41 @@
+---
+source: tests/integration/e2e.rs
+expression: "zizmor().offline(false).output(OutputMode::Both).args([\"--no-online-audits\",\n\"--collect=workflows-only\"]).input(\"python/cpython@f963239ff1f986742d4c6bab2ab7b73f5a4047f6\").run()?"
+snapshot_kind: text
+---
+ INFO zizmor: skipping impostor-commit: offline audits only requested
+ INFO zizmor: skipping ref-confusion: offline audits only requested
+ INFO zizmor: skipping known-vulnerable-actions: offline audits only requested
+ INFO audit: zizmor: 🌈 completed .github/workflows/add-issue-header.yml
+ INFO audit: zizmor: 🌈 completed .github/workflows/build.yml
+ INFO audit: zizmor: 🌈 completed .github/workflows/documentation-links.yml
+ INFO audit: zizmor: 🌈 completed .github/workflows/jit.yml
+ INFO audit: zizmor: 🌈 completed .github/workflows/lint.yml
+ INFO audit: zizmor: 🌈 completed .github/workflows/mypy.yml
+ INFO audit: zizmor: 🌈 completed .github/workflows/new-bugs-announce-notifier.yml
+ INFO audit: zizmor: 🌈 completed .github/workflows/project-updater.yml
+ INFO audit: zizmor: 🌈 completed .github/workflows/require-pr-label.yml
+ INFO audit: zizmor: 🌈 completed .github/workflows/reusable-context.yml
+ INFO audit: zizmor: 🌈 completed .github/workflows/reusable-docs.yml
+ INFO audit: zizmor: 🌈 completed .github/workflows/reusable-macos.yml
+ INFO audit: zizmor: 🌈 completed .github/workflows/reusable-tsan.yml
+ INFO audit: zizmor: 🌈 completed .github/workflows/reusable-ubuntu.yml
+ INFO audit: zizmor: 🌈 completed .github/workflows/reusable-wasi.yml
+ INFO audit: zizmor: 🌈 completed .github/workflows/reusable-windows-msi.yml
+ INFO audit: zizmor: 🌈 completed .github/workflows/reusable-windows.yml
+ INFO audit: zizmor: 🌈 completed .github/workflows/stale.yml
+ INFO audit: zizmor: 🌈 completed .github/workflows/tail-call.yml
+ INFO audit: zizmor: 🌈 completed .github/workflows/verify-ensurepip-wheels.yml
+error[dangerous-triggers]: use of fundamentally insecure workflow trigger
+  --> .github/workflows/documentation-links.yml:5:1
+   |
+ 5 | / on:
+ 6 | |   pull_request_target:
+...  |
+10 | |     - 'Doc/**'
+11 | |     - '.github/workflows/doc.yml'
+   | |_________________________________^ pull_request_target is almost always used insecurely
+   |
+   = note: audit confidence → Medium
+
+104 findings (103 suppressed): 0 unknown, 0 informational, 0 low, 0 medium, 1 high