Repository navigation
bug: agent startup auto-import of .credentials.enc calls a backend route that does not exist, so it always fails silently #3510
Description
Summary
When an agent container starts with a .credentials.enc in its home directory and no .env, docker/base-image/startup.sh tries to auto-import the encrypted credentials by calling POST /api/internal/decrypt-and-inject on the backend. That route does not exist. The call fails, startup prints Warning: Could not auto-import credentials (key may not be configured), and the agent runs without the credentials — with a message that points the operator at the wrong cause.
Context
Found while reviewing #3483 (removal of uncalled internal routes).
docker/base-image/startup.sh:693— thecurltohttp://backend:8000/api/internal/decrypt-and-inject, sent with no internal-secret header.src/backend/routers/internal.py— no such handler. A pickaxe oversrc/backendhistory (git log -S"decrypt_and_inject") finds no commit that ever added one; the feature commit (6821f0d59, CRED-002) added the request model and thestartup.shcall only.src/backend/models.py:1517—InternalDecryptInjectRequest, referenced by nothing.- Docs describe the route as live, including a handler snippet:
docs/memory/architecture/api-endpoints.md:140,docs/memory/feature-flows/credential-injection.md:61and:301-310. startup.sh:713-714(the.mcp.json.templaterenderer) states it runs after "both.envsources", counting this fallback as one of them.
So the "portable credentials via a git-committed .credentials.enc" path has not worked at startup since it shipped. Agents whose .env arrives through the /generated-creds mount (startup.sh:530-545) are unaffected; the gap is an agent whose only credential source is the committed encrypted file (for example a github: template that ships one). The manual path — Import from Git on the Credentials tab / import_credentials — works.
Not yet measured: how many real agents hit this branch.
Acceptance Criteria
- Decide: implement the startup auto-import, or remove it. Record the decision here.
- If implemented: an agent that starts with
.credentials.encand no.envends up with a decrypted.envbefore the.mcp.json.templaterender, the call authenticates the way the other agent→backend internal calls do (the internal router is gated byverify_internal_secret), and the route appears in the route census with its gate. - If removed: the
startup.shblock,InternalDecryptInjectRequest, and the comment atstartup.sh:713-714are gone, and startup tells the operator to import from the Credentials tab when it sees.credentials.encwithout.env. - Either way, a failed or absent import is reported with its real cause, not "key may not be configured".
-
api-endpoints.mdandcredential-injection.mdmatch the code. - A test fails if
startup.shcalls a backend path that is not in the route table.
Technical Notes
- Related: refactor: remove API routes nothing calls — five unauthenticated internal routes, two duplicate activity routes — and the orphan observability store #3434 / refactor(api): remove nine uncalled routes and the orphan observability store (#3434) #3483 (corrected the "(no auth)" wording on these doc rows but left the rows),
POST /api/credentials/updatehas no callers but is named as the sanctioned MCP-config path #2008 (listed this call as live). - The import logic already exists behind the owner-gated import route in
src/backend/routers/credentials.py; an internal variant would reuse the same service.
Activity
dolho commentedon Oct 15, 2026
/claim
github-actions commentedon Oct 15, 2026
@dolho This issue is already assigned to dolho. Use
/unclaimfirst if you want to take over.dolho commentedon Oct 15, 2026
Decision (2026-10-15, @dolho): remove the startup auto-import.
Implementing it would need an agent-callable decrypt. The master internal secret never reaches an agent container (
routers/internal.py), so the agent would authenticate with its own scoped key and ask the platform to decrypt a file in its own home. An agent that can read another agent's.credentials.enc(for example from a shared or public repo) could stage that blob and have it decrypted, unless the envelope were bound to the agent. Decrypting stays the owner's step: Import from Git on the Credentials tab, or theimport_credentialsMCP tool, which already works.The PR removes the dead call and the unused model. Startup now says what actually happens and names that action. It also adds a test that fails if the agent image calls a backend path the app doesn't register, and corrects the docs.
fix(agent-runtime): describe the credentials import truthfully; scan …
fix(credentials): subscription agents import a committed .credentials…
fix(agent-runtime): drop the startup credentials auto-import that cal…