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
16 changes: 16 additions & 0 deletions .clusterfuzzlite/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,22 @@ RUN curl -fsSLO "https://static.rust-lang.org/rustup/archive/${RUSTUP_VERSION}/x
rm rustup-init && \
cargo --version

# The graph-rs fuzz targets (rust/cgr_graph_rs/fuzz) are cargo-fuzz targets,
# built by the `language: rust` job. OSS-Fuzz's compile hands Rust builds
# -Zsanitizer through RUSTFLAGS, which only a nightly accepts, so they get the
# nightly and the cargo-fuzz release base-builder-rust pins, the pair OSS-Fuzz
# builds against this image's clang. The nightly must be new enough for the
# crate's dependencies, as RUST_TOOLCHAIN above must. A rust build also copies
# the DEFAULT toolchain's std sources (for coverage reports) and stops if they
# are missing, so the stable toolchain carries rust-src.
ARG CARGO_FUZZ_VERSION=0.13.2
ENV RUST_FUZZ_TOOLCHAIN=nightly-2025-09-05
RUN rustup toolchain install "${RUST_FUZZ_TOOLCHAIN}" --profile minimal && \
rustup component add rust-src --toolchain "${RUST_TOOLCHAIN}" && \
cargo install cargo-fuzz --locked --version "${CARGO_FUZZ_VERSION}" && \
rm -rf /rust/registry && \
cargo fuzz --version

# Make that interpreter the default `python3` so compile_python_fuzzer and
# build.sh use it without either knowing where it came from.
RUN ln -sf "$(uv python find 3.12)" /usr/local/bin/python3 && \
Expand Down
35 changes: 34 additions & 1 deletion .clusterfuzzlite/build.sh
Original file line number Diff line number Diff line change
@@ -1,5 +1,38 @@
#!/bin/bash -eu
# Builds one fuzz target per harness in fuzz/.
# Builds one fuzz target per harness in fuzz/, or, for the `language: rust`
# job, one per cargo-fuzz target in rust/cgr_graph_rs/fuzz.
#
# Each job builds only its own language's targets. ClusterFuzzLite's bad-build
# check holds every target in $OUT to the job's language: under `python` it
# checks the frozen `<target>.pkg` a native Rust binary does not have, and
# under `rust` it wants a sanitizer runtime linked into the binary, which a
# Python wrapper script gets through LD_PRELOAD instead.
if [ "$FUZZING_LANGUAGE" = "rust" ]; then
cd "$SRC/code-graph-rag/rust/cgr_graph_rs"
# The linker base-builder-rust sets: libFuzzer comes in as OSS-Fuzz's
# prebuilt libFuzzingEngine.a, built against libc++.
export CARGO_TARGET_X86_64_UNKNOWN_LINUX_GNU_LINKER=clang
# RUSTUP_TOOLCHAIN rather than `cargo +toolchain`, so `fuzz build` stays the
# first argument OSS-Fuzz's cargo wrapper sees: it rewrites that command for
# a coverage build. Debug assertions on, as OSS-Fuzz builds Rust targets, so
# an integer overflow is a crash.
export RUSTUP_TOOLCHAIN="$RUST_FUZZ_TOOLCHAIN"
cargo fuzz build -O --debug-assertions
for target in $(cargo fuzz list); do
cp "fuzz/target/x86_64-unknown-linux-gnu/release/$target" "$OUT/"
# As for the Python targets below: fail here, naming the target, rather
# than in the bad-build check after the whole job.
echo "Smoke-testing $target"
if ! "$OUT/$target" -runs=1 > /tmp/smoke.log 2>&1; then
echo "ERROR: $target failed to run a single input:" >&2
tail -25 /tmp/smoke.log >&2
exit 1
fi
done
exit 0
fi

# The rest is the Python build.
#
# The harnesses import the package under test, so the project and its parsing
# extras must be installed into the image's interpreter before
Expand Down
2 changes: 1 addition & 1 deletion .github/SECURITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ This policy applies to the `code-graph-rag` Python package and its official repo
- **Code scanning**: The CodeQL workflow (`.github/workflows/codeql.yml`) analyses the Actions, C/C++, C#, JavaScript/TypeScript and Python code in this repository on every pull request targeting `main`, pull requests from forks included, on every push to `main` and weekly, and reports findings to the Security tab. The `main` ruleset requires CodeQL results on every pull request and blocks the merge while CodeQL reports any new alert on it
- **Static analysis gate**: The SonarCloud workflow analyses pushes to `main` and pull requests targeting `main` from a branch of this repository, Dependabot's excepted, and on those pull requests the `All Checks Pass` status check fails while SonarCloud reports any open issue, vulnerabilities included
- **Python security linting**: Bandit runs as a pre-commit hook, and the `Lint & Format` CI job runs the same hook on every pull request, so a high-severity finding fails `All Checks Pass`. The same job's `ruff check` enforces Ruff's flake8-bandit (`S`) rules outside tests, and `pyproject.toml` records why each rule left off is off
- **Fuzzing**: [ClusterFuzzLite](https://google.github.io/clusterfuzzlite/) runs the harnesses in `fuzz/` against every pull request targeting `main`, and daily for longer against `main`
- **Fuzzing**: [ClusterFuzzLite](https://google.github.io/clusterfuzzlite/) runs the harnesses in `fuzz/` and the Rust extension's cargo-fuzz targets in `rust/cgr_graph_rs/fuzz/` against every pull request targeting `main`, and daily for longer against `main`
- **Vulnerability scanning**: The [OSV-Scanner](https://google.github.io/osv-scanner/) workflow checks dependencies against the OSV database on pull requests targeting `main`, on pushes to `main` and weekly, and reports findings to the Security tab
- **Supply chain scorecard**: The [OpenSSF Scorecard](https://github.com/ossf/scorecard) workflow audits the repository's supply chain posture and reports findings to the Security tab
- **Private vulnerability reporting**: Enabled, so vulnerabilities can be reported privately through the Security tab as described above
Expand Down
12 changes: 7 additions & 5 deletions .github/workflows/cflite_batch.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,20 +20,22 @@ concurrency:

jobs:
batch-fuzzing:
name: Batch fuzz (${{ matrix.sanitizer }})
name: Batch fuzz (${{ matrix.language }}, ${{ matrix.sanitizer }})
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
# One job per language: see .clusterfuzzlite/build.sh.
language: [python, rust]
sanitizer: [address]
steps:
- name: Build fuzzers (${{ matrix.sanitizer }})
- name: Build fuzzers (${{ matrix.language }}, ${{ matrix.sanitizer }})
uses: google/clusterfuzzlite/actions/build_fuzzers@884713a6c30a92e5e8544c39945cd7cb630abcd1 # v1
with:
sanitizer: ${{ matrix.sanitizer }}
language: python
language: ${{ matrix.language }}

- name: Run fuzzers (${{ matrix.sanitizer }})
- name: Run fuzzers (${{ matrix.language }}, ${{ matrix.sanitizer }})
uses: google/clusterfuzzlite/actions/run_fuzzers@884713a6c30a92e5e8544c39945cd7cb630abcd1 # v1
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
Expand All @@ -46,4 +48,4 @@ jobs:
# 30s default). A PyInstaller-frozen Python target is slow to start,
# so dropping this risks real crashes being written off as
# unreproducible.
language: python
language: ${{ matrix.language }}
15 changes: 9 additions & 6 deletions .github/workflows/cflite_pr.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
# ClusterFuzzLite on pull requests.
#
# Runs each harness in fuzz/ for a short budget against the PR's head, seeded
# from the corpus in fuzz/corpus/. A crash fails the job with the reproducing
# from the corpus in fuzz/corpus/, and each cargo-fuzz target in
# rust/cgr_graph_rs/fuzz in a job of its own (see .clusterfuzzlite/build.sh for
# why a job builds one language). A crash fails the job with the reproducing
# input attached as an artifact.
#
# See https://google.github.io/clusterfuzzlite/
Expand All @@ -21,23 +23,24 @@ concurrency:

jobs:
fuzzing:
name: Fuzz (${{ matrix.sanitizer }})
name: Fuzz (${{ matrix.language }}, ${{ matrix.sanitizer }})
runs-on: ubuntu-latest
permissions:
security-events: write
strategy:
fail-fast: false
matrix:
language: [python, rust]
sanitizer: [address]
steps:
- name: Build fuzzers (${{ matrix.sanitizer }})
- name: Build fuzzers (${{ matrix.language }}, ${{ matrix.sanitizer }})
uses: google/clusterfuzzlite/actions/build_fuzzers@884713a6c30a92e5e8544c39945cd7cb630abcd1 # v1
with:
sanitizer: ${{ matrix.sanitizer }}
language: python
language: ${{ matrix.language }}
github-token: ${{ secrets.GITHUB_TOKEN }}

- name: Run fuzzers (${{ matrix.sanitizer }})
- name: Run fuzzers (${{ matrix.language }}, ${{ matrix.sanitizer }})
uses: google/clusterfuzzlite/actions/run_fuzzers@884713a6c30a92e5e8544c39945cd7cb630abcd1 # v1
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
Expand All @@ -50,5 +53,5 @@ jobs:
# 30s default). A PyInstaller-frozen Python target is slow to start,
# so dropping this risks real crashes being written off as
# unreproducible.
language: python
language: ${{ matrix.language }}
output-sarif: true
4 changes: 3 additions & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,9 @@ To move them:
`cgr-graph-rs` floor in `pyproject.toml`), as CI requires for any change
to the library. If the new crate needs a newer Rust than
`RUST_TOOLCHAIN` in `.clusterfuzzlite/Dockerfile`, raise that too: the
fuzz build compiles the crate with it and fails otherwise.
fuzz build compiles the crate with it and fails otherwise. The same goes
for `RUST_FUZZ_TOOLCHAIN`, the nightly the crate's cargo-fuzz targets are
built with.
5. Run `codebase_rag/tests/test_native_trees.py` and
`cargo test --manifest-path rust/cgr_graph_rs/Cargo.toml`.

Expand Down
18 changes: 18 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,24 @@ When fuzzing finds a crash: add a regression test under `codebase_rag/tests/`
that fails without the fix, commit the reproducer into the harness' corpus
directory, and only then fix the bug.

The Rust extension has cargo-fuzz targets of its own, in
`rust/cgr_graph_rs/fuzz/fuzz_targets/`, and both workflows run them in a
separate `rust` job with the same budgets. Each target's header says what it
drives and checks:

| Target | What it drives |
| --- | --- |
| `cypher_parse` | the Cypher lexer and parser on any query text |
| `json_string` | `json_text::push_string`, against Python's `json.dumps` |
| `graph_algorithms` | the graph procedures and `run` on a graph and arguments the input chooses |
| `sqlite_graph` | the bulk writer, the variable-length walk and `Graph::load`, against a model of the graph |

To run one locally, with a nightly toolchain and `cargo install cargo-fuzz`:

```bash
cd rust/cgr_graph_rs && cargo +nightly fuzz run cypher_parse -- -max_total_time=60
```

## Coding Standards

### Tooling
Expand Down
Loading