Bug Description
The March 13 auth hardening in PR #4090 changed local_user_only() to trust only request.environ["REMOTE_USER"], but the local PFS startup path still launches plain Waitress (waitress.serve(...)) without any middleware that injects REMOTE_USER for local requests.
Under normal WSGI rules, client-supplied headers are exposed as HTTP_* keys, not REMOTE_USER, so local callers never satisfy the guard. The two endpoints still protected by @local_user_only — /v1.0/Connections/<name>/listsecrets and /v1.0/Telemetries/ — now return 403 even for the same desktop user who started PFS.
This is not an intentional breaking change — there is no corresponding documentation update, CHANGELOG entry, or migration path. The in-tree tests still send X-Remote-User (which no longer maps to REMOTE_USER), confirming the regression is unintentional.
Root Cause
src/promptflow-devkit/promptflow/_sdk/_service/utils/utils.py L60-66:
user = request.environ.get("REMOTE_USER")
if user != getpass.getuser():
abort(403)
src/promptflow-devkit/promptflow/_cli/_pf/_service.py:
waitress.serve(app, host=service_host, port=port, threads=PF_SERVICE_WORKER_NUM)
# No middleware to inject REMOTE_USER for local requests
Evidence
- Gate reads
REMOTE_USER — utils.py L60
- No injection path —
waitress.serve() is called without any auth middleware
- Tests still use old contract —
tests/sdk_pfs_test/utils.py L66, L129, L231 and test_telemetry_apis.py L15 still send X-Remote-User
- Historical context confirms this was the API contract:
- No follow-up — No subsequent PR/issue addresses the broken local auth path
#4092 is unrelated — that's a listing/spam issue, not this regression
Steps to Reproduce
- Start local PFS:
pf service start
- Try to list secrets:
GET /v1.0/Connections/my-conn/listsecrets
- Response: 403 Forbidden (even though you are the local user who started PFS)
- Same for
POST /v1.0/Telemetries/
Expected Behavior
The security fix in #4090 should have simultaneously provided a new trusted identity mechanism for local PFS callers. Local requests from the user who started PFS should still be authorized.
Suggested Fix
Keep the anti-spoofing fix from #4090, but also add a trusted local auth mechanism that actually populates REMOTE_USER in the PFS hosting stack — for example, a WSGI middleware that sets REMOTE_USER to getpass.getuser() for connections originating from 127.0.0.1/::1. Update the test helpers and swagger text to reflect the real contract.
Bug Description
The March 13 auth hardening in PR #4090 changed
local_user_only()to trust onlyrequest.environ["REMOTE_USER"], but the local PFS startup path still launches plain Waitress (waitress.serve(...)) without any middleware that injectsREMOTE_USERfor local requests.Under normal WSGI rules, client-supplied headers are exposed as
HTTP_*keys, notREMOTE_USER, so local callers never satisfy the guard. The two endpoints still protected by@local_user_only—/v1.0/Connections/<name>/listsecretsand/v1.0/Telemetries/— now return 403 even for the same desktop user who started PFS.This is not an intentional breaking change — there is no corresponding documentation update, CHANGELOG entry, or migration path. The in-tree tests still send
X-Remote-User(which no longer maps toREMOTE_USER), confirming the regression is unintentional.Root Cause
src/promptflow-devkit/promptflow/_sdk/_service/utils/utils.pyL60-66:src/promptflow-devkit/promptflow/_cli/_pf/_service.py:Evidence
REMOTE_USER—utils.pyL60waitress.serve()is called without any auth middlewaretests/sdk_pfs_test/utils.pyL66, L129, L231 andtest_telemetry_apis.pyL15 still sendX-Remote-UserX-Remote-Userfrom Swagger docs, did not remove runtime supportX-Remote-Userpattern#4092is unrelated — that's a listing/spam issue, not this regressionSteps to Reproduce
pf service startGET /v1.0/Connections/my-conn/listsecretsPOST /v1.0/Telemetries/Expected Behavior
The security fix in #4090 should have simultaneously provided a new trusted identity mechanism for local PFS callers. Local requests from the user who started PFS should still be authorized.
Suggested Fix
Keep the anti-spoofing fix from #4090, but also add a trusted local auth mechanism that actually populates
REMOTE_USERin the PFS hosting stack — for example, a WSGI middleware that setsREMOTE_USERtogetpass.getuser()for connections originating from127.0.0.1/::1. Update the test helpers and swagger text to reflect the real contract.