Skip to content

fix: resolve eight confirmed bugs from production bug scan#75

Merged
deveshctl merged 4 commits into
mainfrom
fix/confirmed-bug-scan-findings
Jul 9, 2026
Merged

fix: resolve eight confirmed bugs from production bug scan#75
deveshctl merged 4 commits into
mainfrom
fix/confirmed-bug-scan-findings

Conversation

@deveshctl

Copy link
Copy Markdown
Owner

Summary

  • BUG-5/6 — Tighten isDaemonUnreachable and isImageNotFoundMessage: remove overly broad substrings ("no such file or directory", "not found") that were misfiring on missing credential helpers, certificate errors, and routing failures, misdirecting users toward the wrong root cause
  • BUG-7/8 — Engine env var parity: DockerResolver now honours DOCKER_CONFIG; PodmanResolver now honours PODMAN_CONNECTIONS_CONF and CONTAINERS_CONF, matching the behaviour of the respective engine CLIs
  • BUG-9 — Config discovery: config.Load() walks up from CWD to find .layerx.yaml, with an XDG fallback; adds --config PATH persistent flag for explicit paths (fixes silent threshold substitution in monorepos)
  • BUG-10 — Archive auto-detection uses os.Lstat so a symlink named like an image reference isn't mistaken for an archive on Linux/macOS
  • BUG-13normalizeDigest requires exactly 64 lowercase hex chars after sha256:, preventing layerx cache prune from deleting unrelated directories in a shared LAYERX_CACHE_DIR
  • BUG-14 — File extraction (x key) no longer silently overwrites a dangling symlink; uniquePath uses os.Lstat to treat broken links as present, and atomicWriteFile returns an error rather than replacing the link

BUG-4 (whiteout re-add) was re-verified against current code and found correct — no change needed.

Test plan

  • go build ./... and go vet ./... pass (verified locally)
  • CI: go test ./... including engine resolver tests with injected env fakes
  • Verify layerx --config /path/to/config.yaml ci IMAGE loads the explicit config
  • Verify layerx ci IMAGE invoked from a monorepo subdirectory picks up the root .layerx.yaml
  • Verify DOCKER_CONFIG=/tmp/alt layerx IMAGE reads context from the alternate directory
  • Verify PODMAN_CONNECTIONS_CONF=/tmp/conns.json layerx --engine podman IMAGE routes correctly
  • Verify layerx cache prune with a shared LAYERX_CACHE_DIR does not delete non-hex-named sibling directories

deveshctl and others added 4 commits July 9, 2026 06:40
Fixes BUG-5, BUG-6, BUG-7, BUG-8, BUG-9, BUG-10, BUG-13, BUG-14 from
internal-docs/bug_scan.md. BUG-4 was re-verified against current code and
found correct — no change needed.

Error classification (BUG-5, BUG-6): remove overly broad substrings from
isDaemonUnreachable ("no such file or directory", "file does not exist") and
isImageNotFoundMessage ("not found"). Both were firing on unrelated errors
such as missing credential helpers, routing errors, and certificate failures,
misdirecting users toward the wrong root cause. Daemon check now requires
daemon-specific phrasing; image-not-found check uses OCI spec terms
("name unknown", "name not known") instead of the bare "not found" substring.

Engine env var parity (BUG-7, BUG-8): DockerResolver now honours DOCKER_CONFIG
when resolving config.json and contexts/meta/. PodmanResolver now honours
PODMAN_CONNECTIONS_CONF and CONTAINERS_CONF before falling back to XDG defaults.
Both match the behaviour of the respective engine's own CLI.

Config discovery (BUG-9): config.Load() now walks up from CWD (max 50 levels)
looking for .layerx.yaml, then falls back to $XDG_CONFIG_HOME/layerx/config.yaml.
Adds --config PATH persistent flag to bypass the walk and load from an explicit
path. Fixes silent threshold substitution when layerx ci is invoked from a
monorepo subdirectory.

Archive auto-detection (BUG-10): isRegularFilePath switches from os.Stat to
os.Lstat. A symlink named like an image reference (e.g. "nginx:latest") is no
longer mistaken for an archive path on Linux/macOS.

Cache prune safety (BUG-13): normalizeDigest now requires exactly 64 lowercase
hex characters after the sha256: prefix. Arbitrary directory names in a shared
LAYERX_CACHE_DIR are no longer accepted as evictable cache entries.

File extraction symlink safety (BUG-14): production statFile changed from
os.Stat to os.Lstat so uniquePath treats a dangling symlink as present and
bumps to a .N suffix. atomicWriteFile now detects dangling symlinks via Lstat
when EvalSymlinks returns ErrNotExist and returns an error rather than silently
replacing the link with a regular file.
The initial fix commit (f935d85) tightened three classifiers but shipped
with test-side regressions that surfaced on CI:

isDaemonUnreachable — the substring guard removed 'no such file or directory'
and 'file does not exist' entirely, breaking classifier tests that assert
'dial unix /var/run/docker.sock: connect: no such file or directory' and
'open //./pipe/docker_engine: file does not exist' are still recognised as
daemon-unreachable (both are the real moby SDK dial-error format for a
missing daemon socket). Restore both substrings but require a co-occurring
socket or named-pipe token (.sock / /pipe/ / podman.sock) before firing.
Unrelated filesystem errors (credential helpers, certificates) don't
mention a socket path and remain excluded, so BUG-5's false-positive fix
is preserved.

isImageNotFoundMessage — the pushed commit dropped the bare 'not found'
needle (BUG-6) but forgot to remove the pre-existing test case
'plain_not_found' that expected 'Error response from daemon: not found'
to match. Delete that case and replace it with a regression guard that
asserts a credential-helper error MUST NOT be classified as image-not-found.

normalizeDigest — the pushed commit's 64-hex check tripped staticcheck
QF1001 (De Morgan's law) on '!((c >= 0-9) || (c >= a-f))'. Rewrite the
predicate using two named booleans (isDigit / isHex) so no reducible
negation-of-disjunction remains for the linter to flag. The behaviour is
identical.

Test fixture migration — cache_test.go and analysis_test.go used short
synthetic digests like 'sha256:abcdef' and 'sha256:driftguard' that fail
the newly-strict normalizeDigest. Every short fixture is now
'sha256:' + strings.Repeat('<hex>', 64), preserving each test's intent
while satisfying the canonical-form requirement. TestNormalizeDigest_RejectsUnsafe
extended with length-not-64, uppercase-hex, and non-hex-ASCII cases so a
future loosening of the validator fails a test rather than silently
regressing BUG-13.

CHANGELOG — 'isDaemonUnreachable' bullet reworded to reflect the guarded
behaviour ('.sock/pipe context required') rather than the pushed commit's
'requires daemon-specific phrasing' phrasing that is no longer accurate.
ci: pin Go to 1.26.5 for GO-2026-5856 (CVE-2026-42505)
 govulncheck flagged the stdlib TLS vulnerability GO-2026-5856 when
  setup-go with spec '1.26' resolved to go1.26.4 on the CI runner.
  Pinning to 1.26.5 lands the patched toolchain. go.mod minimum stays
  at 1.26.2 so users building from source with an older 1.26.x are
  not forced to upgrade — only CI is.
@deveshctl deveshctl merged commit cb200c1 into main Jul 9, 2026
13 checks passed
@deveshctl deveshctl deleted the fix/confirmed-bug-scan-findings branch July 12, 2026 12:24
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