Skip to content

fix(deps): remove vulnerable Docker SDK path from root module#188

Merged
SantiagoDePolonia merged 2 commits intomainfrom
sec/docker-upgrade
Mar 30, 2026
Merged

fix(deps): remove vulnerable Docker SDK path from root module#188
SantiagoDePolonia merged 2 commits intomainfrom
sec/docker-upgrade

Conversation

@SantiagoDePolonia
Copy link
Copy Markdown
Contributor

@SantiagoDePolonia SantiagoDePolonia commented Mar 30, 2026

Summary

  • remove the vulnerable github.com/docker/docker transitive dependency from the root module by dropping testcontainers-go
  • replace the integration test bootstrap with an explicit Docker CLI harness for PostgreSQL and MongoDB
  • update integration test docs/comments to reflect the new Docker-managed container setup

Security context

Dependabot flagged the root module for:

The repo was pulling github.com/docker/docker v28.5.2+incompatible only through integration-test dependencies. This PR removes that path from the root go.mod instead of forcing an incompatible Docker SDK upgrade.

Verification

  • go test ./internal/... ./config/...
  • go test -v -tags=integration -timeout=10m ./tests/integration/...

Summary by CodeRabbit

  • Chores

    • Replaced external container management library with Docker CLI-based integration test helpers, reducing dependencies.
    • Updated Makefile and package documentation to reflect Docker CLI usage for integration tests.
  • Documentation

    • Updated testing strategy documentation to clarify that integration tests use Docker-managed containers.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Mar 30, 2026

📝 Walkthrough

Walkthrough

This pull request replaces the testcontainers-go library dependency with direct Docker CLI-based container management for integration tests. Documentation, dependency declarations, and test implementations are updated to reflect this architectural shift from library-driven to CLI-driven container orchestration.

Changes

Cohort / File(s) Summary
Documentation Updates
CLAUDE.md, Makefile, docs/TESTING_STRATEGY.md, tests/integration/doc.go
Updated references from "testcontainers" to "Docker-managed containers" and clarified that Docker CLI is used directly rather than through the testcontainers library.
Dependency Management
go.mod
Removed github.com/testcontainers/testcontainers-go and its MongoDB/PostgreSQL modules; also removed transitive Docker/containerd, OpenTelemetry, and utility dependencies. Added kr/text and go-internal as indirect dependencies.
Docker CLI Helper Layer
tests/integration/docker_test.go
New test helper file providing Docker container orchestration: dockerContainer type with methods for running containers, executing commands, querying ports/IPs, polling for readiness, and cleanup. Includes utilities for Docker output parsing and host address resolution.
Docker Helper Unit Tests
tests/integration/docker_test_internal_test.go
Test coverage for Docker output parsing logic (parseDockerRunContainerID) with cases for valid multi-line Docker output and error handling for missing container IDs.
Integration Test Migration
tests/integration/main_test.go
Refactored from testcontainers-go to the new Docker CLI helpers; PostgreSQL and MongoDB container startup now use dockerRunDetached with explicit readiness polling. MongoDB replica set initialization is now manual via rs.initiate() and rs.status() checks. Added mongoReplicaSetName constant.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐰 A rabbit exchanges library chains for Docker's CLI commands,
No more testcontainers cluttering the path—
Just pure container juggling, raw and grand,
Replicas are initiated with willful hands,
And ephemeral databases spin up, then vanish like morning mist. ✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title directly describes the main change: removing testcontainers-go (a Docker SDK dependency path) from the module. It matches the primary objective and file changes.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch sec/docker-upgrade

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@SantiagoDePolonia SantiagoDePolonia marked this pull request as ready for review March 30, 2026 19:06
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
tests/integration/main_test.go (1)

139-205: ⚠️ Potential issue | 🟠 Major

Give each Mongo bootstrap phase its own timeout budget.

readyCtx starts before the first shell ping and keeps ticking through rs.initiate, the replica-set readiness loop, and the final driver ping. Because rs.initiate itself runs on the 10-minute parent context, a slow initiate/election can consume most of the 60-second budget before the later waits even start, causing false setup failures. Create a fresh timeout per phase, or wrap the whole bootstrap in a longer parent readiness deadline.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/integration/main_test.go` around lines 139 - 205, The readiness timeout
(readyCtx) is reused across multiple bootstrap phases (initial shell ping via
waitForCondition, rs.initiate via mongoContainer.exec, replica-set readiness
wait, and the final mongoClient.Ping), so a slow phase can exhaust the 60s
budget and cause later waits to fail; fix by giving each phase its own
context.WithTimeout (e.g., create new readyCtx/ctxTimeout before each call to
waitForCondition, before the rs.initiate mongoContainer.exec call, before the
replica-set readiness wait, and before the final mongoClient.Ping), ensure you
call the corresponding cancel() for each context, or alternatively extend the
overall parent deadline if you intend a longer shared budget; update references
around waitForCondition, mongoContainer.exec(rs.initiate), and mongoClient.Ping
to use the fresh per-phase contexts.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@tests/integration/docker_test_internal_test.go`:
- Around line 1-33: The tests for parseDockerRunContainerID and
isLikelyDockerContainerID are currently under an integration build tag; move the
pure-string helper functions (parseDockerRunContainerID and
isLikelyDockerContainerID) into a non-build-tagged source file and add
corresponding unit tests in an untagged *_test.go file (or remove the
integration build tag from the test file) so they run in the normal unit-test
workflow without Docker; ensure the helper function signatures remain unchanged
and update imports if needed so the new test file references
parseDockerRunContainerID and isLikelyDockerContainerID directly.

In `@tests/integration/docker_test.go`:
- Around line 20-88: The file uses a custom Docker wrapper (dockerRunDetached,
runDocker, parseDockerRunContainerID usage, and dockerContainer methods
exec/hostPort/ip/terminate); replace these with testcontainers-go APIs: create
containers via testcontainers.GenericContainer/CreateContainerRequest, set
Image, ExposedPorts and WaitingFor strategies, obtain host and mapped ports
using container.Host(ctx) and container.MappedPort(ctx, nat.Port(...)), run
commands with container.Exec(ctx, []string{...}), and terminate with
container.Terminate(ctx); alternatively, if you must avoid adding testcontainers
to the root module, move the integration tests into a nested module and update
imports to use github.com/testcontainers/testcontainers-go and adjust tests to
use the container methods above instead of
dockerRunDetached/runDocker/dockerContainer.
- Around line 109-128: dockerPublishedHost() currently returns 127.0.0.1 when
DOCKER_HOST is empty, which breaks remote docker contexts; update it to, when
raw == "", run `docker context inspect` for the active context (e.g., `docker
context inspect <name>` or `docker context inspect` without args) and parse the
JSON to extract the docker endpoint host (look under the Endpoints -> docker ->
Host or similar field), use that hostname if present, and only fall back to
"127.0.0.1" on error or if no host found; ensure you handle command errors, JSON
parsing errors, and keep existing parsing for DOCKER_HOST in
dockerPublishedHost().

---

Outside diff comments:
In `@tests/integration/main_test.go`:
- Around line 139-205: The readiness timeout (readyCtx) is reused across
multiple bootstrap phases (initial shell ping via waitForCondition, rs.initiate
via mongoContainer.exec, replica-set readiness wait, and the final
mongoClient.Ping), so a slow phase can exhaust the 60s budget and cause later
waits to fail; fix by giving each phase its own context.WithTimeout (e.g.,
create new readyCtx/ctxTimeout before each call to waitForCondition, before the
rs.initiate mongoContainer.exec call, before the replica-set readiness wait, and
before the final mongoClient.Ping), ensure you call the corresponding cancel()
for each context, or alternatively extend the overall parent deadline if you
intend a longer shared budget; update references around waitForCondition,
mongoContainer.exec(rs.initiate), and mongoClient.Ping to use the fresh
per-phase contexts.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 466b0a51-2f73-405e-9480-63423b426f28

📥 Commits

Reviewing files that changed from the base of the PR and between 64320e7 and 35ae3f6.

⛔ Files ignored due to path filters (1)
  • go.sum is excluded by !**/*.sum
📒 Files selected for processing (8)
  • CLAUDE.md
  • Makefile
  • docs/TESTING_STRATEGY.md
  • go.mod
  • tests/integration/doc.go
  • tests/integration/docker_test.go
  • tests/integration/docker_test_internal_test.go
  • tests/integration/main_test.go

Comment on lines +1 to +33
//go:build integration

package integration

import "testing"

func TestParseDockerRunContainerID(t *testing.T) {
t.Parallel()

output := "Unable to find image 'postgres:16-alpine' locally\r\n" +
"16-alpine: Pulling from library/postgres\r\n" +
"Status: Downloaded newer image for postgres:16-alpine\r\n" +
"e591860a76dbb858258e678a2d9c76fbd5fc3e6c5bf6e86f93266a14ca6be7f8\r\n"

id, err := parseDockerRunContainerID(output)
if err != nil {
t.Fatalf("parseDockerRunContainerID returned error: %v", err)
}

const want = "e591860a76dbb858258e678a2d9c76fbd5fc3e6c5bf6e86f93266a14ca6be7f8"
if id != want {
t.Fatalf("parseDockerRunContainerID returned %q, want %q", id, want)
}
}

func TestParseDockerRunContainerIDRejectsUnexpectedOutput(t *testing.T) {
t.Parallel()

_, err := parseDockerRunContainerID("Status: Downloaded newer image for postgres:16-alpine")
if err == nil {
t.Fatal("parseDockerRunContainerID unexpectedly succeeded")
}
}
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.

🛠️ Refactor suggestion | 🟠 Major

Move these parser checks out of the integration build.

parseDockerRunContainerID and isLikelyDockerContainerID are pure string helpers, but this build tag forces their coverage into the Docker-backed integration path. Please move the pure helpers into an untagged file/package that the regular unit-test workflow can exercise, or extend the unit-test target so these tests run without Docker.

As per coding guidelines, **/*_test.go: "Unit tests must be written in *_test.go files alongside implementation files with no Docker dependency".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/integration/docker_test_internal_test.go` around lines 1 - 33, The
tests for parseDockerRunContainerID and isLikelyDockerContainerID are currently
under an integration build tag; move the pure-string helper functions
(parseDockerRunContainerID and isLikelyDockerContainerID) into a
non-build-tagged source file and add corresponding unit tests in an untagged
*_test.go file (or remove the integration build tag from the test file) so they
run in the normal unit-test workflow without Docker; ensure the helper function
signatures remain unchanged and update imports if needed so the new test file
references parseDockerRunContainerID and isLikelyDockerContainerID directly.

Comment on lines +90 to +107
func waitForCondition(ctx context.Context, interval time.Duration, check func(context.Context) error) error {
var lastErr error

for {
attemptCtx, cancel := context.WithTimeout(ctx, interval)
lastErr = check(attemptCtx)
cancel()
if lastErr == nil {
return nil
}

select {
case <-ctx.Done():
return fmt.Errorf("%w: last error: %v", ctx.Err(), lastErr)
case <-time.After(interval):
}
}
}
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.

⚠️ Potential issue | 🟠 Major

Decouple the retry cadence from the per-attempt deadline.

Every check is canceled after exactly interval, and the Mongo call sites pass 2*time.Second. Spawning docker exec ... mongosh can exceed that on slower CI or Docker Desktop hosts, so the readiness loop can fail even when the container is healthy. Give each attempt a longer timeout than the sleep interval.

Suggested fix
-func waitForCondition(ctx context.Context, interval time.Duration, check func(context.Context) error) error {
+func waitForCondition(ctx context.Context, interval, attemptTimeout time.Duration, check func(context.Context) error) error {
 	var lastErr error

 	for {
-		attemptCtx, cancel := context.WithTimeout(ctx, interval)
+		attemptCtx, cancel := context.WithTimeout(ctx, attemptTimeout)
 		lastErr = check(attemptCtx)
 		cancel()

Then call the Mongo readiness checks with something like interval=2*time.Second and attemptTimeout=10*time.Second.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
func waitForCondition(ctx context.Context, interval time.Duration, check func(context.Context) error) error {
var lastErr error
for {
attemptCtx, cancel := context.WithTimeout(ctx, interval)
lastErr = check(attemptCtx)
cancel()
if lastErr == nil {
return nil
}
select {
case <-ctx.Done():
return fmt.Errorf("%w: last error: %v", ctx.Err(), lastErr)
case <-time.After(interval):
}
}
}
func waitForCondition(ctx context.Context, interval, attemptTimeout time.Duration, check func(context.Context) error) error {
var lastErr error
for {
attemptCtx, cancel := context.WithTimeout(ctx, attemptTimeout)
lastErr = check(attemptCtx)
cancel()
if lastErr == nil {
return nil
}
select {
case <-ctx.Done():
return fmt.Errorf("%w: last error: %v", ctx.Err(), lastErr)
case <-time.After(interval):
}
}
}

Comment on lines +109 to +128
func dockerPublishedHost() string {
raw := os.Getenv("DOCKER_HOST")
if raw == "" {
return "127.0.0.1"
}

parsed, err := url.Parse(raw)
if err != nil {
return "127.0.0.1"
}

switch parsed.Scheme {
case "", "unix", "npipe":
return "127.0.0.1"
default:
if host := parsed.Hostname(); host != "" {
return host
}
return "127.0.0.1"
}
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.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

current_context=$(docker context show)
current_host=$(docker context inspect "$current_context" --format '{{ (index .Endpoints "docker").Host }}')

printf 'docker context: %s\n' "$current_context"
printf 'context host: %s\n' "$current_host"
printf 'DOCKER_HOST=%s\n' "${DOCKER_HOST-<unset>}"

if [[ -z "${DOCKER_HOST-}" && "$current_host" != unix://* && "$current_host" != npipe://* ]]; then
  echo "dockerPublishedHost() would incorrectly return 127.0.0.1 here"
  exit 1
fi

Repository: ENTERPILOT/GoModel

Length of output: 108


🏁 Script executed:

cd tests/integration && wc -l docker_test.go

Repository: ENTERPILOT/GoModel

Length of output: 80


🏁 Script executed:

cat -n tests/integration/docker_test.go | head -150

Repository: ENTERPILOT/GoModel

Length of output: 4559


🏁 Script executed:

rg "dockerPublishedHost" tests/integration/

Repository: ENTERPILOT/GoModel

Length of output: 409


🏁 Script executed:

rg "docker context" .

Repository: ENTERPILOT/GoModel

Length of output: 44


🏁 Script executed:

rg -i "docker.context|remote.daemon" tests/integration/

Repository: ENTERPILOT/GoModel

Length of output: 44


🏁 Script executed:

cat -n tests/integration/main_test.go | head -100

Repository: ENTERPILOT/GoModel

Length of output: 2980


🏁 Script executed:

cat -n tests/integration/main_test.go | tail -50

Repository: ENTERPILOT/GoModel

Length of output: 1604


🏁 Script executed:

git log --oneline tests/integration/docker_test.go | head -10

Repository: ENTERPILOT/GoModel

Length of output: 111


🏁 Script executed:

fd -t f "README" tests/integration/ -o -path "*.md" tests/integration/

Repository: ENTERPILOT/GoModel

Length of output: 178


🏁 Script executed:

cat -n README.md | grep -A 20 -B 5 -i "docker\|integration\|test"

Repository: ENTERPILOT/GoModel

Length of output: 8790


Resolve the active Docker context before falling back to localhost.

When contributors use docker context to target a remote daemon, DOCKER_HOST is often unset. In that case dockerPublishedHost() returns 127.0.0.1, causing PostgreSQL and MongoDB connection URLs to point at the wrong machine. The function should read the active docker context via docker context inspect when DOCKER_HOST is unset to support remote daemon configurations.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/integration/docker_test.go` around lines 109 - 128,
dockerPublishedHost() currently returns 127.0.0.1 when DOCKER_HOST is empty,
which breaks remote docker contexts; update it to, when raw == "", run `docker
context inspect` for the active context (e.g., `docker context inspect <name>`
or `docker context inspect` without args) and parse the JSON to extract the
docker endpoint host (look under the Endpoints -> docker -> Host or similar
field), use that hostname if present, and only fall back to "127.0.0.1" on error
or if no host found; ensure you handle command errors, JSON parsing errors, and
keep existing parsing for DOCKER_HOST in dockerPublishedHost().

@SantiagoDePolonia SantiagoDePolonia merged commit 5d6286f into main Mar 30, 2026
16 checks passed
@SantiagoDePolonia SantiagoDePolonia deleted the sec/docker-upgrade branch April 1, 2026 21:29
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.

1 participant