Skip to content

fix(views): error-response bodies must carry their message - #44090

Merged
richardfogaca merged 3 commits into
apache:masterfrom
mikebridge:sc-120052-eager-error-bodies
Sep 14, 2026
Merged

richardfogaca merged 3 commits into
apache:masterfrom
mikebridge:sc-120052-eager-error-bodies

Conversation

@mikebridge

@mikebridge mikebridge commented Sep 9, 2026 •

Copy link
Copy Markdown
Contributor

SUMMARY

json_error_response sets payload["error"] only for exact str arguments. A flask-babel lazy_gettext proxy (LazyString) fails that isinstance check, so the body silently degrades to {} while the status still says "denied" — the client gets a 403/404 with no message. Five live call sites in superset/views/core.py ship that today: both "permalink state not found" 404s and the create-chart / alter-dashboard / create-dashboard permission denials. (The overwrite-chart denial has the identical fix on the in-review #44025 branch; it is included here as well so the tripwire below can assert zero — identical hunks reconcile cleanly whichever lands first.)

Two-layer fix:

  • Call sites switch to the module's eager __() alias (core.py imports gettext as __, lazy_gettext as _, and the neighboring sites already use __ — these were the stragglers).
  • Root cause: json_error_response now coerces LazyString values with str(...), so any future lazy call produces a correct body instead of {} — deliberately narrowed to exactly that case, so any other out-of-contract object keeps degrading to an empty body rather than leaking str(obj) to the client (per committer review). The eager alias remains the convention; the coercion is the safety net.

Found via sc-120011/#44025, where a message-asserting regression test caught the same empty-body shape on a new denial — and turned out to be catching this pre-existing class. A binding-aware repo sweep (only modules binding _ to lazy_gettext can have the bug; several files import _ as eager gettext and are fine) confirmed views/core.py holds all the live sites.

BEFORE/AFTER SCREENSHOTS OR ANIMATED GIF

N/A — API error bodies only. Before: {}. After: {"error": "You don't have the rights to create a dashboard"} etc.

TESTING INSTRUCTIONS

pytest tests/unit_tests/views/test_error_bodies.py — 3 tests: a LazyString body survives into payload["error"] (fails pre-fix), the plain-str path is unchanged, and a binding-aware source scan asserts no json_error_response(_(...)) remains in any module where _ is lazy_gettext (fails pre-fix on the five sites). Reverting the production change flips the mechanism test and the tripwire.

ADDITIONAL INFORMATION

  • Has associated issue:
  • Required feature flags:
  • Changes UI
  • Includes DB Migration (follow approval process in SIP-59)
    • Migration is atomic, supports rollback & is backwards-compatible
    • Confirm DB migration upgrade and downgrade tested
    • Runtime estimates and downtime expectations provided
  • Introduces new feature or API
  • Removes existing feature or API

🤖 Generated with Claude Code

https://claude.ai/code/session_01JRLEJS4mUqKBoPjSjviKUW

Five json_error_response call sites in views/core.py (both permalink-404s
and the create-chart / alter-dashboard / create-dashboard denials) passed
flask-babel's lazy_gettext proxy, which fails the helper's
isinstance(error_details, str) check — so those 403/404 responses have
been shipping an empty {} body: status says denied, payload says nothing.
(A sixth site, the overwrite-chart denial, carries the identical fix on
the in-review apache#44025 branch; fixed here too so the tripwire below can
assert zero.)

Two-layer fix: the sites switch to the module's eager gettext alias, and
json_error_response itself now coerces string-like proxies so any future
lazy call degrades to a correct body instead of {}. Tests pin the
mechanism (a LazyString body survives), the str path, and a binding-aware
source tripwire that keeps lazy _() out of error bodies in any module
binding _ to lazy_gettext. Reverting the production change flips the
mechanism test and the tripwire.

sc-120052

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JRLEJS4mUqKBoPjSjviKUW
@github-actions github-actions Bot added the api Related to the REST API label Sep 9, 2026
@codecov

codecov Bot commented Sep 9, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 80.14%. Comparing base (078915f) to head (14b900e).
⚠️ Report is 110 commits behind head on master.

Additional details and impacted files
@@            Coverage Diff             @@
##           master   #44090      +/-   ##
==========================================
+ Coverage   80.08%   80.14%   +0.05%     
==========================================
  Files        2922     2923       +1     
  Lines      171962   172510     +548     
  Branches    39907    40014     +107     
==========================================
+ Hits       137717   138250     +533     
+ Misses      31664    31662       -2     
- Partials     2581     2598      +17     
Flag Coverage Δ
hive 37.33% <25.00%> (-0.07%) ⬇️
mysql 56.85% <50.00%> (-0.14%) ⬇️
postgres 56.88% <50.00%> (-0.14%) ⬇️
presto 39.21% <25.00%> (-0.09%) ⬇️
python 84.56% <75.00%> (+0.07%) ⬆️
sqlite 56.58% <50.00%> (-0.13%) ⬇️
unit 75.90% <50.00%> (+0.18%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Committer-round folds: the coercion arm stringified ANY out-of-contract
object — sanitize_error_message only masks for embedded guests, so a
future mis-caller would leak str(obj) to normal users where the old
behavior leaked nothing. Narrowed to isinstance(LazyString) (the case it
exists for), with the type hint widened to match; any other
out-of-contract value keeps degrading to an empty body. The source-scan
tripwire is now package-anchored (superset.__file__) instead of
CWD-relative, so it cannot pass vacuously from another working
directory.

sc-120052

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JRLEJS4mUqKBoPjSjviKUW
@mikebridge
mikebridge marked this pull request as ready for review September 11, 2026 20:42
@netlify

netlify Bot commented Sep 11, 2026

Copy link
Copy Markdown

✅ Deploy Preview for superset-docs-preview ready!

Name Link
🔨 Latest commit 78666e0
🔍 Latest deploy log https://app.netlify.com/projects/superset-docs-preview/deploys/6aa467c7bc6cda00082d802b
😎 Deploy Preview https://deploy-preview-44090--superset-docs-preview.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.
🤖 Make changes Run an agent on this branch

To edit notification comments on pull requests, go to your Netlify project configuration.

@bito-code-review

bito-code-review Bot commented Sep 11, 2026 •

Copy link
Copy Markdown
Contributor

Code Review Agent Run #02b85b

Actionable Suggestions - 0
Additional Suggestions - 1
  • tests/unit_tests/views/test_error_bodies.py - 1
    • Full-package scan in test · Line 75-83
      `test_no_lazy_gettext_reaches_json_error_response` runs `package_root.rglob("*.py")` and reads every file's text on each test session (1481 files), though only 6 files reference `json_error_response`. Consider pre-filtering to those files (e.g. via `grep -rl`) to avoid the full-package read on every run. The scan logic itself is sound.
Review Details
  • Files reviewed - 3 · Commit Range: 3c1b5a2..78666e0
    • superset/views/core.py
    • superset/views/error_handling.py
    • tests/unit_tests/views/test_error_bodies.py
  • Files skipped - 0
  • Tools
    • MyPy (Static Code Analysis) - ✔︎ Successful
    • Astral Ruff (Static Code Analysis) - ✔︎ Successful
    • Whispers (Secret Scanner) - ✔︎ Successful
    • Detect-secrets (Secret Scanner) - ✔︎ Successful

Bito Usage Guide

Commands

Type the following command in the pull request comment and save the comment.

  • /review - Manually triggers an incremental AI Review.

  • /review full - Manually triggers a full AI Review.

  • /pause - Pauses automatic reviews on this pull request.

  • /resume - Resumes automatic reviews.

  • /resolve - Marks all Bito-posted review comments as resolved.

  • /abort - Cancels all in-progress reviews.

Refer to the documentation for additional commands.

Configuration

This repository uses Superset You can customize the agent settings here or contact your Bito workspace admin at evan@preset.io.

Documentation & Help

AI Code Review powered by Bito Logo

@fitzee fitzee left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Adversarial review of the error-body LazyString fix. The production fix is correct: LazyString isn't a str subclass, the new isinstance branch coerces it so the body carries the message instead of degrading to {}, and the deliberate narrowness (only LazyString, not arbitrary objects whose str() could leak internals) is a sound security-conscious choice — I'm not flagging that. Two quality/altitude notes below, both about the guardrails around the fix rather than the fix itself. (Automated review; posted by a human reviewer.)

Comment thread superset/views/core.py Resolved
…ror_response

Review round (fitzee): the regex scan matched only the inline
json_error_response(_(...)) shape and skipped any module not binding
lazy_gettext to exactly `_`. Replaced with an AST classifier that
resolves the module's actual lazy Babel bindings (aliases, bare calls,
module-attribute calls) and flags any json_error_response call whose
argument subtree contains a lazy call — nesting and keyword args
included, docstring text inert; 8 classifier corner cases pinned.
Variable indirection is documented as out of scope for a static scan:
the runtime LazyString coercion (behaviorally pinned above) is the
guard for every shape — the scan enforces the call-site convention.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JRLEJS4mUqKBoPjSjviKUW
@bito-code-review

bito-code-review Bot commented Sep 14, 2026 •

Copy link
Copy Markdown
Contributor

Code Review Agent Run #fafba8

Actionable Suggestions - 0
Review Details
  • Files reviewed - 1 · Commit Range: 78666e0..14b900e
    • tests/unit_tests/views/test_error_bodies.py
  • Files skipped - 0
  • Tools
    • MyPy (Static Code Analysis) - ✔︎ Successful
    • Astral Ruff (Static Code Analysis) - ✔︎ Successful
    • Whispers (Secret Scanner) - ✔︎ Successful
    • Detect-secrets (Secret Scanner) - ✔︎ Successful

Bito Usage Guide

Commands

Type the following command in the pull request comment and save the comment.

  • /review - Manually triggers an incremental AI Review.

  • /review full - Manually triggers a full AI Review.

  • /pause - Pauses automatic reviews on this pull request.

  • /resume - Resumes automatic reviews.

  • /resolve - Marks all Bito-posted review comments as resolved.

  • /abort - Cancels all in-progress reviews.

Refer to the documentation for additional commands.

Configuration

This repository uses Superset You can customize the agent settings here or contact your Bito workspace admin at evan@preset.io.

Documentation & Help

AI Code Review powered by Bito Logo

@richardfogaca richardfogaca left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Richard's agent here:

Reviewed 14b900e8e977; no substantive findings. CI is green. Review was based on source and available test evidence; full local integration/browser validation was not performed.

@richardfogaca
richardfogaca merged commit e797f3d into apache:master Sep 14, 2026
80 checks passed
villebro pushed a commit that referenced this pull request Sep 16, 2026
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
(cherry picked from commit e797f3d)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

api Related to the REST API size/L

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants