# Patch Analysis for CVE-2026-23537 Variant Search

## Fix inspected

- Repository: `https://github.com/feast-dev/feast`
- Vulnerable commit tested: `be1b52227e1ade9a3be9836391ade45eb1a26909`
- Fixed commit tested: `4018e7b0c39825a5647fb17fc5607d72fb4bc0ce`
- Latest default branch observed during this run: `origin/master` at `f296d4ba14c5d512429219b2b7845673e0fe524d`
- Fix commit subject: `Clean up document endpoints`

The fixed commit deletes the document endpoints rather than attempting to repair their path validation. The diff removes:

1. From `sdk/python/feast/feature_server.py`:
   - `ReadDocumentRequest`
   - `SaveDocumentRequest`
   - `@app.post("/read-document", dependencies=[Depends(inject_user_details)])`
   - `@app.post("/save-document", dependencies=[Depends(inject_user_details)])`
2. From `sdk/python/feast/ui_server.py`:
   - `SaveDocumentRequest`
   - `@app.post("/save-document")`
3. Related unit tests for those removed endpoints from `sdk/python/tests/unit/test_ui_server.py`.

The exact vulnerable sink in both the Feature Server and Web UI endpoint was the same pattern:

```python
file_path = Path(request.file_path).resolve()
if not str(file_path).startswith(os.getcwd()):
    return {"error": "Invalid file path"}

base_name = file_path.stem
labels_file = file_path.parent / f"{base_name}-labels.json"
with open(labels_file, "w", encoding="utf-8") as file:
    json.dump(request.data, file, indent=2, ensure_ascii=False)
```

## What the fix changes

The fix eliminates the remote write/read API surface. On the tested fixed commit:

- `sdk/python/feast/feature_server.py` no longer contains `/save-document` or `/read-document`.
- `sdk/python/feast/ui_server.py` no longer contains `/save-document`.
- `git grep` on latest `origin/master` also found no `save-document`/`read-document` in those files.
- Runtime testing confirmed a patched UI server reports `HAS_SAVE_DOCUMENT=False` and returns HTTP `405` for POST `/save-document`; no config file or RCE marker is created.

The fix does not change `load_static_artifacts()` in `feature_server.py`. That loader remains a documented/product startup extension point that imports `static_artifacts.py` from the feature repository and calls `load_artifacts(app)`. This is relevant to impact chaining, but the vulnerability depended on a remote attacker being able to write a JSON/config file into the repository; once the document endpoints are gone, the tested remote write primitive is removed.

## Fix assumptions

The patch assumes that removing the document endpoints is acceptable and complete for this bug class. That assumption is stronger than trying to validate paths correctly because it removes both the vulnerable sink and the alternate surfaces that reached it.

The security model relevant to this analysis is Feast's remote server boundary. Feast documentation describes the Python Feature Server and Web UI as HTTP services. Feast permissions documentation states that the default auth configuration is `no_auth`, meaning no permission enforcement is applied if `auth` is omitted. The Feature Server document endpoint used `Depends(inject_user_details)`, but under `no_auth` this does not require credentials. The UI endpoint had no dependency at all.

The repository did not contain a top-level `SECURITY.md` in the tested checkout. The closest in-repo scope documentation found was the permissions model documentation, including that authorization enforcement applies on Python servers and that `no_auth` means no permission enforcement.

## Code paths/inputs the fix does and does not cover

### Covered

The fix covers all in-repository occurrences of the vulnerable document API names and the vulnerable `Path.resolve()` + `str.startswith(os.getcwd())` + JSON write sink:

- `feature_server.py` `/save-document`: removed.
- `feature_server.py` `/read-document`: removed. This was primarily a read primitive, not the arbitrary write sink, but it was part of the same document endpoint cleanup.
- `ui_server.py` `/save-document`: removed. This is the meaningful alternate entrypoint found during variant analysis.

### Not covered / residual behavior

- `feature_server.py::load_static_artifacts()` remains present. This function intentionally imports and executes feature-repository `static_artifacts.py` on server startup. This is a legitimate extension point, not itself the same vulnerability. It only becomes part of the exploit chain when a remote attacker can place or modify the JSON/config consumed by such a deployment component.
- Normal local filesystem writes performed by CLI/repository management code remain out of scope for this CVE because they do not cross the same unauthenticated HTTP trust boundary.
- The fix does not introduce a reusable safe path-containment helper. If similar remote file endpoints are reintroduced later, they must use real containment checks and authorization rather than repeating the removed string-prefix pattern.

## Variant/bypass assessment

A distinct vulnerable entrypoint was found: `sdk/python/feast/ui_server.py` exposed unauthenticated `POST /save-document` and used the same sink as the Feature Server endpoint. Runtime proof showed the UI endpoint could write `ui_variant_static_artifacts-labels.json` into a feature repository. A subsequent real Feast Feature Server startup loaded `static_artifacts.py`, consumed the attacker-written JSON, and executed the configured startup hook, creating `bundle/vuln_variant/artifacts/ui_variant_rce_marker.vulnerable.txt`.

However, this is **not a bypass** of commit `4018e7b0c39825a5647fb17fc5607d72fb4bc0ce`, because that commit removes the UI endpoint as well. The fixed runtime returned HTTP `405` for POST `/save-document`, and latest `origin/master` did not contain the document endpoint strings in the relevant server files.

## Behavior comparison

| Target | Entry point | Observed behavior |
| --- | --- | --- |
| Vulnerable `be1b522...` | Feature Server `/save-document` | Confirmed by prior repro: arbitrary JSON write and RCE chain. |
| Vulnerable `be1b522...` | Web UI `/save-document` | Confirmed by this variant stage: unauthenticated JSON write and same RCE chain. |
| Fixed `4018e7b...` | Feature Server `/save-document` | Endpoint absent; prior repro observed 404. |
| Fixed `4018e7b...` | Web UI `/save-document` | Endpoint absent; this stage observed HTTP 405 from static/catch-all routing and no file write. |
| Latest `origin/master` `f296d4b...` | Document endpoints | Static scan found no `save-document`/`read-document` in relevant server files. |

## Recommendation

The current fix appears complete for the tested bug class because it removes both known remote document-write entrypoints. To keep it complete:

- Keep `/save-document` and `/read-document` absent from remotely reachable servers unless there is a strong product requirement.
- If any future file/document write endpoint is added, require authentication/authorization and use `Path.resolve().is_relative_to(allowed_base.resolve())` or equivalent parent-path containment checks.
- Do not derive output paths from attacker-controlled absolute paths; use a fixed server-controlled output directory.
- Add regression tests for both Feature Server and Web UI endpoints to assert `/save-document` is not registered.
- Add a targeted static check for `str(path).startswith(os.getcwd())` around remote file operations.
