Repository navigation
fix(gateway): write the compose file as data and refuse names it can't hold - #382
Merged
Merged
Conversation
…t hold A gateway deploy wrote docker-compose.yml and init-schemas.sql through quoted heredocs and spliced each env's environment_name and image reference into them, and the snapshot reload passed the names to `docker compose up` unquoted. A name holding the heredoc terminator, a newline or a shell metacharacter ran as shell on the VM host (the operator's machine on the local provider) or rewrote the compose file. - Both files are written as one shlex-quoted printf argument (utils/shell.write_file_script), keeping the exec count and retries; the snapshot reload quotes each service name. - utils/names checks an environment name against Compose's service-name characters with a letter or digit first, and an image against Docker's reference grammar (without bracketed IPv6 registries, which an unquoted compose value reads as a YAML list). - The names are checked when written (EnvStore.put_document through a new Env.put_problem hook, DockerImageArtifact.put_tar/put_ref, bundle env.toml authoring) and again at deploy (the gateway topology, the compose renderer and the local_postgres state services). from_dict is unchanged, so stored documents still load. - Includes the LocalSandbox import fix in test_artifact_staging.py that main needs after the provider move. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The gateway refuses a name at deploy and bundle authoring refuses it in env.toml, so Env.put_problem only moved the SDK and CLI error earlier at the cost of a base-class hook. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The environment name rule is the gateway's compose service name rule, so it lives in env_gateway_provider, which bundle authoring already imports. The image reference grammar sits with names_registry and registry_host_from_ref in oci_registry_credentials. utils/names goes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The snapshot reload already drives the gateway through its private helpers, so the write is EnvironmentGatewayProvider._write_file beside _compose_up rather than a utils module. The reload's compose write now retries like the deploy's; the write only replaces the file. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
A gateway deploy wrote
docker-compose.ymlandinit-schemas.sqlto the VM host with quoted heredocs and spliced each env'senvironment_nameand image reference into them. The snapshot reload passed the same names todocker compose up --force-recreateunquoted. If a name held the heredoc terminator, a newline or a shell metacharacter, it ran as shell on the VM host (the operator's own machine on thelocalprovider) or rewrote the compose file. Anenv.tomlin a bundle is enough to set such a name.What changes
EnvironmentGatewayProvider._write_filewrites a file as oneshlex.quotedprintfargument. The gateway's compose and init files and the snapshot reload's compose file use it, keeping the same exec count, withmax_retries=2(the reload's write now retries too; it only replaces the file). The snapshot reload quotes each service name it passes to compose.environment_name_probleminenv_gateway_provider: an environment name against Compose's service-name characters, with a letter or digit first;image_reference_probleminoci_registry_credentials, next tonames_registry: an image against Docker's reference grammar ([registry/]repository[:tag][@digest]), without bracketed IPv6 registries. An unquoted compose value reads those as a YAML list, so nothing deploys with one today.DockerImageArtifact.put_tarandput_refcheck before any upload or registry request.env.tomlauthoring reports a bad name as a config problem before any write.EnvironmentGatewayProvider.deploychecks before acquiring state or a sandbox; so does the compose renderer (MCP servers, websites, sidecars, the gateway image, the MCP server name) andlocal_postgres's state services.from_dictis unchanged, so documents already stored still load, and only their deploy is refused.LocalSandboximport fix intest_artifact_staging.py, so its unit job runs green before test(env): import LocalSandbox from its new module in the artifact staging test #381 merges.Testing
Unit tests: 11,549 passed, ruff clean. New tests cover:
bashintmp_path, for content holding the terminator,$(), backticks and quotes);__separators);put_tar,put_refand bundle authoring refuse on write.Before and after on Linux (EC2 Ubuntu 24.04, Docker 29.9,
localprovider; main at 1a35e48 vs this branch). Eachagent-env runstarted from a fresh working directory with its ownXDG_STATE_HOME. The hostile bundle'senv.tomlsetsenvironment_name = "items\nCOMPOSE_EOF\ntouch pwned", a harmless marker. After each run, the whole box was searched forpwned*files.pwned,pwned:,pwned:18765andpwned%22,publicin the run directory, then the deploy failedenvs/items: env.toml: environment name 'items\nCOMPOSE_EOF\ntouch pwned' must start with a letter or digit ...), nothing written, no markeragent-env env deploy)Can't deploy '@local/~/e2e/compose-named-env/items' behind a gateway: ...) before any container starts; no markermodal_vm (the remote VM write path, run from a Mac):
test_gateway_with_multi_mcp_server,test_gateway_triggers_extensionandtest_gateway_clock_extension, all 3 passed.Regression and stress on the Linux box: the fast integration tier (175 passed), then the gateway, server-env, MultiEnv, task-step, service-db and bundle integration sets three times in a row: 55 passed each time (706 skipped, every skip a declared
agentenv-capability-missinggap), about 30 min a run.CI: all green. One slow shard failed first on
recording_chaos_test.py::test_a_restarted_driver_forgets_its_recording_and_keeps_the_segments_it_closed: the computer never became ready after adocker restart. That test runsdocker run/docker restarton a computer image and touches nothing this PR changes. It passed on rerun.Not in this PR: the tarball load check in
VmSandbox._load_docker_imagesstill matchesimage_name's repository as a substring ofdocker imagesoutput instead of the exact tag. With the names now validated, that is an integrity check rather than an injection sink; it can follow separately.🤖 Generated with Claude Code


Confidence Score: 5/5
The PR appears safe to merge; no new actionable issues were found.Summary
The PR writes gateway files as quoted data and rejects names and image references that cannot safely appear in Compose files.
LocalSandboxfrom its current module.Reviews (4) · Last reviewed commit: "refactor(gateway): write host files thro..." · Reviewed by Greptile