Deep-copy ERROR_SCHEMA/PROBLEM_SCHEMA before handing them out - #78
Merged
Merged
Conversation
.freeze on these constants is shallow — only the top-level Hash is frozen, not the Hashes nested inside it — but Permittable::OpenAPI handed the constants out by direct reference through .components and .document. A caller mutating a nested level of ITS document (an easy mistake, since the rest of the document is caller-owned data) silently succeeded with no FrozenError and permanently corrupted the shared constant for every document generated for the rest of the process, e.g. a long-lived Rails console or admin endpoint regenerating docs. error_schema now returns .deep_dup of the constant, the same ActiveSupport helper the contract registry already uses to copy authored default:/example: values before freezing them. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The bug
Permittable::OpenAPI::ERROR_SCHEMAandPROBLEM_SCHEMAare.freezed, but Ruby's.freezeis shallow — it only freezes the top-levelHash, not theHashes nested inside it..components(and, through it,.document) handed these constants out by direct reference:So a caller mutating a nested level of ITS OWN generated document — an easy mistake, since everything else about the returned document is caller-owned data — silently succeeded with no
FrozenError, and permanently corrupted the shared constant for every document generated for the rest of the process:In a long-lived process — a Rails console, or an admin endpoint that regenerates the OpenAPI doc on demand — one caller's mutation leaks into every other caller's document from then on.
The fix
error_schemanow returns.deep_dupof the constant instead of the constant itself:deep_dupis ActiveSupport's existing recursive-copy helper — already required bylib/permittable.rband already the pattern this gem uses to copy authoreddefault:/example:values before freezing them (seelib/permittable.rb), so this reuses rather than duplicates that mechanism.VIOLATION_SCHEMA, nested inside bothERROR_SCHEMAandPROBLEM_SCHEMA, is copied along with them sincedeep_duprecurses through nested Hashes/Arrays.lib/permittable/json_schema.rbdoesn't need an equivalent change here — it builds its schema Hashes fresh on every call rather than freezing and sharing a constant — so this fix is scoped toopen_api.rbonly.Verification
spec/open_api_spec.rb), confirmed to fail before the fix (mutating a returned document's nested schema silently succeeded and leaked into a freshly generated document) and pass afterbundle exec rspec— 852 examples, 0 failuresbundle exec rubocop lib/permittable/open_api.rb spec/open_api_spec.rb— no offenses🤖 Generated with Claude Code