Skip to content

fix(agent-server): resolve linked providers for plaintext profile reads - #4952

Open
enyst wants to merge 2 commits into
mainfrom
fix/plaintext-profile-provider-resolution
Open

fix(agent-server): resolve linked providers for plaintext profile reads#4952
enyst wants to merge 2 commits into
mainfrom
fix/plaintext-profile-provider-resolution

Conversation

@enyst

@enyst enyst commented Sep 11, 2026

Copy link
Copy Markdown
Member

HUMAN:
This PR proposes an addition to assist PR 547 on extensions/ repo to show the LLM profile of an Automation.


AGENT:
I am an AI agent (smolpaws) acting on behalf of Engel.

Why

OpenHands/extensions#547 selects a named LLM profile and passes its configuration to a new conversation, but plaintext profile reads currently omit a linked provider's API key and base URL.

Summary

  • Resolve linked providers for GET /api/profiles/{name} with X-Expose-Secrets: plaintext.
  • Preserve stored profiles, active defaults, and the existing unresolved behavior of ordinary and encrypted editor reads.
  • Cover encrypted provider storage and credential rotation, plus real HTTP selection through RemoteWorkspace.get_llm(profile_name=...).

Issue Number

Supports OpenHands/extensions#548 and OpenHands/automation#430.

How to Test

uv run pytest tests/agent_server/test_profiles_router.py -q
uv run pytest tests/cross/test_remote_conversation_live_server.py -k 'workspace_named_llm_resolves_current_provider_credentials or workspace_default_llm_resolves_active_profile_despite_settings_drift' -q
uv run pre-commit run --files openhands-agent-server/openhands/agent_server/profiles_router.py tests/agent_server/test_profiles_router.py tests/cross/test_remote_conversation_live_server.py

Results: 106 profile tests passed; both live-server tests passed; all applicable pre-commit hooks passed.
The live test starts Uvicorn, creates a provider and profile through HTTP, selects the profile through the SDK, rotates the provider key, and confirms the next selection uses that key while the active default is unchanged.
The existing shared-provider test failed on the original route because the plaintext API key was None, then passed with this fix.

Type

  • Bug fix

Notes

This dependency must reach the deployed Agent Server before the extensions#547 follow-up can run provider-linked profiles. No external LLM calls are used by these tests.


🐳 Agent Server images for this PR — GHCR package, pull/run commands, and all pushed tags (click to expand)

GHCR package: https://github.com/OpenHands/agent-sdk/pkgs/container/agent-server

Variants & Base Images

Variant Architectures Base Image Docs / Tags
java amd64, arm64 eclipse-temurin:17-jdk Link
python-slim amd64, arm64 nikolaik/python-nodejs:python3.13-nodejs22-slim Link
python amd64, arm64 nikolaik/python-nodejs:python3.13-nodejs22-slim Link
golang amd64, arm64 golang:1.21-bookworm Link

Pull (multi-arch manifest)

# Each variant is a multi-arch manifest supporting both amd64 and arm64
docker pull ghcr.io/openhands/agent-server:edc90a4-python

Run

docker run -it --rm \
  -p 8000:8000 \
  --name agent-server-edc90a4-python \
  ghcr.io/openhands/agent-server:edc90a4-python

All tags pushed for this build

ghcr.io/openhands/agent-server:edc90a4-golang-amd64
ghcr.io/openhands/agent-server:edc90a46285ab611b85e6cef8b463dead3fcefe5-golang-amd64
ghcr.io/openhands/agent-server:fix-plaintext-profile-provider-resolution-golang-amd64
ghcr.io/openhands/agent-server:edc90a4-golang_tag_1.21-bookworm-amd64
ghcr.io/openhands/agent-server:edc90a4-golang-arm64
ghcr.io/openhands/agent-server:edc90a46285ab611b85e6cef8b463dead3fcefe5-golang-arm64
ghcr.io/openhands/agent-server:fix-plaintext-profile-provider-resolution-golang-arm64
ghcr.io/openhands/agent-server:edc90a4-golang_tag_1.21-bookworm-arm64
ghcr.io/openhands/agent-server:edc90a4-java-amd64
ghcr.io/openhands/agent-server:edc90a46285ab611b85e6cef8b463dead3fcefe5-java-amd64
ghcr.io/openhands/agent-server:fix-plaintext-profile-provider-resolution-java-amd64
ghcr.io/openhands/agent-server:edc90a4-eclipse-temurin_tag_17-jdk-amd64
ghcr.io/openhands/agent-server:edc90a4-java-arm64
ghcr.io/openhands/agent-server:edc90a46285ab611b85e6cef8b463dead3fcefe5-java-arm64
ghcr.io/openhands/agent-server:fix-plaintext-profile-provider-resolution-java-arm64
ghcr.io/openhands/agent-server:edc90a4-eclipse-temurin_tag_17-jdk-arm64
ghcr.io/openhands/agent-server:edc90a4-python-amd64
ghcr.io/openhands/agent-server:edc90a46285ab611b85e6cef8b463dead3fcefe5-python-amd64
ghcr.io/openhands/agent-server:fix-plaintext-profile-provider-resolution-python-amd64
ghcr.io/openhands/agent-server:edc90a4-nikolaik_s_python-nodejs_tag_python3.13-nodejs22-slim-amd64
ghcr.io/openhands/agent-server:edc90a4-python-arm64
ghcr.io/openhands/agent-server:edc90a46285ab611b85e6cef8b463dead3fcefe5-python-arm64
ghcr.io/openhands/agent-server:fix-plaintext-profile-provider-resolution-python-arm64
ghcr.io/openhands/agent-server:edc90a4-nikolaik_s_python-nodejs_tag_python3.13-nodejs22-slim-arm64
ghcr.io/openhands/agent-server:edc90a4-python-slim-amd64
ghcr.io/openhands/agent-server:edc90a46285ab611b85e6cef8b463dead3fcefe5-python-slim-amd64
ghcr.io/openhands/agent-server:fix-plaintext-profile-provider-resolution-python-slim-amd64
ghcr.io/openhands/agent-server:edc90a4-nikolaik_s_python-nodejs_tag_python3.13-nodejs22-slim-slim-amd64
ghcr.io/openhands/agent-server:edc90a4-python-slim-arm64
ghcr.io/openhands/agent-server:edc90a46285ab611b85e6cef8b463dead3fcefe5-python-slim-arm64
ghcr.io/openhands/agent-server:fix-plaintext-profile-provider-resolution-python-slim-arm64
ghcr.io/openhands/agent-server:edc90a4-nikolaik_s_python-nodejs_tag_python3.13-nodejs22-slim-slim-arm64
ghcr.io/openhands/agent-server:edc90a4-golang
ghcr.io/openhands/agent-server:edc90a46285ab611b85e6cef8b463dead3fcefe5-golang
ghcr.io/openhands/agent-server:fix-plaintext-profile-provider-resolution-golang
ghcr.io/openhands/agent-server:edc90a4-golang_tag_1.21-bookworm
ghcr.io/openhands/agent-server:edc90a4-java
ghcr.io/openhands/agent-server:edc90a46285ab611b85e6cef8b463dead3fcefe5-java
ghcr.io/openhands/agent-server:fix-plaintext-profile-provider-resolution-java
ghcr.io/openhands/agent-server:edc90a4-eclipse-temurin_tag_17-jdk
ghcr.io/openhands/agent-server:edc90a4-python-slim
ghcr.io/openhands/agent-server:edc90a46285ab611b85e6cef8b463dead3fcefe5-python-slim
ghcr.io/openhands/agent-server:fix-plaintext-profile-provider-resolution-python-slim
ghcr.io/openhands/agent-server:edc90a4-nikolaik_s_python-nodejs_tag_python3.13-nodejs22-slim-slim
ghcr.io/openhands/agent-server:edc90a4-python
ghcr.io/openhands/agent-server:edc90a46285ab611b85e6cef8b463dead3fcefe5-python
ghcr.io/openhands/agent-server:fix-plaintext-profile-provider-resolution-python
ghcr.io/openhands/agent-server:edc90a4-nikolaik_s_python-nodejs_tag_python3.13-nodejs22-slim

About Multi-Architecture Support

  • Each variant tag (e.g., edc90a4-python) is a multi-arch manifest supporting both amd64 and arm64
  • Docker automatically pulls the correct architecture for your platform
  • Individual architecture tags (e.g., edc90a4-python-amd64) are also available if needed

Return current linked credentials to runtime clients without changing stored
profiles or default settings. Keep editor reads unresolved. Cover encrypted
storage, rotation, and real HTTP profile selection with RemoteWorkspace.

Co-authored-by: openhands <openhands@all-hands.dev>
@github-actions

Copy link
Copy Markdown
Contributor

Python API breakage checks — ✅ PASSED

Result:PASSED

Action log

@github-actions

github-actions Bot commented Sep 11, 2026

Copy link
Copy Markdown
Contributor

REST API breakage checks (OpenAPI) — ✅ PASSED

Result:PASSED

Action log

@enyst

enyst commented Sep 11, 2026

Copy link
Copy Markdown
Member Author

@OpenHands /codereview this pr please and post your feedback as a comment. look at the linked pr too. do we really need to add a plaintext secrets? other options? why does it seemingly needed?

@openhands-ai

openhands-ai Bot commented Sep 11, 2026

Copy link
Copy Markdown

I'm on it! enyst can track my progress at all-hands.dev

enyst commented Sep 11, 2026

Copy link
Copy Markdown
Member Author

🟢 Good taste

I looked through this PR and the linked OpenHands/extensions#547 flow. I do not think this PR adds a new plaintext-secret capability: X-Expose-Secrets: plaintext already exists on settings and profile reads, RemoteWorkspace.get_llm(profile_name=...) already requests it, and an inline profile API key was already returned in plaintext under that mode. This patch makes a provider-linked profile obey the same runtime-read contract as an inline profile.

Why it is needed in the current design: a linked profile deliberately stores only provider_connection_id; the current provider key and base URL live in the provider store. The conversation-create API accepts a concrete agent/LLM, but it does not accept an LLM profile name to resolve server-side. Therefore extensions#547 must resolve the named profile before constructing the spawned conversation. Without this change, the existing plaintext profile read returns an unrunnable LLM (api_key=None, base_url=None) specifically for provider-linked profiles.

There are alternatives, but none is a smaller fix:

  1. Best long-term design: add an additive server-side LLM-profile selector to conversation creation, and return non-secret resolved model metadata separately for provenance. That would keep the API key inside the agent server. It also requires a new public REST/SDK/client contract and clear precedence against agent, agent_settings, and agent_profile_id.
  2. Use /switch_profile after creation: avoids returning plaintext, but requires creating an unstarted conversation, switching it, then sending/running. That is multi-request, non-atomic, and complicates failure cleanup.
  3. Activate the profile globally first: not acceptable; it mutates shared defaults and introduces races.
  4. Add a dedicated /resolve endpoint: clearer naming, but it still returns plaintext and duplicates the existing runtime contract without reducing exposure.

The relevant trust boundary remains unchanged: /api/* requires the session API key when configured, and the server explicitly treats authenticated clients as one trust domain. Deployments with no session keys already expose the existing plaintext mode and should not be considered secure production configurations.

The tests cover encrypted-at-rest provider credentials, key rotation, unchanged stored profiles/default settings, and a real HTTP path through RemoteWorkspace.get_llm(profile_name=...). I also reran the focused suites locally: 106 profile tests passed; 2 selected live-server tests passed.

[RISK ASSESSMENT]

  • [Overall PR] ⚠️ Risk Assessment: 🟡 MEDIUM

This touches credential material, so the impact of a mistake is meaningful. The implementation itself is narrow, authenticated under the existing trust model, preserves redacted/encrypted editor reads, and adds no new secret-exposure mode. The two current failing checks appear unrelated: a timing-sensitive session-socket test and an expired deprecation elsewhere.

VERDICT:
Worth merging: This is the correct minimal compatibility fix for the API that exists today. A server-side profile selector would be a worthwhile follow-up, not a prerequisite.

KEY INSIGHT:
Plaintext is not fundamentally required by the product goal; it is required by today’s client-resolved conversation API, and this PR makes linked profiles consistent with that already-established runtime contract.

This review comment was generated by an AI agent (OpenHands) on behalf of @enyst.

@openhands-ai

openhands-ai Bot commented Sep 11, 2026

Copy link
Copy Markdown

Reviewed PR #4952 and linked OpenHands/extensions#547, then posted feedback here:

#4952 (comment)

Conclusion: the PR does not introduce plaintext exposure; it makes provider-linked profiles consistent with the existing authenticated X-Expose-Secrets: plaintext runtime contract. A server-side LLM-profile selector would avoid plaintext transit and is the cleaner long-term design, but it is a larger API change and should not block this focused fix.

Validation:

  • 106 profile-router tests passed
  • 2 selected live-server tests passed
  • Current CI failures appear unrelated
  • No workspace files were changed

GitHub formal approval was not possible because the available token belongs to the PR author, so I posted the requested review as a PR comment.

@github-actions

Copy link
Copy Markdown
Contributor

Coverage

Coverage Report •
FileStmtsMissCoverMissing
openhands-agent-server/openhands/agent_server
   profiles_router.py196697%494–499
TOTAL428231774559% 

@enyst
enyst marked this pull request as ready for review September 14, 2026 02:27
@all-hands-bot

Copy link
Copy Markdown
Collaborator

🚦 CI is currently failing on this PR's latest commit.

Please fix the failing checks before OpenHands reviews it - this is re-checked automatically once you push a new commit. (A maintainer can also request @all-hands-bot as a reviewer to have it reviewed regardless of CI status.)

This is an automated check - no AI was used to generate this comment.

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.

2 participants