Skip to content

fix(compose): harden agent registration - #1602

Merged
Cedric Vidal (cedricvidal) merged 1 commit into
mainfrom
cedricvidal-harden-agent-registration
Nov 5, 2026
Merged

Cedric Vidal (cedricvidal) merged 1 commit into
mainfrom
cedricvidal-harden-agent-registration

Conversation

@cedricvidal

@cedricvidal Cedric Vidal (cedricvidal) commented Nov 3, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

  • Replace the register-agents runtime package installation with the audited multi-architecture mikefarah/yq 4.54.1 OCI index pinned exactly as docker.io/mikefarah/yq:4.54.1@sha256:4b3d9475d65571d28cbb19544d3820ec2945e4c8b2f18279394282b8dc3a592e.
  • Rewrite scripts/register-agent.sh deliberately for BusyBox wget, timeout, and --post-file while preserving API readiness, exact JSON POST bytes, response/diagnostic capture, bounded retry/backoff, permanent-error fail-fast behavior, and nonzero failure.
  • Harden the one-shot service with its image-provided non-root UID/GID 1000, a read-only root filesystem, all capabilities dropped, no-new-privileges, a 1 MiB /tmp tmpfs, and read-only script/manifest mounts.
  • Update docs/architecture/worker-requirements.md with the runtime contract, reviewed-image rationale, and residual security limitations.

Problem

register-agents previously started from Alpine and ran apk add --no-cache curl yq > /dev/null 2>&1 at runtime. Package-repository or network failures were therefore hidden, left the registration helper without its required tools, and caused the one-shot registration service to fail. Every scheduler/worker service that depends on successful agent registration then remained blocked even though the API and backing infrastructure were otherwise healthy.

This change removes package repositories from the startup critical path. The digest-pinned mikefarah/yq image already contains /usr/bin/yq, BusyBox /bin/sh, /usr/bin/wget, /usr/bin/timeout, and CA certificates. The helper posts payloads from temporary JSON files with Content-Type: application/json, uses a hard total deadline around wget, and parses the final wget -S HTTP status so redirect chains cannot be misclassified. HTTP 408/425/429/5xx outcomes and transport failures without a completed response retry with bounded exponential backoff; permanent 4xx responses fail immediately with the response body when available or a bounded diagnostic tail otherwise. A transfer that fails after receiving a 2xx/3xx header is treated as incomplete and retried rather than reported as success.

The reviewed upstream image is unsigned and has no authoritative signed image SBOM. Alpine musl 1.2.6-r2 and zlib 1.3.2-r0 have known High CVEs; the documented residual assessment accepts them for this static one-shot workload because the yq binary is CGO-disabled and the vulnerable paths are not exercised by manifest conversion or the trusted registration flow. No existing internal SBOM/vulnerability scanner was found in the repository/toolchain, so this PR does not add a new scanner dependency or external service solely for scanning.

This is layer 2 of the native PR stack. It remains a draft dependent on #1601 with base cedricvidal-reproduce-copilot-stack-issue and head cedricvidal-harden-agent-registration. The current head be95df38c8325b05124f386695952d46a7aeedf8 includes the updated lower-layer tip e5b43c0aae8eea21e40bd782cf7cbf74886ec6ee through a normal merge.

Demos

N/A — this changes local Compose startup infrastructure and has no user-visible Portal/CLI UI.

Before

N/A

After

N/A

Testing

  • pnpm exec vitest run scripts/register-agent.test.ts --reporter=default — PASS, 18/18 tests. Coverage includes 2xx, permanent 4xx, 408/425/429, 5xx, redirects/final status, DNS and connection failure, hard timeout, interrupted transfer after a response header, response diagnostics, availability override, URL validation, and exact --post-file JSON bytes.
  • sh -n scripts/register-agent.sh && git diff --check — PASS, shell syntax and whitespace validation completed with exit code 0.
  • scripts/worktree-env.sh >/dev/null && scripts/dev-compose.sh -f docker-compose.yml -f docker-compose.dev.yml --profile copilot --profile claude-code config --quiet — PASS, merged production/development Compose configuration is valid.
  • scripts/worktree-env.sh >/dev/null && scripts/dev-compose.sh -f docker-compose.yml -f docker-compose.dev.yml --profile copilot --profile claude-code config --format json | jq '{image: .services["register-agents"].image, user: .services["register-agents"].user, read_only: .services["register-agents"].read_only, cap_drop: .services["register-agents"].cap_drop, security_opt: .services["register-agents"].security_opt, tmpfs: .services["register-agents"].tmpfs, volumes: .services["register-agents"].volumes}' — PASS, confirmed the exact digest, no Compose user override, read_only: true, cap_drop: [ALL], no-new-privileges:true, 1 MiB /tmp tmpfs, and read-only mounts.
  • docker run --rm --entrypoint /bin/sh docker.io/mikefarah/yq:4.54.1@sha256:4b3d9475d65571d28cbb19544d3820ec2945e4c8b2f18279394282b8dc3a592e -c 'id; command -v yq; command -v wget; command -v timeout; wget --help 2>&1 | head -40; timeout --help 2>&1 | head -20' — PASS, confirmed UID/GID 1000 and the required yq, wget, and timeout binaries.
  • docker run --rm --read-only --cap-drop ALL --security-opt no-new-privileges --tmpfs /tmp:size=1m,mode=1777 -v "$PWD/apps/workers:/workers:ro" -v "$PWD/scripts/register-agent.sh:/register-agent.sh:ro" --entrypoint /bin/sh docker.io/mikefarah/yq:4.54.1@sha256:4b3d9475d65571d28cbb19544d3820ec2945e4c8b2f18279394282b8dc3a592e /register-agent.sh http://host.docker.internal:18080 /workers/coder-acp-copilot/agent.yaml --available false /workers/coder-acp-copilot/agent-version.dev.yaml — PASS, exact pinned-image health, agent POST, and version POST flow completed under the production hardening flags.
  • Exact pinned-image HTTP 422 flow with SCOPE_REGISTRATION_MAX_ATTEMPTS=1 — PASS, exited 1 and reported the final BusyBox wget HTTP/1.1 422 diagnostic when wget did not expose the response body.
  • pnpm headers:check — PASS, all 965 in-scope files have the Microsoft MIT license header after synchronizing the lower layer.
  • Final-base GitHub Actions CI run 41752659313 at be95df38c8325b05124f386695952d46a7aeedf8 — PASS. License Headers, Third-party NOTICE, detect-changes, Unit Tests (22), Lint Code, Build Applications, CLI Bundle Integration Tests, and CI Summary succeeded; non-applicable integration, gateway, LLM-eval, and Windows jobs were skipped. Secret Scan run 41752659748, PR Labeler run 41752659467, gitleaks, and license/cla also passed.

Documentation and compatibility

Updated docs/architecture/worker-requirements.md with the BusyBox wget retry/deadline behavior, final-status and incomplete-transfer handling, hardened Compose runtime, exact reviewed image digest, and residual signature/SBOM/CVE limitations.

No API, CLI, manifest, or registration endpoint compatibility changes. Existing retry environment variables remain supported. Runtime startup no longer depends on Alpine package repositories. The service remains non-root and receives no Docker socket, credentials, or additional host mounts.

Checklist

  • If Portal features changed, keep CLI capabilities in sync. — N/A, no Portal changes.
  • If Portal components changed, update their Storybook stories. — N/A, no Portal changes.
  • If database changes require a migration, include up() / down() and keep it CosmosDB-compatible. — N/A, no database changes.
  • If dependencies changed, update the lockfile and regenerate NOTICE / NOTICE-REVIEW.txt with pnpm notice as needed. — N/A, no dependency changes.
  • Video showing the behavior before the suggested change — N/A, infrastructure-only change.
  • Video showing the behavior after the suggested change — N/A, infrastructure-only change.

@github-actions github-actions Bot added type: documentation Documentation additions, corrections, and improvements. area: cicd Build, test, release, and deployment pipelines. area: infrastructure Cloud resources, hosting, networking, and shared infrastructure. language: javascript Work involving JavaScript code, tooling, or dependencies. topic: testing Test coverage, test infrastructure, and validation quality. labels Nov 3, 2026
@github-actions

github-actions Bot commented Nov 3, 2026

Copy link
Copy Markdown

Test Results (Node.js 22)

test: Run #443

Tests 📝 Passed ✅ Failed ❌ Skipped ⏭️ Pending ⏳ Other ❓ Flaky 🍂 Duration ⏱️
3238 3238 0 0 0 0 0 1m27s

🎉 All tests passed!

Github Test Reporter

@github-actions

github-actions Bot commented Nov 4, 2026

Copy link
Copy Markdown

Test Results (Node.js 22)

test: Run #452

Tests 📝 Passed ✅ Failed ❌ Skipped ⏭️ Pending ⏳ Other ❓ Flaky 🍂 Duration ⏱️
3238 3238 0 0 0 0 0 1m29s

🎉 All tests passed!

Github Test Reporter

@github-actions

github-actions Bot commented Nov 4, 2026

Copy link
Copy Markdown

Test Results (Node.js 22)

test: Run #460

Tests 📝 Passed ✅ Failed ❌ Skipped ⏭️ Pending ⏳ Other ❓ Flaky 🍂 Duration ⏱️
3238 3238 0 0 0 0 0 1m12s

🎉 All tests passed!

Github Test Reporter

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

LGTM

@github-actions

github-actions Bot commented Nov 4, 2026

Copy link
Copy Markdown

Test Results (Node.js 22)

test: Run #477

Tests 📝 Passed ✅ Failed ❌ Skipped ⏭️ Pending ⏳ Other ❓ Flaky 🍂 Duration ⏱️
3238 3238 0 0 0 0 0 1m0s

🎉 All tests passed!

Github Test Reporter

Base automatically changed from cedricvidal-reproduce-copilot-stack-issue to main November 5, 2026 00:37
Use the pinned yq image and BusyBox wget for bounded, non-root agent registration without runtime package installation.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@cedricvidal
Cedric Vidal (cedricvidal) force-pushed the cedricvidal-harden-agent-registration branch from cabf646 to c9128f6 Compare November 5, 2026 00:37
@cedricvidal
Cedric Vidal (cedricvidal) marked this pull request as ready for review November 5, 2026 00:37
@cedricvidal
Cedric Vidal (cedricvidal) merged commit 96f8571 into main Nov 5, 2026
17 checks passed
@github-actions

github-actions Bot commented Nov 5, 2026

Copy link
Copy Markdown

Test Results (Node.js 22)

test: Run #493

Tests 📝 Passed ✅ Failed ❌ Skipped ⏭️ Pending ⏳ Other ❓ Flaky 🍂 Duration ⏱️
3238 3238 0 0 0 0 0 1m26s

🎉 All tests passed!

Github Test Reporter

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area: cicd Build, test, release, and deployment pipelines. area: infrastructure Cloud resources, hosting, networking, and shared infrastructure. language: javascript Work involving JavaScript code, tooling, or dependencies. topic: testing Test coverage, test infrastructure, and validation quality. type: documentation Documentation additions, corrections, and improvements.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants