Skip to content

chore(readme): reorganize for scannability and clearer positioning#120

Merged
vaaraio merged 4 commits into
mainfrom
chore/readme-reorganize
May 22, 2026
Merged

chore(readme): reorganize for scannability and clearer positioning#120
vaaraio merged 4 commits into
mainfrom
chore/readme-reorganize

Conversation

@vaaraio
Copy link
Copy Markdown
Owner

@vaaraio vaaraio commented May 22, 2026

Summary

README reorganized for scannability and clearer positioning. No code or behavior changes.

  • Numbers section gets a lede with the three headline stats and moves above Install
  • Three integration prose sections compressed into two tables (framework adapters, upstream-signal adapters)
  • Long capability paragraphs in the MCP proxy section collapsed into <details>
  • "Per-article verdict drill-down" paragraph collapsed into <details>
  • New "Who reaches for Vaara" section between Quick start and evidence output
  • OVERT 1.0 section reframed as What / Why / How
  • MS Agent Governance Toolkit framing inverted: orchestration toolkits sit on top of Vaara
  • IMDA MGF v1.5 acknowledgement link added next to the Apply AI Alliance link
  • License section names Apache 2.0 explicitly
  • "since vX.Y.Z" framing removed throughout; CHANGELOG owns version evolution

Test plan

Summary by CodeRabbit

  • New Features

    • Integrated ClusterFuzzLite for continuous security fuzzing of critical parsing components including OVERT envelope decoding, audit record deserialization, and policy loading.
  • Bug Fixes

    • Fixed a regression in YAML policy loading where oversized input could raise system errors instead of policy errors.
  • Documentation

    • Updated README with improved structure and audience-focused sections.
    • Added continuous fuzzing practices to security documentation.
  • Chores

    • Bumped version to 0.27.0.

Review Change Stack

vaaraio added 4 commits May 22, 2026 06:46
Wires ClusterFuzzLite into CI under AddressSanitizer and
UndefinedBehaviorSanitizer for the three parsers that ingest
attacker-controlled bytes: the OVERT envelope CBOR decoder, the
audit-record from_dict deserialiser, and the policy YAML/JSON loader.
PR-triggered (300s, code-change mode), nightly batch (3600s), and
push-to-main build sanity all land as separate workflows so a broken
Dockerfile surfaces immediately rather than at next PR.

Bundled fix: from_yaml previously leaked OSError(ENAMETOOLONG) past
its PolicyError contract on single-line YAML strings longer than the
OS path limit. Path(source).is_file() raised before any handler ran.
The is_file probe is now wrapped so any stat failure is interpreted
as "not a path" and the input falls through to YAML parsing. Found
by the local smoke run of fuzz_policy_loader.py before any atheris
fuzzing ran. Regression test pinned in test_policy.py.

Full pytest suite: 771 passed, 12 skipped.
npm Trusted Publishing has been 404'ing on every release tag since
v0.23.0. The TP form on the package access page kept clearing on save
(or never persisting in the first place), so the OIDC token had
nothing to match against on npm side. Every release after v0.23 has
required the manual `npm login --auth-type=web` + `npm publish
--no-provenance` fallback to get the TypeScript client out the door.

This swaps the publish-npm job back to NODE_AUTH_TOKEN with the
NPM_TOKEN repo secret, drops --provenance from the publish command,
and removes the now-unused id-token: write permission. The secret has
been rotated to a token with "Allow bypass 2FA when publishing"
enabled.

Tradeoff accepted: lose npm-side provenance attestation on the
@vaara/client artefact. SLSA build provenance via
actions/attest-build-provenance still attests the wheel, sdist, and
GitHub Release artefacts. The TypeScript client is a thin HTTP
wrapper, not security-critical surface.
- Numbers section gets a lede with the three headline stats and moves above Install
- Three integration prose sections compressed into two tables (framework adapters, upstream-signal adapters)
- Long capability paragraphs in the MCP proxy section collapsed into <details>
- 'Per-article verdict drill-down' paragraph collapsed into <details>
- New 'Who reaches for Vaara' section between Quick start and evidence output
- OVERT 1.0 section reframed as What / Why / How
- MS Agent Governance Toolkit framing inverted: orchestration toolkits sit on top of Vaara
- IMDA MGF v1.5 acknowledgement link added next to the Apply AI Alliance link
- License section names Apache 2.0 explicitly
- 'since vX.Y.Z' framing removed throughout; CHANGELOG owns version evolution
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 22, 2026

📝 Walkthrough

Walkthrough

This PR integrates ClusterFuzzLite continuous fuzzing into CI with three Atheris fuzz targets for critical parsers (OVERT envelope, AuditRecord, policy loaders). It fixes a regression in policy.loader.from_yaml where oversized single-line strings could bypass the PolicyError contract by raising OSError, and ships version 0.27.0 with updated documentation.

Changes

ClusterFuzzLite Integration with Policy Loader Fix and 0.27.0 Release

Layer / File(s) Summary
ClusterFuzzLite Docker, Build, and Workflow Foundation
.clusterfuzzlite/Dockerfile, .clusterfuzzlite/build.sh, .github/workflows/cflite_batch.yml, .github/workflows/cflite_cifuzz.yml, .github/workflows/cflite_pr.yml, pyproject.toml, clients/ts/package.json
Docker image for fuzz-target compilation, bash build script that installs attestation and yaml extras and compiles fuzz_*.py targets. Three workflows run fuzzing on PR (300s with code-change mode and SARIF output), continuous pushes to main (with artifact upload), and nightly batch (3600s). Both sanitizers (address, undefined) are tested. Version bumped to 0.27.0.
Atheris Fuzz Targets for Attack Surface
fuzz/fuzz_overt_envelope.py, fuzz/fuzz_audit_from_dict.py, fuzz/fuzz_policy_loader.py
fuzz_overt_envelope.py decodes CBOR into a fixed 9-key schema, constructs BaseEnvelope, and verifies with a dummy public key. fuzz_audit_from_dict.py parses JSON and calls AuditRecord.from_dict, then exercises compute_hash() and narrative. fuzz_policy_loader.py randomly chooses JSON or (if available) YAML loading and enforces that only expected exception types (PolicyError, yaml.YAMLError, RecursionError, etc.) terminate without crashing.
Policy Loader Path-Detection Fix and Regression Test
src/vaara/policy/loader.py, tests/test_policy.py
from_yaml wraps Path(source).is_file() in try/except OSError to catch and handle path-probe failures (e.g., ENAMETOOLONG), falling back to YAML parsing instead of leaking OSError to callers. Regression test verifies a single-line string longer than OS path limit raises PolicyError rather than OSError.
Release Documentation
CHANGELOG.md, SECURITY.md, README.md
CHANGELOG documents 0.27.0 with continuous-fuzzing integration and policy-loader fix. SECURITY.md adds "Continuous Fuzzing" section describing parser coverage (BaseEnvelope CBOR, AuditRecord.from_dict, policy JSON/YAML), sanitizers (AddressSanitizer, UndefinedBehaviorSanitizer), and workflow triggers. README restructured with new "Who reaches for Vaara" audience section, collapsible details for evidence surfaces and adapter capabilities, operator-perimeter filtering behavior, and MCP proxy OVERT envelope emission, plus repositioned acknowledgements.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • vaaraio/vaara#119: Overlaps directly with ClusterFuzzLite scaffolding, Atheris fuzz targets, and the from_yaml defensive fix and regression test in the same code areas.
  • vaaraio/vaara#47: Substantial README.md rewrite and restructuring that relates to the documentation changes in this PR.

Poem

🐰 Fuzzing whispers through the code,
From CBOR paths to policy load,
Three harnesses stand guard today,
One fix caught ENAMETOOLONG's sway,
0.27.0 marks the way. 🔐

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Title check ⚠️ Warning The PR title describes a README reorganization but the changeset includes ClusterFuzzLite integration (Dockerfile, build.sh, workflows), fuzz targets, security/bug fixes, and version bumps—changes well beyond README reorganization. Update the title to reflect the primary change: ClusterFuzzLite fuzzing integration with supporting infrastructure, fuzz targets, and policy loader fix; or split into separate PRs focused on each concern.
Docstring Coverage ⚠️ Warning Docstring coverage is 22.22% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch chore/readme-reorganize

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

steps:
- name: Build fuzzers (${{ matrix.sanitizer }})
id: build
uses: google/clusterfuzzlite/actions/build_fuzzers@v1
sanitizer: ${{ matrix.sanitizer }}
- name: Run fuzzers (${{ matrix.sanitizer }})
id: run
uses: google/clusterfuzzlite/actions/run_fuzzers@v1
steps:
- name: Build fuzzers (${{ matrix.sanitizer }})
id: build
uses: google/clusterfuzzlite/actions/build_fuzzers@v1
id: build
# v1 tag — ClusterFuzzLite ships its action set under this moving ref;
# see https://google.github.io/clusterfuzzlite/build-integration/#step-3-create-the-github-actions-workflows
uses: google/clusterfuzzlite/actions/build_fuzzers@v1
sanitizer: ${{ matrix.sanitizer }}
- name: Run fuzzers (${{ matrix.sanitizer }})
id: run
uses: google/clusterfuzzlite/actions/run_fuzzers@v1
from vaara.policy.schema import PolicyError

try:
import yaml as _yaml # noqa: F401
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
README.md (1)

1-1: ⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Critical: PR objectives do not match actual changes.

The PR objectives state:

  • Title: "chore(readme): reorganize for scannability and clearer positioning"
  • Description: "README reorganized for scannability; no code or behavior changes"
  • Test plan focuses on README rendering

However, the actual changes include:

  1. Security fix in src/vaara/policy/loader.py (wrapping Path.is_file() to prevent OSError(ENAMETOOLONG) bypass)
  2. New regression test in tests/test_policy.py
  3. Version 0.27.0 release documented in CHANGELOG.md with ClusterFuzzLite integration
  4. SECURITY.md continuous fuzzing section

The AI summary correctly identifies this as "ClusterFuzzLite Integration with Policy Loader Fix and Release 0.27.0", but the PR objectives describe only documentation changes.

This inconsistency will confuse reviewers and make the git history misleading. The PR title and description should accurately reflect that this is a v0.27.0 release that includes a security fix, fuzzing infrastructure, and documentation updates.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@README.md` at line 1, Update the PR title and description to accurately
reflect the code changes: change the title from "chore(readme): reorganize for
scannability and clearer positioning" to something like "release(v0.27.0):
ClusterFuzzLite integration, policy loader security fix, and docs"; in the
description list the key changes explicitly (security fix in
src/vaara/policy/loader.py wrapping Path.is_file() to prevent
OSError(ENAMETOOLONG), added regression test in tests/test_policy.py,
CHANGELOG.md entry for v0.27.0 with ClusterFuzzLite, and SECURITY.md fuzzing
section) and update the test plan to include running the new regression test and
verification steps for the loader fix and ClusterFuzzLite docs.
🧹 Nitpick comments (1)
fuzz/fuzz_overt_envelope.py (1)

56-59: ⚡ Quick win

Enforce exact key set to match the declared closed schema.

This currently accepts supersets of _REQUIRED_KEYS. If the intent is a closed 9-field schema, require exact key equality.

Proposed change
-    if any(k not in decoded for k in _REQUIRED_KEYS):
+    if set(decoded.keys()) != set(_REQUIRED_KEYS):
         return
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@fuzz/fuzz_overt_envelope.py` around lines 56 - 59, The current guard in
fuzz_overt_envelope.py only checks for presence of required keys and allows
extra keys; change the check on the decoded object to require exact key equality
with the closed schema by comparing key sets (e.g., replace the any(k not in
decoded for k in _REQUIRED_KEYS) check with a set equality test such as
set(decoded.keys()) == set(_REQUIRED_KEYS)) so decoded contains exactly the nine
declared fields; reference the variables decoded and _REQUIRED_KEYS and update
the early-return logic accordingly.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.github/workflows/cflite_batch.yml:
- Line 20: The workflow currently pins third-party actions with mutable tag refs
(e.g. the lines using "google/clusterfuzzlite/actions/build_fuzzers@v1" and
"google/clusterfuzzlite/actions/run_fuzzers@v1"); replace those tag refs with
specific commit SHAs for each occurrence (in .github/workflows/cflite_batch.yml,
.github/workflows/cflite_pr.yml, and .github/workflows/cflite_cifuzz.yml) so the
actions are pinned to an immutable SHA (update the "uses:" values to the
corresponding full commit SHA strings for build_fuzzers and run_fuzzers).

In @.github/workflows/cflite_cifuzz.yml:
- Line 26: Replace the moving tag
"google/clusterfuzzlite/actions/build_fuzzers@v1" with a fixed commit SHA:
locate the workflow step that uses the action string "uses:
google/clusterfuzzlite/actions/build_fuzzers@v1" and change it to the full
immutable commit SHA form "uses:
google/clusterfuzzlite/actions/build_fuzzers@<COMMIT_SHA>" (pick the desired
upstream commit SHA), then run/validate the workflow to ensure the pinned
version builds correctly.

In @.github/workflows/cflite_pr.yml:
- Line 30: The workflow uses floating tags for ClusterFuzzLite actions; replace
both occurrences of "google/clusterfuzzlite/actions/build_fuzzers@v1" and
"google/clusterfuzzlite/actions/run_fuzzers@v1" with their respective immutable
commit SHAs to pin the actions, i.e., look up the exact commit SHAs for the
build_fuzzers and run_fuzzers actions in the clusterfuzzlite repo and update the
"uses:" entries to use those SHAs instead of "`@v1`" so the workflow no longer
follows a moving tag.

In `@fuzz/fuzz_policy_loader.py`:
- Around line 46-53: The fuzz harness currently special-cases yaml.YAMLError in
the except block (in fuzz/fuzz_policy_loader.py) which masks regressions because
from_yaml() is expected to wrap yaml.YAMLError into PolicyError; remove the
import yaml and the isinstance(exc, yaml.YAMLError) return branch from the
exception handler so the harness no longer whitelists YAML errors and will
surface any contract regressions instead (locate the except Exception as exc:
block in the fuzz harness that references yaml.YAMLError and delete that
conditional return).

---

Outside diff comments:
In `@README.md`:
- Line 1: Update the PR title and description to accurately reflect the code
changes: change the title from "chore(readme): reorganize for scannability and
clearer positioning" to something like "release(v0.27.0): ClusterFuzzLite
integration, policy loader security fix, and docs"; in the description list the
key changes explicitly (security fix in src/vaara/policy/loader.py wrapping
Path.is_file() to prevent OSError(ENAMETOOLONG), added regression test in
tests/test_policy.py, CHANGELOG.md entry for v0.27.0 with ClusterFuzzLite, and
SECURITY.md fuzzing section) and update the test plan to include running the new
regression test and verification steps for the loader fix and ClusterFuzzLite
docs.

---

Nitpick comments:
In `@fuzz/fuzz_overt_envelope.py`:
- Around line 56-59: The current guard in fuzz_overt_envelope.py only checks for
presence of required keys and allows extra keys; change the check on the decoded
object to require exact key equality with the closed schema by comparing key
sets (e.g., replace the any(k not in decoded for k in _REQUIRED_KEYS) check with
a set equality test such as set(decoded.keys()) == set(_REQUIRED_KEYS)) so
decoded contains exactly the nine declared fields; reference the variables
decoded and _REQUIRED_KEYS and update the early-return logic accordingly.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: f4f44c45-7ba6-46bb-ab67-53d91ca9d567

📥 Commits

Reviewing files that changed from the base of the PR and between cb34832 and 018a91b.

📒 Files selected for processing (15)
  • .clusterfuzzlite/Dockerfile
  • .clusterfuzzlite/build.sh
  • .github/workflows/cflite_batch.yml
  • .github/workflows/cflite_cifuzz.yml
  • .github/workflows/cflite_pr.yml
  • CHANGELOG.md
  • README.md
  • SECURITY.md
  • clients/ts/package.json
  • fuzz/fuzz_audit_from_dict.py
  • fuzz/fuzz_overt_envelope.py
  • fuzz/fuzz_policy_loader.py
  • pyproject.toml
  • src/vaara/policy/loader.py
  • tests/test_policy.py

steps:
- name: Build fuzzers (${{ matrix.sanitizer }})
id: build
uses: google/clusterfuzzlite/actions/build_fuzzers@v1
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Show the relevant portion with line numbers
echo "=== .github/workflows/cflite_batch.yml (around lines 1-60) ==="
nl -ba .github/workflows/cflite_batch.yml | sed -n '1,80p'

echo
echo "=== Search for clusterfuzzlite action refs in workflows ==="
rg -n "google/clusterfuzzlite/actions/(build_fuzzers|run_fuzzers)@" .github/workflows -S || true

echo
echo "=== Search for any `@v1` usage of those actions across repo ==="
rg -n "google/clusterfuzzlite/actions/(build_fuzzers|run_fuzzers)`@v1`" -S . || true

Repository: vaaraio/vaara

Length of output: 181


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== .github/workflows/cflite_batch.yml (around lines 1-80) ==="
if [ -f .github/workflows/cflite_batch.yml ]; then
  cat -n .github/workflows/cflite_batch.yml | sed -n '1,120p'
else
  echo "File not found: .github/workflows/cflite_batch.yml"
fi

echo
echo "=== Search for clusterfuzzlite action refs in workflows ==="
rg -n "google/clusterfuzzlite/actions/(build_fuzzers|run_fuzzers)@" .github/workflows -S || true

echo
echo "=== Search for any `@v1` usage of those actions across repo ==="
rg -n "google/clusterfuzzlite/actions/(build_fuzzers|run_fuzzers)`@v1`" -S . || true

Repository: vaaraio/vaara

Length of output: 1735


Pin third-party action refs to commit SHAs in clusterfuzzlite workflows.

.github/workflows/cflite_batch.yml uses tag refs:

uses: google/clusterfuzzlite/actions/build_fuzzers@v1
uses: google/clusterfuzzlite/actions/run_fuzzers@v1

Tag refs like @v1 can move upstream and change behavior without notice.

  • Also applies to .github/workflows/cflite_pr.yml (build_fuzzers@v1, run_fuzzers@v1) and .github/workflows/cflite_cifuzz.yml (build_fuzzers@v1)
Suggested change
-        uses: google/clusterfuzzlite/actions/build_fuzzers@v1
+        uses: google/clusterfuzzlite/actions/build_fuzzers@<full_commit_sha>
...
-        uses: google/clusterfuzzlite/actions/run_fuzzers@v1
+        uses: google/clusterfuzzlite/actions/run_fuzzers@<full_commit_sha>
🧰 Tools
🪛 GitHub Check: CodeQL

[warning] 20-20: Unpinned tag for a non-immutable Action in workflow
Unpinned 3rd party Action 'ClusterFuzzLite batch fuzzing' step Uses Step: build uses 'google/clusterfuzzlite/actions/build_fuzzers' with ref 'v1', not a pinned commit hash

🪛 zizmor (1.25.2)

[error] 20-20: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)

(unpinned-uses)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/cflite_batch.yml at line 20, The workflow currently pins
third-party actions with mutable tag refs (e.g. the lines using
"google/clusterfuzzlite/actions/build_fuzzers@v1" and
"google/clusterfuzzlite/actions/run_fuzzers@v1"); replace those tag refs with
specific commit SHAs for each occurrence (in .github/workflows/cflite_batch.yml,
.github/workflows/cflite_pr.yml, and .github/workflows/cflite_cifuzz.yml) so the
actions are pinned to an immutable SHA (update the "uses:" values to the
corresponding full commit SHA strings for build_fuzzers and run_fuzzers).

steps:
- name: Build fuzzers (${{ matrix.sanitizer }})
id: build
uses: google/clusterfuzzlite/actions/build_fuzzers@v1
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Show the referenced portion of the workflow
sed -n '1,120p' .github/workflows/cflite_cifuzz.yml | nl -ba | sed -n '15,60p'

# Also search for other uses of the same action in the repo
rg -n "google/clusterfuzzlite/actions/build_fuzzers@" -S . || true

Repository: vaaraio/vaara

Length of output: 100


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== workflow snippet =="
sed -n '1,120p' .github/workflows/cflite_cifuzz.yml | cat -n | sed -n '15,60p'

echo "== all uses of build_fuzzers action =="
rg -n "google/clusterfuzzlite/actions/build_fuzzers@" -S . || true

Repository: vaaraio/vaara

Length of output: 625


🌐 Web query:

google/clusterfuzzlite/actions/build_fuzzers v1 tag moving or stable

💡 Result:

In the google/clusterfuzzlite GitHub Actions, the v1 tag for build_fuzzers is a moving tag [1]. While it is commonly used in documentation as a stable reference for production workflows [2], it is technically a pointer to a specific Docker image version (e.g., gcr.io/oss-fuzz-base/clusterfuzzlite-build-fuzzers:v1) [1]. In the context of GitHub Actions, using @v1 means your workflow will pull the version of the action currently associated with that tag at the time of execution [1]. Because v1 is a moving tag, it may be updated by maintainers to include improvements or fixes [1][3]. For users who prioritize strict build reproducibility and want to avoid unexpected changes, it is recommended to pin the action to a specific commit SHA rather than using the v1 tag [2][4].

Citations:


🌐 Web query:

GitHub Actions security hardening pin actions to commit SHA recommended

💡 Result:

Pinning GitHub Actions to a full-length commit SHA is a highly recommended security practice that creates an immutable, predictable reference for your workflows [1][2][3]. Why it is recommended: - Supply Chain Security: Unlike version tags (e.g., @v4) or branches (e.g., @main), which are mutable and can be repointed by repository maintainers or attackers who compromise an account, a commit SHA points to a specific, immutable version of the code [3][4]. - Protection Against Compromise: If a third-party action is compromised, attackers often update the tag to point to malicious code. Workflows pinned to a specific SHA will continue to use the known-good code, preventing automatic execution of the malicious changes [5][4]. - Organizational Compliance: GitHub now provides organization-level policies that allow administrators to enforce full commit SHA pinning [6][5]. When enabled, any workflow using a tag or branch instead of a SHA will fail [6][7]. Note that this policy enforcement can also apply to nested sub-actions, which can cause failures if sub-dependencies are not also pinned [6]. How to implement: - Format: Use the full-length (40-character) SHA instead of a tag [1][2]. - Maintainability: To ensure clarity, it is a common best practice to include a comment indicating which version the SHA corresponds to [8][7][9]: uses: actions/checkout@f43a0e5 # v4.1.1 - Automation: Because manual pinning can be burdensome, organizations often use tools like Dependabot, Renovate, or specialized CLI tools to manage and update these pinned SHAs automatically [6][3]. When to use: While pinning is the gold standard for security, it is especially critical for high-trust pipelines, release-sensitive jobs, and any workflows handling sensitive data or secrets [2][10]. If you choose to use tags for convenience in lower-risk environments, ensure you only use actions from highly trusted sources [1].

Citations:


Pin google/clusterfuzzlite/actions/build_fuzzers to an immutable commit SHA (not @v1).

.github/workflows/cflite_cifuzz.yml uses google/clusterfuzzlite/actions/build_fuzzers@v1 in the fuzz build job; @v1 is a moving tag, so upstream changes can alter CI behavior without notice. Pinning to a full commit SHA avoids that supply-chain/surprise risk.

Suggested change
-        uses: google/clusterfuzzlite/actions/build_fuzzers@v1
+        uses: google/clusterfuzzlite/actions/build_fuzzers@<full_commit_sha>
🧰 Tools
🪛 GitHub Check: CodeQL

[warning] 26-26: Unpinned tag for a non-immutable Action in workflow
Unpinned 3rd party Action 'ClusterFuzzLite continuous build' step Uses Step: build uses 'google/clusterfuzzlite/actions/build_fuzzers' with ref 'v1', not a pinned commit hash

🪛 zizmor (1.25.2)

[error] 26-26: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)

(unpinned-uses)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/cflite_cifuzz.yml at line 26, Replace the moving tag
"google/clusterfuzzlite/actions/build_fuzzers@v1" with a fixed commit SHA:
locate the workflow step that uses the action string "uses:
google/clusterfuzzlite/actions/build_fuzzers@v1" and change it to the full
immutable commit SHA form "uses:
google/clusterfuzzlite/actions/build_fuzzers@<COMMIT_SHA>" (pick the desired
upstream commit SHA), then run/validate the workflow to ensure the pinned
version builds correctly.

id: build
# v1 tag — ClusterFuzzLite ships its action set under this moving ref;
# see https://google.github.io/clusterfuzzlite/build-integration/#step-3-create-the-github-actions-workflows
uses: google/clusterfuzzlite/actions/build_fuzzers@v1
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail
FILE=".github/workflows/cflite_pr.yml"
if [ ! -f "$FILE" ]; then
  echo "Missing file: $FILE" >&2
  exit 1
fi
echo "== $FILE =="
nl -ba "$FILE" | sed -n '1,120p'

Repository: vaaraio/vaara

Length of output: 156


🏁 Script executed:

#!/bin/bash
set -euo pipefail
FILE=".github/workflows/cflite_pr.yml"
echo "== $FILE =="
if [ ! -f "$FILE" ]; then
  echo "Missing file: $FILE" >&2
  exit 1
fi
# Use cat -n (nl is unavailable in this sandbox)
cat -n "$FILE" | sed -n '1,120p'

Repository: vaaraio/vaara

Length of output: 1628


Pin both ClusterFuzzLite GitHub Actions to immutable SHAs (avoid moving @v1 tags).

In .github/workflows/cflite_pr.yml:

  • uses: google/clusterfuzzlite/actions/build_fuzzers@v1 (line 30)
  • uses: google/clusterfuzzlite/actions/run_fuzzers@v1 (line 36)
Suggested change
-        uses: google/clusterfuzzlite/actions/build_fuzzers@v1
+        uses: google/clusterfuzzlite/actions/build_fuzzers@<full_commit_sha>
...
-        uses: google/clusterfuzzlite/actions/run_fuzzers@v1
+        uses: google/clusterfuzzlite/actions/run_fuzzers@<full_commit_sha>
🧰 Tools
🪛 GitHub Check: CodeQL

[warning] 30-30: Unpinned tag for a non-immutable Action in workflow
Unpinned 3rd party Action 'ClusterFuzzLite PR fuzzing' step Uses Step: build uses 'google/clusterfuzzlite/actions/build_fuzzers' with ref 'v1', not a pinned commit hash

🪛 zizmor (1.25.2)

[error] 30-30: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)

(unpinned-uses)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/cflite_pr.yml at line 30, The workflow uses floating tags
for ClusterFuzzLite actions; replace both occurrences of
"google/clusterfuzzlite/actions/build_fuzzers@v1" and
"google/clusterfuzzlite/actions/run_fuzzers@v1" with their respective immutable
commit SHAs to pin the actions, i.e., look up the exact commit SHAs for the
build_fuzzers and run_fuzzers actions in the clusterfuzzlite repo and update the
"uses:" entries to use those SHAs instead of "`@v1`" so the workflow no longer
follows a moving tag.

Comment on lines +46 to +53
except Exception as exc:
# PyYAML raises subclasses of yaml.YAMLError on parse failure.
# The loader is supposed to wrap those into PolicyError; anything
# else leaking through is a finding.
import yaml

if isinstance(exc, yaml.YAMLError):
return
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Verify loader contract and tests around YAML exception wrapping.
rg -n -C3 'def from_yaml|YAMLError|PolicyError|except' src/vaara/policy/loader.py tests fuzz

Repository: vaaraio/vaara

Length of output: 47816


🏁 Script executed:

#!/bin/bash
set -euo pipefail

sed -n '230,310p' src/vaara/policy/loader.py
echo "-----"
sed -n '1,120p' fuzz/fuzz_policy_loader.py

Repository: vaaraio/vaara

Length of output: 3842


Don’t whitelist yaml.YAMLError in the fuzz harness.

from_yaml() already wraps yaml.YAMLError into PolicyError (except yaml.YAMLError as e: raise PolicyError(...)), so this harness branch won’t trigger today; removing it prevents masking a future contract regression.

Proposed change
-        except Exception as exc:
-            # PyYAML raises subclasses of yaml.YAMLError on parse failure.
-            # The loader is supposed to wrap those into PolicyError; anything
-            # else leaking through is a finding.
-            import yaml
-
-            if isinstance(exc, yaml.YAMLError):
-                return
-            raise
+        except Exception:
+            # Any non-whitelisted exception escaping from from_yaml is a finding.
+            raise
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
except Exception as exc:
# PyYAML raises subclasses of yaml.YAMLError on parse failure.
# The loader is supposed to wrap those into PolicyError; anything
# else leaking through is a finding.
import yaml
if isinstance(exc, yaml.YAMLError):
return
except Exception:
# Any non-whitelisted exception escaping from from_yaml is a finding.
raise
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@fuzz/fuzz_policy_loader.py` around lines 46 - 53, The fuzz harness currently
special-cases yaml.YAMLError in the except block (in fuzz/fuzz_policy_loader.py)
which masks regressions because from_yaml() is expected to wrap yaml.YAMLError
into PolicyError; remove the import yaml and the isinstance(exc, yaml.YAMLError)
return branch from the exception handler so the harness no longer whitelists
YAML errors and will surface any contract regressions instead (locate the except
Exception as exc: block in the fuzz harness that references yaml.YAMLError and
delete that conditional return).

@vaaraio vaaraio merged commit 079be0f into main May 22, 2026
12 checks passed
@vaaraio vaaraio deleted the chore/readme-reorganize branch May 22, 2026 05:56
@coderabbitai coderabbitai Bot mentioned this pull request May 22, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants