Repository navigation
fix(database): reject non-dict extra field in extra_validator - #44092
Conversation
json.loads() accepts any valid JSON, not just objects, so an `extra`
payload like "123", "null", "[1,2]", "true" or '"abc"' decoded to a
non-mapping and then hit extra_.get("metadata_params", {}), raising a
raw AttributeError. Marshmallow only converts ValidationError raised by
a field validator, and the database API only catches ValidationError
around schema.load(), so the AttributeError propagated as an opaque 500
instead of a validation error.
Guard the decoded value with the same isinstance + ValidationError
pattern already used a few lines below for metadata_cache_timeout.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Code Review Agent Run #eff883Actionable Suggestions - 0Review Details
Bito Usage GuideCommands Type the following command in the pull request comment and save the comment.
Refer to the documentation for additional commands. Configuration This repository uses Documentation & Help |
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## master #44092 +/- ##
===========================================
+ Coverage 57.47% 80.08% +22.60%
===========================================
Files 2922 2922
Lines 171962 171964 +2
Branches 39907 39908 +1
===========================================
+ Hits 98837 137719 +38882
+ Misses 72197 31664 -40533
- Partials 928 2581 +1653
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
rebenitez1802
left a comment
There was a problem hiding this comment.
Approve — correct, well-tested fix and a strict improvement: a raw AttributeError→500 becomes a proper 4xx validation error. Security model intact — POST/PUT /api/v1/database already requires can_write on Database, so this is robustness, not a privilege change, and the constant error message leaks nothing. One optional, pre-existing gap on an adjacent line is worth considering while you're here.
🟢 Low — Sibling metadata_params still 500s on a non-iterable value
The new guard fully closes the top-level case, but one level down at superset/databases/schemas.py:278, for key in extra_.get("metadata_params", {}) carries the same unguarded-iteration bug this PR targets. A payload whose top level is a dict (so it passes the new guard) but whose metadata_params is a non-iterable scalar — {"metadata_params": 123}, null, true, 1.5 — raises TypeError: 'int' object is not iterable. That isn't a ValidationError, so it propagates to Flask's catch-all and surfaces as the exact SIP-40 GENERIC_BACKEND_ERROR 500 this PR set out to eliminate. (List/string values happen to iterate and yield a handled-but-misleading ValidationError.) Pre-existing on master and outside this PR's title scope, so non-blocking — but it's the same bug class on the adjacent line and cheap to close in the same diff:
metadata_params = extra_.get("metadata_params", {})
if not isinstance(metadata_params, dict):
raise ValidationError(
[_("The metadata_params in Extra field must be a mapping from string keys to values.")]
)
metadata_signature = inspect.signature(MetaData)
for key in metadata_params:
...
SUMMARY
superset/databases/schemas.py::extra_validator— the shared marshmallowvalidate=callable for the
extrafield onDatabasePostSchema,DatabasePutSchema,DatabaseTestConnectionSchemaand friends — could let a rawAttributeErrorescape asan opaque HTTP 500 instead of returning a validation error.
Problem
json.loads()happily returns non-mapping values:json.loads("123")→int,"null"→None,"[1,2]"→list,"true"→bool,'"abc"'→str. None ofthose have
.get(), so the next line raisesAttributeError: 'int' object has no attribute 'get'.Nothing along the path turns that into a validation error:
ValidationErrorraised by a field validator; anythingelse propagates straight out of
schema.load();superset/databases/api.pypost()/put()wrapself.add_model_schema.load(...)/self.edit_model_schema.load(...)inexcept ValidationErroronly — nothing broader(
statsd_metricscatchesExceptionpurely to emit a metric and re-raises);@app.errorhandler(Exception)insuperset/views/error_handling.py, which renders it as a SIP-40GENERIC_BACKEND_ERROR500 whose message is the raw'int' object has no attribute 'get'.So any authenticated user with database create/edit permission sending
POST /api/v1/database/orPUT /api/v1/database/<pk>/with{"extra": "123"}(or anyother non-object JSON string) gets a 500 leaking an internal type error, where a 4xx
validation error is the correct response. This is a robustness/error-handling
bug, not a privilege issue: the request already requires the database write capability,
so no principal gains anything they were not entitled to.
Fix
Guard the decoded value with the same
isinstance+ValidationErrorpattern thatalready exists a few lines below in the very same function for
metadata_cache_timeout:The check is placed after the
json.JSONDecodeErrorhandler and before any.get()/inuse ofextra_, so malformed JSON still reports the existing decode message anddict-valued
extrabehaves exactly as before.This follows the same "raw exception → proper Superset exception" cleanup as
#42366 and #42401.
Behavior change: a request that previously returned an unhandled 500 now returns a
proper 4xx validation error. Strict improvement — no tradeoff to disclose. Valid
extrapayloads are unaffected.One incidental note: adding the branch pushed
extra_validatorfrom McCabe complexity10 to 11, so the function carries a
# noqa: C901. Its complexity is a flat sequence ofindependent field validations rather than nesting, and this matches ~144 existing
# noqa: C901usages undersuperset/. Extracting the neighboringmetadata_cache_timeoutblock into a helper would also work if reviewers prefer that,but it would enlarge an otherwise 5-line diff.
BEFORE/AFTER SCREENSHOTS OR ANIMATED GIF
N/A — API error-handling change, no UI surface.
TESTING INSTRUCTIONS
Automated:
pytest tests/unit_tests/databases/schema_tests.pyA new parametrized regression test,
test_extra_validator_rejects_non_dict_top_level_value, drives the real code paththrough
DatabasePostSchema().load(...)(not a direct call to the validator, somarshmallow's error handling is exercised) for all five non-mapping JSON shapes
(
123,null,[1, 2],true,"abc").Verified both ways:
AttributeError: 'str' object has no attribute 'get'atschemas.py:278metadata_params,metadata_cache_timeoutand JSON-decode-message tests — the newcheck does not fire ahead of the
JSONDecodeErrorhandler and does not changebehavior for dict-valued
extra.Manual:
Before: 500 (
GENERIC_BACKEND_ERROR, message'int' object has no attribute 'get').After: a 422 validation error naming the
extrafield.ADDITIONAL INFORMATION