CVE-2026-23537: Verified Reproduction
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://www.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 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.
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.