CVE-2026-23537: Verified Repro With Script Download
CVE-2026-23537: Feast Feature Server unauthenticated arbitrary file write to RCE
CVE-2026-23537 is verified against feast-dev/feast · github. Affected versions: feast < 0.60.0 (the /save-document endpoint was introduced in v0.59.0 via PR #5865, commit 2081b55de32b830b4fb64e93b6d88cdfaeff2378). Fixed in 0.60.0. Vulnerability class: RCE. This critical reproduction includes runnable sandbox proof, artifacts, and a plain-text agent view under REPRO-2026-00244.
What Is CVE-2026-23537?
CVE-2026-23537 is a critical unauthenticated arbitrary file write vulnerability in Feast's Python Feature Server, exposed through the /save-document endpoint, that can be escalated to remote code execution. Pruva reproduced it (reproduction REPRO-2026-00244).
CVE-2026-23537 Severity & CVSS Score
CVE-2026-23537 is rated critical severity, with a CVSS base score of 9.1 out of 10.
Critical — the most severe class — typically remotely exploitable with severe impact. Treat as an emergency.
Affected feast-dev/feast Versions
feast-dev/feast · github versions feast < 0.60.0 (the /save-document endpoint was introduced in v0.59.0 via PR #5865, commit 2081b55de32b830b4fb64e93b6d88cdfaeff2378) are affected.
How to Reproduce CVE-2026-23537
pruva-verify REPRO-2026-00244 curl -O https://pruva.dev/api/v1/reproductions/REPRO-2026-00244/artifacts/bundle/repro/reproduction_steps.sh && chmod +x reproduction_steps.sh && ./reproduction_steps.sh Proof of Reproduction for CVE-2026-23537
- reached the target end-to-end
- full exploit chain demonstrated
- on the real production code path
- high confidence
- the upstream fix blocks the same trigger
Unauthenticated POST to /save-document with attacker-controlled file_path pointing to the active Feast feature repository static_artifacts.json and data containing a startup_hook.shell JSON payload
- POST /save-document
- SaveDocumentRequest(file_path,data)
- Path(file_path).resolve()
- naive str(file_path).startswith(os.getcwd()) bypass when cwd=/ or string-prefix sibling traversal
- write static_artifacts-labels.json
- Feast Feature Server restart
- feature_server.py load_static_artifacts() imports feature_repo/static_artifacts.py
- deployment component consumes attacker-written JSON…
reproduction_steps.sh Feast Web UI exposed an unauthenticated /save-document endpoint that reached the same Path.resolve()+str.startswith(os.getcwd()) arbitrary JSON write sink as the Feature Server endpoint; vulnerable revisions allow the same write-to-static-artifacts RCE chain, but the tested fix removes this UI endpoint so it is not a…
How the agent worked
Root Cause and Exploit Chain for CVE-2026-23537
CVE-2026-23537 is an unauthenticated arbitrary JSON file write in the Feast Python Feature Server /save-document API. The vulnerable endpoint accepts a user-supplied file_path, resolves it, checks it with a naive string-prefix comparison against os.getcwd(), and then writes request.data to a derived *-labels.json path. The check can be bypassed, allowing a remote unauthenticated caller to place attacker-controlled JSON in locations writable by the Feast service. In this run, that write was leveraged into code execution through Feast's real feature_server.py startup path: load_static_artifacts() imports the deployment's static_artifacts.py, that deployment component consumes the attacker-written static_artifacts-labels.json, and the configured startup hook executes under the Feast server process account.
- Package/component affected: Feast (
feast-dev/feast), Python Feature Server, specificallysdk/python/feast/feature_server.py/save-document. - Affected versions: vulnerable at commit
be1b52227e1ade9a3be9836391ade45eb1a26909; fixed by commit4018e7b0c39825a5647fb17fc5607d72fb4bc0ce, which removes the document endpoints from the Feature Server and UI server. Versions/commits that include/save-documentwith the naivestr(file_path).startswith(os.getcwd())check are affected. - Risk level and consequences: Critical. A remote unauthenticated attacker can write arbitrary JSON files wherever the Feast service account has filesystem write permissions. In deployments where Feast startup/static-artifact configuration or other product/deployment components consume JSON files from writable repository paths, the file write can be chained to command execution as the Feast service account. Even without the RCE chain, the primitive enables server-side data/configuration corruption and arbitrary JSON placement.
Impact Parity
- Disclosed/claimed maximum impact: code execution via unauthenticated
/save-documentarbitrary file write. - Reproduced impact from this run: code execution. The script sends unauthenticated HTTP POSTs to the real Feast Feature Server
/save-documentendpoint, writesstatic_artifacts-labels.jsoninto the active Feast feature repository, restarts the real Feast Feature Server, and records execution of an attacker-controlled startup hook by the Feastfeature_server.pyload_static_artifacts()product path. - Parity:
full. - Not demonstrated: No privilege escalation beyond the Feast service account was attempted; the code execution is demonstrated as the user running the Feast Feature Server in the sandbox.
Root Cause
In vulnerable sdk/python/feast/feature_server.py, /save-document implements insufficient path validation:
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)
There are two core issues:
- Unauthenticated or no-auth deployments can reach the endpoint remotely. The reproduced feature repository uses Feast
auth: no_auth, and the POSTs require no credentials. - The path restriction is a string-prefix check, not a directory containment check. If the Feature Server is started with
cwd=/, every absolute path starts with/, so any absolute writable path passes. Even whencwdis not/, a path such as/tmp/feast_exploit_repo/../feast_exploit_repo-evil/payload.jsonresolves to/tmp/feast_exploit_repo-evil/payload.json, which still starts with the string/tmp/feast_exploit_repobut is outside that directory.
The fixed commit is 4018e7b0c39825a5647fb17fc5607d72fb4bc0ce (Clean up document endpoints): https://github.com/feast-dev/feast/commit/4018e7b0c39825a5647fb17fc5607d72fb4bc0ce. It removes /save-document and /read-document from the Python Feature Server/UI server, eliminating this write primitive. The reproduction verifies the fixed checkout returns HTTP 404 for /save-document and does not create the attacker-controlled file.
Reproduction Steps
- Run
bundle/repro/reproduction_steps.sh. - The script reuses or creates the Feast repository checkout using
bundle/project_cache_context.json, checks out:- vulnerable commit
be1b52227e1ade9a3be9836391ade45eb1a26909inrepo-vuln, and - fixed commit
4018e7b0c39825a5647fb17fc5607d72fb4bc0ceinrepo.
- vulnerable commit
- It creates a minimal real Feast feature repository with
auth: no_auth, a validFeatureStore, and astatic_artifacts.pydeployment startup component. - For two vulnerable attempts, it starts the real Feast Feature Server with the vulnerable code, sends an unauthenticated POST to
/save-document, verifies the writtenstatic_artifacts-labels.json, restarts the real Feature Server, and observes Feast'sload_static_artifacts()path importstatic_artifacts.py, consume the attacker-written JSON, and execute the configured startup hook. - It also proves the
../string-prefix traversal variant writes outside the server working directory. - For two fixed attempts, it starts the fixed Feature Server, confirms
/save-documentreturns 404, confirms no config file is written, and confirms the deployment component loads without executing any attacker hook.
Expected successful completion ends with:
=== REPRODUCTION COMPLETE ===
Vulnerability: CVE-2026-23537 Feast Feature Server /save-document arbitrary JSON file write to code execution
Evidence
Key current-run evidence is stored under bundle/logs/, bundle/artifacts/, and bundle/repro/runtime_manifest.json.
bundle/logs/reproduction_steps.log— full top-level script transcript. It shows two vulnerable attempts, the additional traversal proof, and two fixed negative controls completing successfully.bundle/logs/vuln_attempt1_writer.logandbundle/logs/vuln_attempt2_writer.log— real vulnerable Feast Feature Server startup logs, including:MODULE_PATH=.../repo-vuln/sdk/python/feast/feature_server.pyHAS_SAVE_DOCUMENT=TrueHAS_STATIC_ARTIFACTS_LOADER=TruePOST /save-document HTTP/1.1" 200 OK
bundle/artifacts/http/vuln_attempt1_save_request.jsonandbundle/artifacts/http/vuln_attempt2_save_request.json— unauthenticated attacker-controlled JSON POST bodies.bundle/artifacts/http/vuln_attempt1_save_response.jsonandbundle/artifacts/http/vuln_attempt2_save_response.json— vulnerable responses showing successful writes tostatic_artifacts-labels.json.bundle/artifacts/feature_repo/static_artifacts-labels.json.attempt1and.attempt2— the exact attacker-written JSON consumed by the Feast deployment component.bundle/logs/vuln_attempt1_consumer.logandbundle/logs/vuln_attempt2_consumer.log— real Feast Feature Server restart logs proving the product path consumed the written file:[static_artifacts] Feast deployment component loaded ...[static_artifacts] Consuming startup config ...[static_artifacts] Executing configured startup hook ...[static_artifacts] hook exit code: 0
bundle/artifacts/rce_proof_vuln_attempt1.txtandbundle/artifacts/rce_proof_vuln_attempt2.txt— code-execution markers created by the startup hook. They includeFEAST_STATIC_ARTIFACT_RCE attempt=...,idoutput, current working directory/, andexecuted_by=Feast feature_server load_static_artifacts.bundle/logs/vuln_dotdot_writer.log,bundle/artifacts/http/request_dotdot.json,bundle/artifacts/http/response_dotdot.json, andbundle/artifacts/written_file_dotdot.json— proof that../plus string-prefix validation writes outside the intended current working directory.bundle/logs/fixed_attempt1_writer.log,bundle/logs/fixed_attempt2_writer.log,bundle/artifacts/http/fixed_attempt1_save_status.txt, andbundle/artifacts/http/fixed_attempt2_save_status.txt— fixed-version negative controls showingHAS_SAVE_DOCUMENT=Falseand HTTP404for/save-document.bundle/logs/fixed_attempt1_consumer.logandbundle/logs/fixed_attempt2_consumer.log— fixed-version restart logs showing the deployment component still loads but reports no startup config and does not execute the hook.bundle/repro/runtime_manifest.json— structured runtime manifest withentrypoint_kind=api_remote,service_started=true,healthcheck_passed=true, andtarget_path_reached=true.
The script was run twice consecutively after the robustness fix, and both runs completed successfully.
Recommendations / Next Steps
- Upgrade to a Feast version containing commit
4018e7b0c39825a5647fb17fc5607d72fb4bc0ceor otherwise remove/disable/save-documentand/read-documentfrom remotely reachable Feature Server deployments. - If similar document/file endpoints are reintroduced, enforce authentication/authorization and use robust path containment checks, for example
Path.resolve().is_relative_to(allowed_base.resolve())on supported Python versions or equivalent parent-directory comparison. - Avoid deriving output paths from attacker-controlled paths unless the output directory is fixed and controlled by the server.
- Add regression tests for:
- absolute-path writes when
cwd=/, ../traversal into a sibling directory with a shared string prefix,- no-auth remote access to document endpoints, and
- fixed behavior returning 404 or rejecting writes without creating files.
- absolute-path writes when
- Review all deployment components that consume JSON/YAML/config files from writable feature repositories, especially startup hooks, static artifact loaders, model-loading configs, and scheduled processing jobs.
Additional Notes
- Idempotency:
bundle/repro/reproduction_steps.shcleans prior server processes and generated proof files, uses fixed ports with cleanup, and was verified with two consecutive successful runs. - Scope: The primary proof uses the real Feast Feature Server over HTTP (
api_remote) and the realfeature_server.pystartup lifecycle. Thestatic_artifacts.pydeployment component is intentionally part of the active Feast feature repository because Feast explicitly supports loading that file at server startup viaload_static_artifacts(). - Limitations: Code execution is demonstrated in a realistic deployment configuration where a startup/static-artifact component consumes a JSON file in the feature repository. Environments without any component that consumes attacker-writable JSON would still have the arbitrary file write primitive but may require a different local chain for RCE.
Variant Analysis & Alternative Triggers for CVE-2026-23537
A materially distinct alternate trigger was confirmed on the vulnerable Feast revision: the Feast Web UI server also exposed unauthenticated POST /save-document and used the same arbitrary JSON file-write sink as the Feature Server endpoint from the parent reproduction. This Web UI entrypoint can write attacker-controlled JSON into a feature repository, after which the real Feast Feature Server startup path (load_static_artifacts()) can consume that file and execute the configured startup hook in the demonstrated deployment chain. This is not a bypass of the tested fix, because commit 4018e7b0c39825a5647fb17fc5607d72fb4bc0ce removes both the Feature Server document endpoints and the Web UI /save-document endpoint; the fixed UI runtime returned HTTP 405 and did not create any attacker-controlled file.
Fix Coverage / Assumptions
The original fix relies on a broad invariant: no remotely reachable document read/write endpoint should remain in Feast's Python servers. Instead of attempting to repair path validation, commit 4018e7b0c39825a5647fb17fc5607d72fb4bc0ce deletes the affected endpoint definitions and request models.
Explicitly covered code paths:
sdk/python/feast/feature_server.py- removes
ReadDocumentRequest - removes
SaveDocumentRequest - removes
@app.post("/read-document", dependencies=[Depends(inject_user_details)]) - removes
@app.post("/save-document", dependencies=[Depends(inject_user_details)])
- removes
sdk/python/feast/ui_server.py- removes
SaveDocumentRequest - removes unauthenticated
@app.post("/save-document")
- removes
sdk/python/tests/unit/test_ui_server.py- removes tests that exercised the now-deleted document endpoints.
The fix does not remove feature_server.py::load_static_artifacts(). That function remains an intentional Feast startup extension point that imports static_artifacts.py from the active feature repository. It is not itself the same remote file-write bug; it becomes part of the exploit chain only when an attacker can remotely write deployment data/configuration into a location consumed by that extension.
A top-level SECURITY.md was not present in the tested repository. Relevant in-repo threat-model/security documentation was the Feast permissions documentation, which states that authorization enforcement applies on Feast Python servers and that the default configuration is no_auth, meaning no permission enforcement is applied. The Web UI /save-document endpoint had no auth dependency at all, and the parent Feature Server endpoint was reachable in a no_auth deployment.
Variant / Alternate Trigger
Confirmed alternate trigger:
- Entry point: Feast Web UI
POST /save-document - Component:
sdk/python/feast/ui_server.py - Vulnerable code path on commit
be1b52227e1ade9a3be9836391ade45eb1a26909:ui_server.get_app(...)@app.post("/save-document")save_document_endpoint(request: SaveDocumentRequest)Path(request.file_path).resolve()- naive
str(file_path).startswith(os.getcwd()) - derived
labels_file = file_path.parent / f"{base_name}-labels.json" json.dump(request.data, file, ...)
The variant proof starts the vulnerable Web UI server with cwd=/, sends an unauthenticated JSON POST with file_path pointing at ui_variant_static_artifacts.json in the active feature repository, and confirms the endpoint writes ui_variant_static_artifacts-labels.json. The script then starts the real Feast Feature Server against that same repository. Its load_static_artifacts() startup hook imports static_artifacts.py, which consumes the UI-written JSON and executes the configured startup hook, creating a marker file.
Negative fixed-version test:
- Entry point tested: same Web UI
POST /save-document - Fixed target: commit
4018e7b0c39825a5647fb17fc5607d72fb4bc0ce - Observed behavior:
HAS_SAVE_DOCUMENT=False, HTTP405, no dropped config, no marker.
Latest/default-branch check:
origin/masterresolved tof296d4ba14c5d512429219b2b7845673e0fe524dduring this run.Static scan found no
save-documentorread-documentin the relevant server files.Package/component affected: Feast (
feast-dev/feast), Python Web UI server and Python Feature Server startup/static-artifact integration.Affected versions as tested: vulnerable at commit
be1b52227e1ade9a3be9836391ade45eb1a26909; fixed at commit4018e7b0c39825a5647fb17fc5607d72fb4bc0ce; latest observedorigin/masterf296d4ba14c5d512429219b2b7845673e0fe524ddid not contain the endpoints.Risk level and consequences: Critical on vulnerable revisions exposing the Web UI server to untrusted clients. An unauthenticated attacker can place arbitrary JSON files in locations writable by the Feast process when the path-prefix check is bypassed. In deployments where startup/static-artifact components consume such files, this can lead to code execution as the Feast service account.
Impact Parity
- Disclosed/claimed maximum impact for the parent: unauthenticated arbitrary file write leading to remote code execution.
- Reproduced impact from this variant run: code execution through an alternate Web UI entrypoint on the vulnerable commit. The proof creates
bundle/vuln_variant/artifacts/ui_variant_rce_marker.vulnerable.txtcontainingFEAST_UI_SAVE_DOCUMENT_VARIANT_RCE,written_by=Feast Web UI /save-document, andexecuted_by=Feast feature_server load_static_artifacts. - Parity:
fullfor the vulnerable-version alternate trigger;noneas a patched-version bypass because the fixed target blocks/removes the entrypoint. - Not demonstrated: privilege escalation beyond the Feast service account was not attempted. The fixed/latest versions were not vulnerable in the tested path.
Root Cause
The same underlying bug existed in two HTTP servers: both the Feature Server and the Web UI server exposed document-write endpoints that trusted a user-supplied file_path and attempted to constrain it with string-prefix validation against os.getcwd(). This validation is not a directory containment check. When the process working directory is /, every absolute path begins with /; when the working directory is a path prefix of a sibling directory, a resolved sibling path can still pass startswith().
The vulnerable Web UI sink in sdk/python/feast/ui_server.py is equivalent to the parent Feature Server sink:
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)
The fix commit is 4018e7b0c39825a5647fb17fc5607d72fb4bc0ce (Clean up document endpoints). It removes both known document-write entrypoints, including the UI variant path, so the variant does not survive the patch.
Reproduction Steps
- Run
bundle/vuln_variant/reproduction_steps.sh. - The script creates isolated worktrees under
bundle/vuln_variant/worktrees/for:- vulnerable commit
be1b52227e1ade9a3be9836391ade45eb1a26909, and - fixed commit
4018e7b0c39825a5647fb17fc5607d72fb4bc0ce.
- vulnerable commit
- It records
origin/masteras the latest/default-branch check inbundle/logs/vuln_variant/latest_version.txt. - It starts the vulnerable Feast Web UI server, sends an unauthenticated POST to
/save-document, and confirms thatui_variant_static_artifacts-labels.jsonis written. - It starts the real vulnerable Feast Feature Server with the same feature repository and confirms
load_static_artifacts()consumes the UI-written JSON and executes the configured startup hook. - It starts the fixed Feast Web UI server and sends the same request. The fixed server reports no registered
/save-document, returns HTTP405, and creates neither the config file nor the marker.
Expected script result is exit code 1, because the variant is confirmed only on the vulnerable version and is not a patched-version bypass. The script still completes fully and idempotently.
Evidence
Primary evidence files:
bundle/logs/vuln_variant/reproduction_steps.log— full transcript of the second verified run.bundle/logs/vuln_variant/vuln_ui_writer.log— vulnerable Web UI startup log; includesHAS_SAVE_DOCUMENT=Trueand the request handling.bundle/vuln_variant/artifacts/http/vuln_ui_save_request.json— attacker-controlled unauthenticated POST body.bundle/vuln_variant/artifacts/http/vuln_ui_save_response.json— vulnerable response showingsuccess: trueandsaved_toending inui_variant_static_artifacts-labels.json.bundle/vuln_variant/artifacts/feature_repo_ui/ui_variant_static_artifacts-labels.json.vuln— exact JSON file written by the vulnerable UI endpoint.bundle/logs/vuln_variant/vuln_feature_consumer.log— Feature Server startup log proving the product path consumed the UI-written config and executed the startup hook.bundle/vuln_variant/artifacts/ui_variant_rce_marker.vulnerable.txt— code-execution marker created by the startup hook.bundle/logs/vuln_variant/fixed_ui_writer.log— fixed Web UI startup log; includesHAS_SAVE_DOCUMENT=False.bundle/vuln_variant/artifacts/http/fixed_ui_save_status.txt— fixed request status (405).bundle/logs/vuln_variant/fixed_version.txtandbundle/logs/vuln_variant/latest_version.txt— source identity for fixed/latest checks.bundle/vuln_variant/runtime_manifest.json— structured runtime manifest for the variant run.
Key observed transcript excerpts from reproduction_steps.log:
[EXPLOIT] Sending unauthenticated POST to vulnerable Feast Web UI /save-document
{"success":true,"saved_to":".../ui_variant_static_artifacts-labels.json"}
[RCE] UI variant marker:
FEAST_UI_SAVE_DOCUMENT_VARIANT_RCE
written_by=Feast Web UI /save-document
executed_by=Feast feature_server load_static_artifacts
[FIXED] /save-document status on patched UI server: 405
Patch bypass: no.
Recommendations / Next Steps
The tested fix appears complete for this bug class because it removes both known document-write entrypoints. Recommended follow-up:
- Keep
/save-documentand/read-documentabsent from all remotely reachable Feast servers unless there is a strong product requirement. - Add regression tests asserting that both
feature_server.get_app()andui_server.get_app()do not register/save-documentor/read-document. - Add a static regression check for remote handlers that combine
Path.resolve(),str(...).startswith(os.getcwd()), and filesystem writes. - If file/document endpoints are ever reintroduced, enforce authentication/authorization and use real containment checks such as
resolved_path.is_relative_to(allowed_base.resolve())or equivalent parent-path comparisons. - Avoid deriving output paths from attacker-controlled absolute paths; use server-controlled storage roots and server-generated filenames.
Additional Notes
- Idempotency:
bundle/vuln_variant/reproduction_steps.shwas run twice consecutively. Both runs completed successfully and exited1as expected for a non-bypass alternate trigger. - The script uses isolated worktrees under
bundle/vuln_variant/worktrees/and does not mutate the reproduction checkout. - Source identities are recorded in
bundle/vuln_variant/source_identity.json,bundle/vuln_variant/variant_manifest.json, and logs underbundle/logs/vuln_variant/. - The RCE chain uses a realistic Feast extension point (
load_static_artifacts()), but depends on a deployment component that consumes JSON from the feature repository. Environments without such a consumer still have the arbitrary JSON write primitive on the vulnerable Web UI endpoint, but may require a different local chain for code execution.
CVE-2026-23537 Reproduction Transcript
The agent's step-by-step process — every tool call, every handoff, the moment the exploit fired.
Full session Replay every step — scrub the timeline or play it back.
Artifacts and Evidence for CVE-2026-23537
Scripts, logs, diffs, and output captured during the reproduction.
How to Fix CVE-2026-23537
Upgrade feast-dev/feast · github to 0.60.0 or later.
FAQ: CVE-2026-23537
How does the CVE-2026-23537 write-to-RCE chain work?
/save-document with a crafted file_path that bypasses the os.getcwd() prefix check, writing an attacker-controlled static_artifacts-labels.json. On startup, Feast's load_static_artifacts() imports the deployment's static_artifacts.py, which consumes that attacker-written file and runs a configured startup hook under the Feast server process account, achieving code execution.Which Feast versions are affected by CVE-2026-23537, and where is it fixed?
/save-document endpoint was introduced in v0.59.0 (PR #5865, commit 2081b55de32b830b4fb64e93b6d88cdfaeff2378); it is fixed in 0.60.0.How severe is CVE-2026-23537?
How can I reproduce CVE-2026-23537?
/save-document with a path that bypasses the location check, confirms the file lands on disk, and shows the write chained through the static-artifacts startup hook into code execution.References for CVE-2026-23537
Authoritative sources for CVE-2026-23537 — official vulnerability databases and the upstream advisory. Pruva's reproduction verifies the issue firsthand; these are the primary records to corroborate it.