Skip to content

feat: add X-Docker-Agent-Version and X-Docker-Desktop-Version headers to built-in tools#2795

Open
dgageot wants to merge 7 commits into
docker:mainfrom
dgageot:identity-headers
Open

feat: add X-Docker-Agent-Version and X-Docker-Desktop-Version headers to built-in tools#2795
dgageot wants to merge 7 commits into
docker:mainfrom
dgageot:identity-headers

Conversation

@dgageot
Copy link
Copy Markdown
Member

@dgageot dgageot commented May 13, 2026

What

Adds two HTTP identity headers to every outbound request made by the api, fetch, and openapi built-in tools, on top of the existing User-Agent:

Header Value When
User-Agent Cagent/<version> (<goos>; <goarch>) Always (unchanged)
X-Docker-Agent-Version <version> (e.g. dev, 1.2.3) Always
X-Docker-Desktop-Version e.g. 4.74.0 Only when Docker Desktop is reachable on the host

Operator-supplied headers in the agent config still win over these defaults — SetIdentity is called first, then caller headers.

Why

Backends that receive built-in-tool traffic (Docker Hub APIs, Hub gateway, ...) want to attribute requests to a specific docker-agent install and the Docker Desktop version it's running alongside, the same way other Docker traffic is already attributed. User-Agent was the only signal so far and is brittle to parse.

How

  • New pkg/useragent.SetIdentity(req) is the single source of truth for these headers. All three built-in tools route through it, including the robots.txt fetch in the fetch tool.
  • New pkg/desktop.GetVersion(ctx) reads currentVersion from Docker Desktop's backend.sock /update endpoint, with:
    • 5-minute TTL memoization (memoize.Call[string]) — recovers automatically if Desktop starts after docker-agent or is upgraded mid-session, while still costing one socket call per tool request at most;
    • 2 s internal timeout so a stale/missing socket can never stall a hot path;
    • returns "" when Desktop isn't running, in which case the header is omitted.

No new dependencies (go-memoize and go-cache were already vendored).

Privacy / security note

X-Docker-Agent-Version adds no information that User-Agent didn't already broadcast. X-Docker-Desktop-Version is genuinely new data sent to whichever host the operator configured. It's no more leaky than User-Agent is for <goos>; <goarch>, but if a privacy/opt-out mode is desired we can layer one on top later. Not adding one now keeps this PR focused.

Tests

  • pkg/useragent: pins User-Agent + X-Docker-Agent-Version on a fresh request.
  • api, fetch, openapi: each existing header test now also asserts the two identity headers reach the test server.
  • All tests in modified packages pass; task lint is clean.

Commits

  1. feat: add identity headers for docker-agent and Docker Desktop tracking
  2. refactor: use SetIdentity() in api tool
  3. refactor: use SetIdentity() in fetch tool
  4. refactor: use SetIdentity() in openapi tool
  5. fix(desktop): make Desktop version lookup TTL-based and context-independent
  6. refactor: simplify Desktop version lookup and useragent docs
  7. refactor(desktop): plumb context through GetVersion

dgageot added 7 commits May 13, 2026 18:08
Introduce SetIdentity() to centralize HTTP identity headers (User-Agent,
X-Docker-Agent-Version, X-Docker-Desktop-Version) used across built-in tools.

Add pkg/desktop/version.go to fetch Docker Desktop version from the backend
socket's /update endpoint once per process (memoized, 2s timeout).
Update api tool to use the new SetIdentity() helper, ensuring consistent
identity header formatting across all built-in tools. Add test coverage
for identity headers.
Update fetch tool to use SetIdentity(), including robots.txt requests.
Remove redundant userAgent parameter from fetchRobots(). Add test coverage
for identity headers.
Update openapi tool to use SetIdentity(), establishing unified identity
header handling. Add test coverage for identity headers.
@dgageot dgageot requested a review from a team as a code owner May 13, 2026 18:31
Copy link
Copy Markdown

@docker-agent docker-agent left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Assessment: 🟡 NEEDS ATTENTION

One medium confirmed finding in the new code.

Comment thread pkg/desktop/version.go
CurrentVersion string `json:"currentVersion"`
}
_ = ClientBackend.Get(ctx, "/update", &info)
return info.CurrentVersion, nil
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[MEDIUM] Empty Desktop version cached for full 5-minute TTL when Desktop is unavailable at first call

GetVersion always returns (info.CurrentVersion, nil) — even when the HTTP call to Desktop's /update endpoint fails (Desktop not running, socket not present, timeout, etc.). In that case info.CurrentVersion is the zero value "", and since go-memoize only caches results where error == nil, the empty string gets cached for the full 5-minute TTL.

This contradicts the PR description's claim that the lookup "recovers automatically if Desktop starts after docker-agent": if GetVersion is first called before Desktop is ready, the X-Docker-Desktop-Version header will be silently omitted for up to 5 minutes even after Desktop becomes available — rather than recovering on the next request.

Fix: return a non-nil error when the lookup fails so go-memoize does not cache the failure:

if err := ClientBackend.Get(ctx, "/update", &info); err != nil {
    return "", err   // not cached; next caller retries
}
return info.CurrentVersion, nil

This way a transient failure (Desktop not yet up) never poisons the cache.

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