Skip to content

Validate edge device ID headers#4825

Open
fallintoplace wants to merge 2 commits into
NVIDIA:mainfrom
fallintoplace:fix-edge-device-id-header
Open

Validate edge device ID headers#4825
fallintoplace wants to merge 2 commits into
NVIDIA:mainfrom
fallintoplace:fix-edge-device-id-header

Conversation

@fallintoplace

Copy link
Copy Markdown
Contributor

Summary

  • Keep X-Flare-Device-ID as the canonical device identity for FEG requests.
  • Reject requests when X-Flare-Device-Info contains a conflicting device_id.
  • Add focused request-context tests for absent, matching, and mismatched metadata device IDs.

Why

The FEG parser creates DeviceInfo from X-Flare-Device-ID, but then blindly applies fields parsed from X-Flare-Device-Info. A device_id embedded in the metadata header could override the canonical ID used for routing, selection, device tracking, and result attribution. The simulation client already removes device_id from the metadata header, so treating the standalone ID header as canonical matches the existing client behavior.

Testing

  • .venv/bin/python -m pytest tests/unit_test/edge/web/views/feg_views_test.py -q
  • .venv/bin/python -m black --check nvflare/edge/web/views/feg_views.py tests/unit_test/edge/web/views/feg_views_test.py
  • .venv/bin/python -m isort --check-only nvflare/edge/web/views/feg_views.py tests/unit_test/edge/web/views/feg_views_test.py
  • .venv/bin/python -m flake8 nvflare/edge/web/views/feg_views.py tests/unit_test/edge/web/views/feg_views_test.py
  • source .venv/bin/activate && ./runtest.sh -s

@greptile-apps

greptile-apps Bot commented Jun 21, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR adds a validation step in _process_headers() that rejects FEG requests where the X-Flare-Device-Info metadata header contains a device_id that conflicts with the canonical X-Flare-Device-ID header, and moves the canonical-ID reassignment outside the conditional block to always enforce it.

  • feg_views.py: After parsing X-Flare-Device-Info, the code now reads back the (possibly overwritten) device_id from the DeviceInfo dict and raises HTTP 400 if it doesn't match the canonical header value; device_info.device_id = device_id is then unconditionally applied to enforce the canonical ID.
  • feg_views_test.py: New test module covering all four header-combination branches: absent info header, info header without device_id, matching device_id, and mismatching device_id.

Confidence Score: 5/5

The change is safe to merge: the new validation correctly rejects conflicting device IDs in all header combinations, and the unconditional canonical-ID reassignment ensures no bypass path exists.

The mismatch check works correctly across all four header-combination branches, all of which are covered by the new tests. The only observation is a variable-naming subtlety that does not affect correctness or security.

No files require special attention; both changed files are straightforward and well-tested.

Important Files Changed

Filename Overview
nvflare/edge/web/views/feg_views.py Adds device-ID conflict detection; logic is correct but header_device_id is read from the post-update dict (which retains the constructor value when the header omits device_id), making the variable name subtly misleading.
tests/unit_test/edge/web/views/feg_views_test.py New test file with four focused parametric branches covering all code paths in the updated _process_headers(); assertions are accurate and complete.

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    participant C as Edge Client
    participant FEG as FEG View (_process_headers)
    participant DI as DeviceInfo

    C->>FEG: POST /job (X-Flare-Device-ID: "dev-1")
    FEG->>FEG: "device_id = headers["X-Flare-Device-ID"]"
    FEG->>DI: DeviceInfo("dev-1")
    Note over DI: dict["device_id"] = "dev-1"

    alt X-Flare-Device-Info present
        FEG->>DI: from_query_string(device_info_header)
        Note over DI: dict updated (may overwrite device_id)
        FEG->>DI: get("device_id") → header_device_id
        alt header_device_id conflicts with canonical
            FEG-->>C: 400 INVALID_REQUEST (Device ID mismatch)
        else no conflict
            FEG->>DI: "device_id = "dev-1" (canonical enforced)"
        end
    else X-Flare-Device-Info absent
        FEG->>DI: "device_id = "dev-1" (canonical enforced)"
    end

    FEG-->>C: proceed with request
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
    participant C as Edge Client
    participant FEG as FEG View (_process_headers)
    participant DI as DeviceInfo

    C->>FEG: POST /job (X-Flare-Device-ID: "dev-1")
    FEG->>FEG: "device_id = headers["X-Flare-Device-ID"]"
    FEG->>DI: DeviceInfo("dev-1")
    Note over DI: dict["device_id"] = "dev-1"

    alt X-Flare-Device-Info present
        FEG->>DI: from_query_string(device_info_header)
        Note over DI: dict updated (may overwrite device_id)
        FEG->>DI: get("device_id") → header_device_id
        alt header_device_id conflicts with canonical
            FEG-->>C: 400 INVALID_REQUEST (Device ID mismatch)
        else no conflict
            FEG->>DI: "device_id = "dev-1" (canonical enforced)"
        end
    else X-Flare-Device-Info absent
        FEG->>DI: "device_id = "dev-1" (canonical enforced)"
    end

    FEG-->>C: proceed with request
Loading

Reviews (3): Last reviewed commit: "Merge branch 'main' into fix-edge-device..." | Re-trigger Greptile

Comment thread nvflare/edge/web/views/feg_views.py
Comment thread tests/unit_test/edge/web/views/feg_views_test.py
@fallintoplace fallintoplace force-pushed the fix-edge-device-id-header branch from f275c5d to 8b5f5cc Compare June 21, 2026 20:18
@YuanTingHsieh

Copy link
Copy Markdown
Collaborator

@codecov-commenter

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 56.26%. Comparing base (a196b56) to head (331d9f2).

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #4825      +/-   ##
==========================================
+ Coverage   56.07%   56.26%   +0.19%     
==========================================
  Files         967      967              
  Lines       91966    91970       +4     
==========================================
+ Hits        51571    51751     +180     
+ Misses      40395    40219     -176     
Flag Coverage Δ
unit-tests 56.26% <100.00%> (+0.19%) ⬆️

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.

@fallintoplace

Copy link
Copy Markdown
Contributor Author

@YuanTingHsieh I have updated both branch. Thank you for your attention.

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.

3 participants