Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions .clusterfuzzlite/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# ClusterFuzzLite builder image for Vaara fuzz targets.
#
# Base image is OSS-Fuzz's `base-builder-python`, which already carries
# atheris + compile_python_fuzzer plumbing. We layer Vaara's source tree
# on top and install with the [yaml,attestation] extras so the policy
# loader and OVERT envelope targets exercise the real PyYAML / cbor2
# / cryptography code paths.
FROM gcr.io/oss-fuzz-base/base-builder-python

RUN apt-get update && apt-get install -y --no-install-recommends \
python3-pip \
&& rm -rf /var/lib/apt/lists/*

COPY . $SRC/vaara
WORKDIR $SRC/vaara

COPY .clusterfuzzlite/build.sh $SRC/build.sh
12 changes: 12 additions & 0 deletions .clusterfuzzlite/build.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
#!/bin/bash -eu
# ClusterFuzzLite build script for Vaara fuzz targets.
#
# Installs Vaara with the optional extras the fuzz targets actually touch
# (attestation = cbor2 + cryptography; yaml = pyyaml), then compiles each
# `fuzz/fuzz_*.py` target with `compile_python_fuzzer` from base-builder.

pip3 install --no-cache-dir ".[attestation,yaml]"

for fuzzer in "$SRC/vaara/fuzz/"fuzz_*.py; do
compile_python_fuzzer "$fuzzer"
done
31 changes: 31 additions & 0 deletions .github/workflows/cflite_batch.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
name: ClusterFuzzLite batch fuzzing

on:
schedule:
- cron: '0 4 * * *'
workflow_dispatch:

permissions: read-all

jobs:
BatchFuzzing:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
sanitizer: [address, undefined]
steps:
- name: Build fuzzers (${{ matrix.sanitizer }})
id: build
uses: google/clusterfuzzlite/actions/build_fuzzers@v1

Check warning

Code scanning / CodeQL

Unpinned tag for a non-immutable Action in workflow or composite action Medium

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

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

with:
language: python
sanitizer: ${{ matrix.sanitizer }}
- name: Run fuzzers (${{ matrix.sanitizer }})
id: run
uses: google/clusterfuzzlite/actions/run_fuzzers@v1

Check warning

Code scanning / CodeQL

Unpinned tag for a non-immutable Action in workflow or composite action Medium

Unpinned 3rd party Action 'ClusterFuzzLite batch fuzzing' step
Uses Step: run
uses 'google/clusterfuzzlite/actions/run_fuzzers' with ref 'v1', not a pinned commit hash
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
fuzz-seconds: 3600
mode: batch
sanitizer: ${{ matrix.sanitizer }}
30 changes: 30 additions & 0 deletions .github/workflows/cflite_cifuzz.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
name: ClusterFuzzLite continuous build

on:
push:
branches: [main]
paths:
- src/**
- fuzz/**
- .clusterfuzzlite/**
- .github/workflows/cflite_cifuzz.yml
- pyproject.toml
workflow_dispatch:

permissions: read-all

jobs:
Build:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
sanitizer: [address, undefined]
steps:
- name: Build fuzzers (${{ matrix.sanitizer }})
id: build
uses: google/clusterfuzzlite/actions/build_fuzzers@v1

Check warning

Code scanning / CodeQL

Unpinned tag for a non-immutable Action in workflow or composite action Medium

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

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.

with:
language: python
sanitizer: ${{ matrix.sanitizer }}
upload-build: true
42 changes: 42 additions & 0 deletions .github/workflows/cflite_pr.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
name: ClusterFuzzLite PR fuzzing

on:
pull_request:
branches: [main]
paths:
- src/**
- fuzz/**
- .clusterfuzzlite/**
- .github/workflows/cflite_pr.yml
- pyproject.toml

permissions: read-all

jobs:
PR:
runs-on: ubuntu-latest
concurrency:
group: ${{ github.workflow }}-${{ matrix.sanitizer }}-${{ github.ref }}
cancel-in-progress: true
strategy:
fail-fast: false
matrix:
sanitizer: [address, undefined]
steps:
- name: Build fuzzers (${{ matrix.sanitizer }})
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

Check warning

Code scanning / CodeQL

Unpinned tag for a non-immutable Action in workflow or composite action Medium

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

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.

with:
language: python
sanitizer: ${{ matrix.sanitizer }}
- name: Run fuzzers (${{ matrix.sanitizer }})
id: run
uses: google/clusterfuzzlite/actions/run_fuzzers@v1

Check warning

Code scanning / CodeQL

Unpinned tag for a non-immutable Action in workflow or composite action Medium

Unpinned 3rd party Action 'ClusterFuzzLite PR fuzzing' step
Uses Step: run
uses 'google/clusterfuzzlite/actions/run_fuzzers' with ref 'v1', not a pinned commit hash
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
fuzz-seconds: 300
mode: code-change
sanitizer: ${{ matrix.sanitizer }}
output-sarif: true
58 changes: 58 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,64 @@ and this project follows [Semantic Versioning](https://semver.org/spec/v2.0.0.ht

## [Unreleased]

## [0.27.0] - 2026-05-22

**Theme: continuous fuzzing on the parsers that ingest attacker-controlled
input.** The OVERT envelope decoder, the audit-record `from_dict`
deserialiser, and the policy YAML/JSON loader all sit at the boundary
between Vaara and untrusted bytes. A crash, hang, or unhandled exception
in any of them is a denial-of-service vector at minimum, and a
deserialisation hazard at worst. This release wires ClusterFuzzLite into
CI so those three parsers get continuously fuzzed on every PR and nightly
in batch, and ships the first finding the local smoke test caught
(`from_yaml` leaking `OSError(ENAMETOOLONG)` past the `PolicyError`
contract).

### Added
- `fuzz/fuzz_overt_envelope.py`: atheris target that decodes attacker
CBOR bytes, validates the closed 9-field schema, reconstructs a
`BaseEnvelope`, and signature-checks against a dummy pubkey. Mirrors
the attack surface of `vaara overt verify`.
- `fuzz/fuzz_audit_from_dict.py`: atheris target for `AuditRecord.from_dict`
+ `compute_hash()` + the `narrative` property. Models the JSONL-replay
path where trail records get reloaded from disk.
- `fuzz/fuzz_policy_loader.py`: atheris target for `from_json` and
`from_yaml`. Exercises both text paths with attacker-controlled strings.
- `.clusterfuzzlite/Dockerfile` and `.clusterfuzzlite/build.sh`: builder
image based on `gcr.io/oss-fuzz-base/base-builder-python`, installs
Vaara with `[attestation,yaml]` extras, compiles each `fuzz/fuzz_*.py`
target with `compile_python_fuzzer`.
- `.github/workflows/cflite_pr.yml`: PR-triggered fuzzing for 300s under
both address and undefined sanitizers, code-change mode.
- `.github/workflows/cflite_batch.yml`: nightly cron, 3600s batch fuzzing
under both sanitizers.
- `.github/workflows/cflite_cifuzz.yml`: build-sanity on push to main, so
a broken Dockerfile or build.sh surfaces immediately rather than at
next PR.
- `tests/test_policy.py`:
`test_from_yaml_oversize_single_line_treated_as_content_not_path`
pins the regression behaviour of the loader fix below.
- SECURITY.md: brief note on the continuous-fuzzing posture so reporters
know the parsers are under active fuzz coverage.

### Fixed
- `vaara.policy.loader.from_yaml`: a single-line YAML string longer than
the OS path limit (~255 bytes on most filesystems) previously caused
`Path(source).is_file()` to raise `OSError(ENAMETOOLONG)` directly,
bypassing the loader's `PolicyError` contract. Any caller that loads
YAML from attacker-controlled config could be DoS'd by an oversized
single-line payload. The is_file probe is now wrapped: any stat
failure is interpreted as "not a path" and the input falls through to
YAML parsing, where it surfaces as a normal `PolicyError`. Found by
the local smoke run of `fuzz_policy_loader.py` before any atheris
fuzzing ran.

### Unchanged
- Hash chain format, OVERT envelope schema, MCP proxy perimeter semantics,
CLI surface, HTTP API, release.yml SLSA provenance. The fuzz targets,
Dockerfile, and CFLite workflows are additive supply-chain
infrastructure; nothing in the runtime kernel moves.

## [0.26.0] - 2026-05-21

**Theme: per-article verdict drill-down inside the compliance report.** A
Expand Down
Loading