Skip to content

fix(compilers/openapi): recover from a resolver panic instead of faulting - #96

Merged
OmarAlJarrah merged 1 commit into
mainfrom
fix/openapi-resolve-panic-barrier
Jul 27, 2026
Merged

fix(compilers/openapi): recover from a resolver panic instead of faulting#96
OmarAlJarrah merged 1 commit into
mainfrom
fix/openapi-resolve-panic-barrier

Conversation

@OmarAlJarrah

Copy link
Copy Markdown
Member

Summary

unmarshal has guarded the third-party parser since the first crash fix, but ResolveAllReferences
was left bare. The resolver faults on shapes the parser accepts, so a document that parsed cleanly
could still take the caller's process down:

openapi: 3.0
components:
 responses:
  000: {$ref: '#/B'}
B: {$ref}

B: {$ref} is a mapping whose $ref key carries no value. Populating the response reference that
points at it nil-dereferences inside speakeasy's Reference.Populate
(openapi/reference.go:468), and the panic propagated out of Compile. That breaks both the
styleguide's no-panics-escape rule and the contract this compiler is built around — a malformed spec
yields diagnostics, never a fault.

This is not a cycle-detector gap. The input is a response reference, a non-schema position the
pre-parse scan deliberately does not walk, and the failure is a nil dereference rather than the
unbounded recursion that scan exists to catch.

The change

resolveAll wraps the resolve call the way unmarshal wraps the parser. The recovered panic joins
the resolve errors the caller already converts into diagnostics rather than aborting the compile,
because a document that trips this is a malformed spec, not an I/O or programmer error. The
document is now refused with an openapi/unresolved-ref error and the process survives.

How it was found

FuzzCycleDetector, at ~5m45s. It reproduces on main at 4e0c0ed, so it predates the reference
work in #94/#95 — this is an independent gap, not a regression from either.

Worth reporting upstream: a $ref key with a null value crashing Reference.Populate is a
speakeasy bug, and the barrier here only stops it from being our crash.

Test plan

  • TestResolveAll_RecoversResolverPanic pins the barrier directly: the parser accepts the document,
    the resolver panics, and the panic comes back as an errParse-wrapped error with no partially
    populated result leaking.
  • TestCompile_ResolverPanicIsADiagnostic pins the end-to-end behavior: no Go error, an
    openapi/unresolved-ref error diagnostic, no fault.
  • The fuzzer's input is committed under compilers/openapi/testdata/fuzz/FuzzCycleDetector/, so a
    plain go test replays it.
  • gofmt -l ., go vet ./..., golangci-lint run (0 issues), go test ./..., and
    ./scripts/check-coverage.sh (100.0% total, 100.0% every package) all pass.

…ting

unmarshal has guarded the third-party parser since the first crash fix, but
ResolveAllReferences was left bare. The resolver faults on shapes the parser
accepts, so a document that parsed cleanly could still take the caller's process
down:

    openapi: 3.0
    components:
     responses:
      000: {$ref: '#/B'}
    B: {$ref}

`B: {$ref}` is a mapping whose $ref key carries no value. Populating the response
reference that points at it nil-dereferences inside speakeasy's Reference.Populate,
and the panic propagated out of Compile — breaking both the no-panics-escape rule
and the contract the compiler is built around, that a malformed spec yields
diagnostics rather than a fault.

resolveAll wraps the call the way unmarshal wraps the parser. The recovered panic
joins the resolve errors the caller already turns into diagnostics rather than
aborting the compile, because a document that trips this is a malformed spec, not
an I/O or programmer error: it is now refused with an unresolved-ref error, and
the process survives.

The reproducer came from FuzzCycleDetector and is committed as a corpus entry, so
a plain `go test` replays it.
@OmarAlJarrah
OmarAlJarrah merged commit d4700bc into main Jul 27, 2026
1 check passed
@OmarAlJarrah
OmarAlJarrah deleted the fix/openapi-resolve-panic-barrier branch July 27, 2026 02:16
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.

1 participant