Skip to content

test(fuzz): add coverage-guided + property-based fuzzing - #47

Closed
seonghobae wants to merge 3 commits into
mainfrom
feat/add-fuzzing
Closed

test(fuzz): add coverage-guided + property-based fuzzing#47
seonghobae wants to merge 3 commits into
mainfrom
feat/add-fuzzing

Conversation

@seonghobae

Copy link
Copy Markdown
Contributor

What

Adds fuzzing to the highest-value untrusted-input surfaces, located with CodeGraph (codegraph explore "parse decode deserialize request config validate untrusted input JSON payload", plus codegraph explore "load_agents parse agent config" and the redaction/orchestration follow-ups):

  1. HTTP request bodyserver._coerce_json / _reject_unknown_keys / _validate_mode / _validate_messages
  2. Agent-pool configorchestrator.ModelAgent.from_dict
  3. Secret/PII redactionorchestrator.redact_text / redact_value (regex + recursion; idempotence invariant)
  4. End-to-end orchestrationorchestrator.TaskOrchestrator.run on arbitrary prompt text against mock:// providers, incl. SSE framing round-trip

Each target asserts no unhandled crash / only documented exceptions / structural invariants on arbitrary input.

Tools (permissive licenses only)

Tool License Role
Hypothesis MPL-2.0 Always-on property tests (tests/fuzz/), run in the normal pytest suite on every platform
Atheris Apache-2.0 Coverage-guided libFuzzer harnesses (fuzz/), bounded CI job on Python 3.11

Both drivers call the same invariant checks in fuzz/targets.py, so a bug found by either reproduces under the other.

CI

.github/workflows/fuzz.yml:

  • Property tests on every push/PR (fast, deterministic, no native deps).
  • Atheris with a 60s-per-target budget on PR/push (300s on the weekly schedule / manual dispatch) so CI stays cheap. Crash inputs uploaded as artifacts.

Actions pinned by SHA to match repo convention.

Notes

  • Fully offline: no network, no secrets (os.getenv), no DB.
  • Seed corpora in fuzz/corpus/<target>/ (valid + malformed + Unicode/Korean cases).
  • Existing suite stays green: 140 → 147 tests (pytest -q).
  • Background paper: docs/papers/fuzzing-art-science-engineering-manes-2019.pdf (open-access arXiv survey).
  • Docs: docs/fuzzing.md.

🤖 Generated with Claude Code

https://claude.ai/code/session_01RTAMs4bpSZS77Xe3RQjv9P

Add fuzzing across the four untrusted-input surfaces CodeGraph surfaced:
the HTTP request-body parser/validators (server._coerce_json,
_validate_mode/_validate_messages/_reject_unknown_keys), the agent-pool
config parser (ModelAgent.from_dict), secret/PII redaction
(redact_text/redact_value), and end-to-end orchestration on arbitrary
prompt text against mock:// providers (TaskOrchestrator.run + SSE framing).

Tooling is permissive-licensed only:
- Hypothesis (MPL-2.0) property tests in tests/fuzz/ run in the normal
  pytest suite on every platform, no native toolchain required.
- Atheris (Apache-2.0) coverage-guided libFuzzer harnesses in fuzz/,
  wired into .github/workflows/fuzz.yml with a bounded per-target budget
  (60s on PR/push, 300s on the weekly schedule) to keep CI cost low.

Both drivers call the same invariant checks in fuzz/targets.py, so a bug
found by either reproduces under the other. Seed corpora live in
fuzz/corpus/. All targets run fully offline (no network, no secrets).
Existing suite stays green (140 -> 147 tests).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RTAMs4bpSZS77Xe3RQjv9P
Comment thread fuzz/corpus/redaction/multi_secret.txt Fixed
Comment thread .github/workflows/fuzz.yml Fixed
Comment thread .github/workflows/fuzz.yml Fixed
Comment thread .github/workflows/fuzz.yml Fixed
Seongho Bae and others added 2 commits July 8, 2026 21:49
…action seed

Resolves 4 GHAS code-scanning alerts on the fuzzing PR:

- Alerts 23/24/25 (OpenSSF Scorecard Pinned-Dependencies): the three
  `pip install` invocations in .github/workflows/fuzz.yml were unpinned.
  Replace with `pip install --require-hashes -r` against fully hash-locked
  requirements (fuzz/requirements-property.txt for the py3.12 property-test
  job, fuzz/requirements-atheris.txt for the py3.11 Atheris job), generated
  via `uv pip compile --generate-hashes`. Source .in files kept for
  reproducible recompiles. pip itself is now pinned by hash too.

- Alert 22 (Trivy AWS Access Key ID, CRITICAL): the redaction fuzz seed
  fuzz/corpus/redaction/multi_secret.txt held an AKIA-format synthetic key.
  The redactor keys off the `token=`/`password:` prefix (12+ chars), not the
  AWS format, so the AKIA value added zero coverage. Swap it for a non-AWS
  placeholder that matches the exact same SECRET_PATTERNS regex path — full
  coverage preserved, no secret-shaped string committed.

tests/fuzz property suite: 7 passed.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RTAMs4bpSZS77Xe3RQjv9P
@seonghobae
seonghobae enabled auto-merge (squash) July 9, 2026 01:22

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

OpenCode cannot approve yet because required coverage evidence did not pass.

Review outcome

1. HIGH .github/workflows/opencode-review.yml:1 - Coverage evidence did not prove required test/docstring evidence

  • Problem: The required coverage-evidence job result was failure, so OpenCode cannot establish approval sufficiency for this head.

  • Root cause: Automated approval is only valid when the same-head coverage-evidence job proves supported repository test suites passed and configured docstring gates passed or were advisory, or reports not applicable because no supported source files or package manifests exist. Missing, failed, skipped, unavailable, or unsupported-tooling test evidence is a blocker.

  • Fix: Install or configure the repository test/docstring evidence tooling when source files or package manifests exist, rerun the current-head coverage-evidence job, and approve only after it reports success with required evidence or explicit no-source not-applicable evidence.

  • Regression test: Keep the approval branch checking needs.coverage-evidence.result == success before posting APPROVE, and publish REQUEST_CHANGES when coverage-evidence blocker states such as cancelled, skipped, failed, unsupported-tooling, or below-100 evidence are present.

  • Result: REQUEST_CHANGES

  • Reason: coverage-evidence result was failure, so required test/docstring evidence was not proven for current head 4ab294091cafd5159f8b3ec55c16102e7489658f.

  • Head SHA: 4ab294091cafd5159f8b3ec55c16102e7489658f

  • Workflow run: 28981783767

  • Workflow attempt: 1

Coverage evidence

Coverage Evidence

  • Head SHA: 4ab294091cafd5159f8b3ec55c16102e7489658f
  • Required test evidence: supported repository test suites must pass.
  • Required docstring evidence: repository-owned docstring gates must pass when configured; otherwise docstring coverage is advisory.

Python project dependencies (.)

Using CPython 3.12.3 interpreter at: /usr/bin/python3
Creating virtual environment at: .venv
Resolved 28 packages in 336ms
Checked in 0.00ms
  • Result: PASS

Python coverage with missing-line report (.)

Downloading pygments (1.2MiB)
 Downloaded pygments
Installed 6 packages in 12ms
============================= test session starts ==============================
platform linux -- Python 3.12.3, pytest-9.1.1, pluggy-1.6.0
rootdir: /home/runner/work/contextual-orchestrator/contextual-orchestrator/pr-head
configfile: pyproject.toml
collected 149 items / 1 error

==================================== ERRORS ====================================
_____________ ERROR collecting tests/fuzz/test_fuzz_properties.py ______________
ImportError while importing test module '/home/runner/work/contextual-orchestrator/contextual-orchestrator/pr-head/tests/fuzz/test_fuzz_properties.py'.
Hint: make sure your test modules/packages have valid Python names.
Traceback:
/usr/lib/python3.12/importlib/__init__.py:90: in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
tests/fuzz/test_fuzz_properties.py:17: in <module>
    from hypothesis import given, settings, strategies as st
E   ModuleNotFoundError: No module named 'hypothesis'
=========================== short test summary info ============================
ERROR tests/fuzz/test_fuzz_properties.py
!!!!!!!!!!!!!!!!!!!! Interrupted: 1 error during collection !!!!!!!!!!!!!!!!!!!!
=============================== 1 error in 1.88s ===============================
  • Result: FAIL (exit 2)

Python docstring coverage advisory

RESULT: PASSED (minimum: 80.0%, actual: 88.7%)
  • Result: PASS

Coverage Decision

  • Result: FAIL
  • Test evidence: not proven passing
  • Docstring evidence: not proven passing when configured
  • Failure count: 1

Changed-File Evidence Map

flowchart LR
  PR["PR changed files"] --> Evidence["OpenCode bounded evidence"]
  Evidence --> S1["Workflow: fuzz.yml"]
  S1 --> I1["GitHub Actions review job"]
  I1 --> R1["Review risk: Workflow: fuzz.yml"]
  R1 --> V1["actionlint plus required checks"]
  Evidence --> S2["Changed file (33 files)"]
  S2 --> I2["repository behavior"]
  I2 --> R2["Review risk: Changed file (33 files)"]
  R2 --> V2["required checks"]
  Evidence --> S3["Docs (2 files)"]
  S3 --> I3["operator or user guidance"]
  I3 --> R3["Review risk: Docs (2 files)"]
  R3 --> V3["docs review"]
  Evidence --> S4["Test: test_fuzz_properties.py"]
  S4 --> I4["regression suite"]
  I4 --> R4["Review risk: Test: test_fuzz_properties.py"]
  R4 --> V4["targeted test run"]
Loading

@github-actions

github-actions Bot commented Jul 9, 2026

Copy link
Copy Markdown

OpenCode Review Overview

  • Head SHA: 4ab294091cafd5159f8b3ec55c16102e7489658f
  • Workflow run: 28981783767
  • Workflow attempt: 1
  • Gate result: REQUEST_CHANGES (approval step)

Pull request overview

OpenCode cannot approve yet because required coverage evidence did not pass.

Review outcome

1. HIGH .github/workflows/opencode-review.yml:1 - Coverage evidence did not prove required test/docstring evidence

  • Problem: The required coverage-evidence job result was failure, so OpenCode cannot establish approval sufficiency for this head.

  • Root cause: Automated approval is only valid when the same-head coverage-evidence job proves supported repository test suites passed and configured docstring gates passed or were advisory, or reports not applicable because no supported source files or package manifests exist. Missing, failed, skipped, unavailable, or unsupported-tooling test evidence is a blocker.

  • Fix: Install or configure the repository test/docstring evidence tooling when source files or package manifests exist, rerun the current-head coverage-evidence job, and approve only after it reports success with required evidence or explicit no-source not-applicable evidence.

  • Regression test: Keep the approval branch checking needs.coverage-evidence.result == success before posting APPROVE, and publish REQUEST_CHANGES when coverage-evidence blocker states such as cancelled, skipped, failed, unsupported-tooling, or below-100 evidence are present.

  • Result: REQUEST_CHANGES

  • Reason: coverage-evidence result was failure, so required test/docstring evidence was not proven for current head 4ab294091cafd5159f8b3ec55c16102e7489658f.

  • Head SHA: 4ab294091cafd5159f8b3ec55c16102e7489658f

  • Workflow run: 28981783767

  • Workflow attempt: 1

Coverage evidence

Coverage Evidence

  • Head SHA: 4ab294091cafd5159f8b3ec55c16102e7489658f
  • Required test evidence: supported repository test suites must pass.
  • Required docstring evidence: repository-owned docstring gates must pass when configured; otherwise docstring coverage is advisory.

Python project dependencies (.)

Using CPython 3.12.3 interpreter at: /usr/bin/python3
Creating virtual environment at: .venv
Resolved 28 packages in 336ms
Checked in 0.00ms
  • Result: PASS

Python coverage with missing-line report (.)

Downloading pygments (1.2MiB)
 Downloaded pygments
Installed 6 packages in 12ms
============================= test session starts ==============================
platform linux -- Python 3.12.3, pytest-9.1.1, pluggy-1.6.0
rootdir: /home/runner/work/contextual-orchestrator/contextual-orchestrator/pr-head
configfile: pyproject.toml
collected 149 items / 1 error

==================================== ERRORS ====================================
_____________ ERROR collecting tests/fuzz/test_fuzz_properties.py ______________
ImportError while importing test module '/home/runner/work/contextual-orchestrator/contextual-orchestrator/pr-head/tests/fuzz/test_fuzz_properties.py'.
Hint: make sure your test modules/packages have valid Python names.
Traceback:
/usr/lib/python3.12/importlib/__init__.py:90: in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
tests/fuzz/test_fuzz_properties.py:17: in <module>
    from hypothesis import given, settings, strategies as st
E   ModuleNotFoundError: No module named 'hypothesis'
=========================== short test summary info ============================
ERROR tests/fuzz/test_fuzz_properties.py
!!!!!!!!!!!!!!!!!!!! Interrupted: 1 error during collection !!!!!!!!!!!!!!!!!!!!
=============================== 1 error in 1.88s ===============================
  • Result: FAIL (exit 2)

Python docstring coverage advisory

RESULT: PASSED (minimum: 80.0%, actual: 88.7%)
  • Result: PASS

Coverage Decision

  • Result: FAIL
  • Test evidence: not proven passing
  • Docstring evidence: not proven passing when configured
  • Failure count: 1

Changed-File Evidence Map

flowchart LR
  PR["PR changed files"] --> Evidence["OpenCode bounded evidence"]
  Evidence --> S1["Workflow: fuzz.yml"]
  S1 --> I1["GitHub Actions review job"]
  I1 --> R1["Review risk: Workflow: fuzz.yml"]
  R1 --> V1["actionlint plus required checks"]
  Evidence --> S2["Changed file (33 files)"]
  S2 --> I2["repository behavior"]
  I2 --> R2["Review risk: Changed file (33 files)"]
  R2 --> V2["required checks"]
  Evidence --> S3["Docs (2 files)"]
  S3 --> I3["operator or user guidance"]
  I3 --> R3["Review risk: Docs (2 files)"]
  R3 --> V3["docs review"]
  Evidence --> S4["Test: test_fuzz_properties.py"]
  S4 --> I4["regression suite"]
  I4 --> R4["Review risk: Test: test_fuzz_properties.py"]
  R4 --> V4["targeted test run"]
Loading

@seonghobae

Copy link
Copy Markdown
Contributor Author

Closing as superseded by the fuzz/security work already merged to main via later PRs. The remaining failures on this stale head are real but obsolete: coverage-evidence could not collect tests because Hypothesis was not installed for the evidence run, and Atheris failed because this branch pins unavailable atheris==3.1.0. Current main carries the corrected fuzz workflow and corpus, uses atheris==3.0.0, keeps upload-artifact on v5, includes the unhashable-role regression coverage, and passed the full local suite during the queue refresh. Keeping this stale PR open would keep failing checks around and risks reverting newer main work if merged.

@seonghobae seonghobae closed this Jul 10, 2026
auto-merge was automatically disabled July 10, 2026 12:18

Pull request was closed

@github-project-automation github-project-automation Bot moved this from Todo to Done in naruon Platform Roadmap Jul 10, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

2 participants