Skip to content

Feat: Add FuzzTesting scaffold for ResponseStreamEvent decoding#421

Open
Krivoblotsky wants to merge 2 commits into
mainfrom
claude/issue-241-fuzz-testing-scaffold
Open

Feat: Add FuzzTesting scaffold for ResponseStreamEvent decoding#421
Krivoblotsky wants to merge 2 commits into
mainfrom
claude/issue-241-fuzz-testing-scaffold

Conversation

@Krivoblotsky
Copy link
Copy Markdown
Contributor

What

Adds a FuzzTesting/ directory at the repo root containing a separate SwiftPM package with one libFuzzer harness:

  • FuzzTesting/Package.swift — declares the package and one .executableTarget.
  • FuzzTesting/Sources/FuzzResponseStreamEventDecoder/FuzzResponseStreamEventDecoder.swiftLLVMFuzzerTestOneInput harness for try JSONDecoder().decode(ResponseStreamEvent.self, …), plus a @main replay entry point gated behind !FUZZING_ENABLED for use without a libFuzzer toolchain.
  • FuzzTesting/FuzzCorpus/FuzzResponseStreamEventDecoder/ — three seed inputs (output_text.delta, function_call_arguments.done, mcp_call_arguments.done).
  • FuzzTesting/README.md — replay-mode and fuzzer-mode invocations, plus how to add more harnesses.

The main Package.swift is unchanged; library consumers see no impact.

Why

First in-repo step toward #241 — Integrate OSS-Fuzz. ResponseStreamEvent.init(from:) is a good first target — it runs several fallible decode paths (early-decode for ResponseEvent, OutputItem, MCPCallArguments, ResponseFunctionCallArgumentsDoneEvent, then a generated raw event with ~50 oneOf cases) over bytes coming straight off the network. Historical bug fixes in stream decoding (#383, #387) suggest this surface benefits from coverage-guided mutation.

The harness deliberately ignores decode errors — we're looking for crashes, hangs, and sanitizer findings.

Affected Areas

  • New top-level directory FuzzTesting/. Separate package, separate .gitignore, no changes to the main package, build, or tests.
  • Refs Integrate OSS-Fuzz #241 (does not close it — the google/oss-fuzz upstream project submission is separate maintainer work).

More Info

Replay mode (no libFuzzer toolchain):

cd FuzzTesting
swift build
swift run FuzzResponseStreamEventDecoder FuzzCorpus/FuzzResponseStreamEventDecoder/seed-function-call-args-done.json

I ran this against all three seeds locally — each decoded without crashing.

Fuzzer mode (requires Swift toolchain with libFuzzer runtime):

cd FuzzTesting
swift build -c debug \
  -Xswiftc -sanitize=fuzzer,address \
  -Xswiftc -parse-as-library \
  -Xswiftc -DFUZZING_ENABLED
./.build/debug/FuzzResponseStreamEventDecoder FuzzCorpus/FuzzResponseStreamEventDecoder -max_total_time=60

Open questions for review:

  1. Scope of this PR: I went with one harness for the easiest path. Other obvious candidates for follow-up: the SSE byte parser (ServerSentEventsStreamParser), ChatStreamResult decode, and the ModelResponseEventsStreamInterpreter data path. Happy to extend in this PR or follow-ups — preference?
  2. google/oss-fuzz upstream submission: that lives in google/oss-fuzz and needs maintainer primary/auto-CC email contacts I shouldn't put in. I left it for you. Note: OSS-Fuzz Swift support is lighter than C/C++/Rust/Go — worth a check before committing to it.

🤖 Generated with Claude Code

…ness

First step toward OSS-Fuzz integration. Adds a separate SwiftPM package
under FuzzTesting/ (kept out of the main Package.swift so library
consumers are unaffected) with one libFuzzer harness targeting
ResponseStreamEvent JSON decoding -- a high-value surface that runs
several fallible decode paths on untrusted bytes from the network.

The harness ships with two modes:
- Replay (default `swift run` build): decodes a single input file and
  exits. Useful for reproducing crash artifacts locally without a
  libFuzzer toolchain.
- Fuzzer (`-sanitize=fuzzer,address -parse-as-library -DFUZZING_ENABLED`):
  libFuzzer's runtime drives `LLVMFuzzerTestOneInput`.

Includes a small seed corpus drawn from real event shapes
(`response.output_text.delta`, `response.function_call_arguments.done`,
`response.mcp_call_arguments.done`).

Refs #241. Upstream `google/oss-fuzz` project submission is left for a
follow-up.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Extend the FuzzTesting scaffold with libFuzzer harnesses for the
remaining bytes-off-the-wire Codable types:

- FuzzChatResultDecoder — /v1/chat/completions non-streaming response
- FuzzChatStreamResultDecoder — streaming chunk type
- FuzzResponseObjectDecoder — /v1/responses non-streaming response
- FuzzAudioTranscriptionStreamResultDecoder — audio transcription
  streaming events (hand-written init(from:) with type-branching)

Each ships with 1-3 realistic seed JSONs. All five harnesses build with
plain `swift build` and all 11 seeds replay without crashing.

The package only depends on the public OpenAI module surface — no
@testable / internal access — so it stays cross-package-clean.

Refs #241.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
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