Skip to content

fix(api): update-traits returns 400 for malformed request body - #8199

Merged
Zaimwa9 merged 1 commit into
Flagsmith:mainfrom
bardock-2393:fix/update-traits-malformed-payload-400
Aug 14, 2026
Merged

fix(api): update-traits returns 400 for malformed request body#8199
Zaimwa9 merged 1 commit into
Flagsmith:mainfrom
bardock-2393:fix/update-traits-malformed-payload-400

Conversation

@bardock-2393

Copy link
Copy Markdown
Contributor
  • I have read the Contributing Guide.
  • I have added information to docs/ if required so people know about the feature.
  • I have filled in the "Changes" section below.
  • I have filled in the "How did you test this code" section below.

Changes

Closes #7951

The edge identity update-traits endpoint (PUT /api/v1/environments/<api_key>/edge-identities/<uuid>/update-traits/) crashed with a 500 error instead of returning a 400 when the request body wasn't a JSON object — for example, sending a JSON array as the payload.

This happened because the view unpacked the request body directly into a pydantic model (TraitModel(**request.data)). When request.data was a list, Python raised a TypeError on the ** unpacking itself, before pydantic ever got a chance to validate anything, so the existing except pydantic.ValidationError handler never caught it.

The fix adds an explicit check that the request body is a JSON object before unpacking it, returning a standard 400 validation error otherwise — consistent with how the endpoint already reports other validation failures.

How did you test this code?

Added a test (test_edge_identities_update_trait__malformed_payload__returns_400) that sends a JSON array as the request body to the update-traits endpoint and asserts a 400 response. I confirmed this test reproduces the original crash on the unpatched code (fails with the exact TypeError from the linked Sentry issue) and passes with the fix applied.

Ran the full test module for this view (api/tests/integration/edge_api/identities/test_edge_identity_viewset.py, 22 tests) against a local Postgres database — all passing. Also ran ruff (lint + format) and mypy on the changed files with no issues.

Sending a non-object payload (e.g. a JSON array) to the edge identity
update-traits endpoint unpacked request.data directly into TraitModel,
raising an uncaught TypeError and a 500 instead of a validation error.
@bardock-2393
bardock-2393 requested a review from a team as a code owner July 31, 2026 19:30
@bardock-2393
bardock-2393 requested review from khvn26 and removed request for a team July 31, 2026 19:30
@vercel

vercel Bot commented Jul 31, 2026

Copy link
Copy Markdown

@bardock-2393 is attempting to deploy a commit to the Flagsmith Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai

coderabbitai Bot commented Jul 31, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 0f787986-f5ad-4fd0-a0b4-2db4cd916e34

📥 Commits

Reviewing files that changed from the base of the PR and between fc33002 and cd5aca0.

📒 Files selected for processing (2)
  • api/edge_api/identities/views.py
  • api/tests/integration/edge_api/identities/test_edge_identity_viewset.py

📝 Walkthrough

Walkthrough

The update_traits endpoint now validates that request data is a JSON object before creating a TraitModel. Non-object payloads raise a DRF ValidationError. An integration test verifies that a JSON list returns HTTP 400.

Estimated code review effort: 2 (Simple) | ~10 minutes

✨ Finishing Touches 💡 1
🛠️ Fix failing CI checks 💡
  • Create stacked PR
  • Commit on current branch

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions github-actions Bot added the api Issue related to the REST API label Jul 31, 2026
@codecov

codecov Bot commented Jul 31, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 98.57%. Comparing base (9f3a4ee) to head (cd5aca0).
⚠️ Report is 2 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #8199      +/-   ##
==========================================
- Coverage   98.71%   98.57%   -0.15%     
==========================================
  Files        1531     1531              
  Lines       61241    61267      +26     
==========================================
- Hits        60453    60392      -61     
- Misses        788      875      +87     

☔ 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.

@Zaimwa9

Zaimwa9 commented Aug 14, 2026

Copy link
Copy Markdown
Contributor

@themis-blindfold review

@themis-blindfold

Copy link
Copy Markdown
Contributor

⚖️ Themis review: ✅ Ship it

The update_traits endpoint unpacked request.data with ** directly into a pydantic model. When the payload was a JSON array instead of an object, Python raised TypeError before pydantic validation ran, producing a 500 instead of a 400. The fix adds an isinstance(request.data, dict) guard and raises ValidationError — correct, minimal, and matches the existing error-handling style in the method. The regression test reproduces the exact Sentry crash and passes with the fix.

CI note: the four failed checks (Fix Documentation Artefacts, three Vercel deployments) are infrastructure/docs jobs unrelated to this change. All API unit tests (3.11, 3.12, 3.13), E2E tests, codecov/patch, and pre-commit passed.

Area Score
🎯 Correctness 5/5
🧪 Test coverage 5/5
📐 Code quality 5/5
🚀 Product impact 3/5
📝 Walkthrough
  • edge_api/identities/views.py — adds a type guard before **request.data unpacking in update_traits, returning 400 for non-dict payloads instead of crashing with a 500.
  • test_edge_identity_viewset.py — new regression test sends a JSON array to the endpoint and asserts 400.
🧪 How to verify
  1. Run the new test: pytest api/tests/integration/edge_api/identities/test_edge_identity_viewset.py::test_edge_identities_update_trait__malformed_payload__returns_400 -v
  2. Revert the guard in views.py and re-run — confirm the test fails with TypeError.
  3. Run the full test module to check for regressions: pytest api/tests/integration/edge_api/identities/test_edge_identity_viewset.py -v

Product take: Fixes a 500 crash on a dashboard API endpoint when malformed JSON is sent. Low blast radius — only affects edge identity trait updates — but turns an opaque server error into a clear validation message for API consumers.

🧭 Assumptions & unverified claims

No unverified assumptions or claims.

A guard clause doing exactly what it should — keeping ** from trying to unpack things that aren't mappings since 1991. · reviewed at cd5aca0

@Zaimwa9 Zaimwa9 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.

Thanks for the contribution

@Zaimwa9
Zaimwa9 merged commit fccb632 into Flagsmith:main Aug 14, 2026
23 of 28 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

api Issue related to the REST API

Projects

None yet

Development

Successfully merging this pull request may close these issues.

update-traits crash when traits malformed

2 participants