Skip to content

fix(server): make session WorkingDir containment opt-in - #3787

Closed
trungutt wants to merge 2 commits into
docker:mainfrom
trungutt:fix/session-workingdir-optional-containment
Closed

fix(server): make session WorkingDir containment opt-in#3787
trungutt wants to merge 2 commits into
docker:mainfrom
trungutt:fix/session-workingdir-optional-containment

Conversation

@trungutt

Copy link
Copy Markdown
Contributor

Why

#3758 added a containment check for user-supplied working_dir on
POST /api/sessions. When no --working-dir was configured it fell
back to os.Getwd() as the permitted root — i.e. the process's current
directory at launch became a hard boundary for every session.

That breaks any deployment where docker-agent runs as a long-lived
daemon and clients legitimately create sessions targeting arbitrary
paths on the host: every session outside the launch directory is
rejected with

working directory "…" is outside the permitted root "…"

--working-dir's documented purpose (cmd/root/flags.go) is a default
cwd for tool execution — not a sandbox root. Overloading it as one
makes the check unusable for the majority of callers, who don't set it
and don't want containment.

What changes

Make containment opt-in:

--working-dir set     → root = that path         → containment enforced
--working-dir unset   → root = ""                → path canonicalised only

resolveWithinRoot still runs filepath.EvalSymlinks on the candidate
in both branches, so the CodeQL data-flow that alert #57 flagged
remains broken. Multi-user / hosted deployments that need containment
opt in by passing --working-dir.

The containment guard added in docker#3758 fell back to os.Getwd() when
runConfig.WorkingDir was unset, which confined session working dirs to
whatever directory the process happened to be launched from. Callers
that use the API as a long-lived daemon and legitimately point sessions
at arbitrary paths on the user's filesystem then hit
'outside the permitted root' for every session.

Make containment opt-in: only enforce it when --working-dir is
explicitly set. When it isn't, resolveWithinRoot still canonicalises
via EvalSymlinks (so the CodeQL data-flow is broken) but does not
constrain the path. Deployments that need containment can opt in by
passing --working-dir.
@trungutt
trungutt marked this pull request as ready for review July 22, 2026 14:10
@trungutt
trungutt requested a review from a team as a code owner July 22, 2026 14:10

@docker-agent docker-agent left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Assessment: 🟢 APPROVE

The PR correctly implements opt-in containment: workingDirRoot() now returns "" when --working-dir is unset, and resolveWithinRoot skips the boundary check in that case while still running filepath.EvalSymlinks to canonicalise the path. The intent is clearly documented in both the PR description and the new function comment. Test coverage for the new opt-in behaviour is thorough (nil config, empty config, whitespace-only config all tested).

Dismissed security findings (review manually)

  • pkg/server/session_manager.go:601 — Path containment disabled by default when --working-dir is unset: arbitrary working directories accepted without restriction (verifier mitigation: intentional design decision — the PR explicitly documents that --working-dir was never meant to be a sandbox root; the previous os.Getwd() fallback was incorrect overloading of the flag's purpose; deployments that need containment must opt in by setting --working-dir)

docker-agent

This comment was marked as resolved.

@trungutt
trungutt requested a review from a team July 22, 2026 14:19
docker-agent

This comment was marked as resolved.

A --working-dir value that trims to empty (e.g. an unresolved shell
variable) previously fell through workingDirRoot's trim and silently
disabled containment. The operator expects containment to be active;
they get none, invisible in logs. Fail loudly at startup instead.
@trungutt

Copy link
Copy Markdown
Contributor Author

Superseded — going with a straight revert of #3758 instead of opt-in containment. See follow-up PR.

@trungutt trungutt closed this Jul 22, 2026
@trungutt
trungutt deleted the fix/session-workingdir-optional-containment branch July 22, 2026 14:31
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