Skip to content

fix(transform): don't panic on flow-style compose files in publish rewrites - #14387

Open
TDnorthgarden wants to merge 1 commit into
docker:mainfrom
TDnorthgarden:fix/transform-flow-style-panic
Open

TDnorthgarden wants to merge 1 commit into
docker:mainfrom
TDnorthgarden:fix/transform-flow-style-panic

Conversation

@TDnorthgarden

Copy link
Copy Markdown
Contributor

Problem

replace() in pkg/compose/transform locates a value's line by scanning
for the newline that precedes it. Line 1 has no preceding newline, so for a
flow-style document — where every node sits on line 1 — the scan runs to
the end of the stream, and the column offset then slices out of range:

$ echo '{services: {test: {extends: {file: old.yaml}}}}' > compose.yaml
$ docker compose publish
panic: runtime error: slice bounds out of range

compose publish feeds the user's original file bytes into this path
(processFile reads the file as-is before rewriting local paths to opaque
digests), so the panic is reachable from the CLI. Flow style is valid YAML
and the compose loader accepts it.

Fix

  • Correct the line-1 scan (start at offset 0 instead of running past the
    stream) and return an error instead of panicking when the computed
    position is out of range.
  • Refuse flow-style nodes explicitly: the rewrite runs from the value's
    start to the end of its line, which would swallow the closing braces and
    commas that follow the value in flow style and corrupt the stream.
  • Guard the env_file entry index against out-of-range values.

@TDnorthgarden
TDnorthgarden requested review from a team as code owners October 26, 2026 02:19

@ndeloof ndeloof 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.

Thanks for the fix — the panic is real (I reproduced it on main with the one-line flow document from the description), and the PR also turns a second, silent failure into a clean error: on main, env_file: [a.env, b.env] with index 1 produces env_file: [a.env, NEW (closing bracket swallowed), which is now rejected. I checked the branch locally. Rejecting flow style outright is a good call for such a rarely used syntax; a few small points, all reproduced.

1. Error does not say which file is at fault (publish.go)

publish.go:200 and :236 return the error from processFile unwrapped. Extended files are processed too (:236), so the message service "web": env_file is in flow style… doesn't tell the user which file to fix. Wrapping with the file name at those two call sites would help (small, could be a follow-up).

2. Test coverage

TestReplaceFlowStyle covers the two one-line documents only. Two cheap cases would exercise the remaining new branches:

  • flow sequence env_file: [a.env, b.env] (this was silently corrupting on main);
  • out-of-range i (the new bounds check is not exercised).

3. Nits

  • replace: the line > 1 guard leaves pos at 0 for line 1, and pos += column then points one byte too far (columns are 1-based): replace([]byte("abc old.env"), 1, 5, "X") returns abc oX. It is unreachable through the public functions now (any node on line 1 is flow style and rejected), so this is dead code today; column-1 would make it correct if it ever becomes reachable.
  • The isFlowStyle doc says a line holding "the value (and an optional trailing comment)" is safe, but replace overwrites to the end of the line, so the comment is dropped (env_file: old.env # keep → env_file: NEW). Either adjust the comment or leave it; this is pre-existing and not a blocker.

@TDnorthgarden
TDnorthgarden force-pushed the fix/transform-flow-style-panic branch from d7cafdc to c282227 Compare October 26, 2026 11:13
@TDnorthgarden

Copy link
Copy Markdown
Contributor Author

Thanks for the fix — the panic is real (I reproduced it on main with the one-line flow document from the description), and the PR also turns a second, silent failure into a clean error: on main, env_file: [a.env, b.env] with index 1 produces env_file: [a.env, NEW (closing bracket swallowed), which is now rejected. I checked the branch locally. Rejecting flow style outright is a good call for such a rarely used syntax; a few small points, all reproduced.

1. Error does not say which file is at fault (publish.go)

publish.go:200 and :236 return the error from processFile unwrapped. Extended files are processed too (:236), so the message service "web": env_file is in flow style… doesn't tell the user which file to fix. Wrapping with the file name at those two call sites would help (small, could be a follow-up).

2. Test coverage

TestReplaceFlowStyle covers the two one-line documents only. Two cheap cases would exercise the remaining new branches:

  • flow sequence env_file: [a.env, b.env] (this was silently corrupting on main);
  • out-of-range i (the new bounds check is not exercised).

3. Nits

  • replace: the line > 1 guard leaves pos at 0 for line 1, and pos += column then points one byte too far (columns are 1-based): replace([]byte("abc old.env"), 1, 5, "X") returns abc oX. It is unreachable through the public functions now (any node on line 1 is flow style and rejected), so this is dead code today; column-1 would make it correct if it ever becomes reachable.
  • The isFlowStyle doc says a line holding "the value (and an optional trailing comment)" is safe, but replace overwrites to the end of the line, so the comment is dropped (env_file: old.env # keep → env_file: NEW). Either adjust the comment or leave it; this is pre-existing and not a blocker.

Thanks for the thorough review, and for reproducing both the panic and the silent bracket-swallowing case on main.

  1. Wrapping the processFile errors with the file name: will do in a follow-up PR.
  2. Both cases added — the flow-sequence env_file (pinning the rejection of the silent corruption) and an out-of-range index.
  3. Fixed the off-by-one on the line-1 branch (column - 1, unreachable today but now correct), and reworded the isFlowStyle doc: anything after the value on the line, a trailing comment included, is dropped, so "the value is the sole content of its line" is the accurate contract.

…writes

replace() locates the value's line by scanning for the newline that
precedes it: line 1 has no preceding newline, so the scan ran to the
end of the stream and the column offset then sliced out of range. A
flow-style document ({services: {test: {extends: {file: x}}}}) puts
every node on line 1, and compose publish feeds the user's original
file bytes into this path (processFile), so the panic was reachable
from the CLI.

Refuse flow-style nodes explicitly instead: the rewrite runs from the
value's start to the end of its line, which would swallow the closing
braces and commas following the value in flow style and corrupt the
stream. Also guard the env_file entry index against out-of-range
values.

The processFile errors in createLayers and processExtends are wrapped
with the compose file name, so a rejection in any of the project's
files — the extended ones included — tells the user which file to fix.

Signed-off-by: Aucoto <tdnorthgarden@gmail.com>
@TDnorthgarden
TDnorthgarden force-pushed the fix/transform-flow-style-panic branch from c282227 to df3f26d Compare October 27, 2026 01:11
@TDnorthgarden

Copy link
Copy Markdown
Contributor Author

Follow-up from #14391 folded in per @ndeloof's suggestion: the processFile errors in createLayers/processExtends now carry the compose file name, with tests asserting against a real flow-style rejection (which does not name the file on its own).

This branch has not been deployed

No deployments
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