Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions AUDIT_OPEN.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
# Open audit items

## 2026-09-25 — local image identity

Full local `sha256:<64hex>` image IDs now count as immutable in deployment
plans and receipts. An existing ID is used without a registry request; an absent
ID refuses with build/load guidance. Named mutable tags still pull on every
deploy. Regression tests cover both cache states and the provenance identity,
including malformed digests. Full Go vet and tests passed.

Unresolved findings for this repository from the ChatGPT-led audit series.
Pass 1-5 (2026-09-09 through 2026-09-11, register: teploy-neutron-lullmail
expanded audit) closed fully below. Pass 6 (2026-09-17, 78 findings F01-F78,
Expand Down
8 changes: 7 additions & 1 deletion internal/cli/deploy.go
Original file line number Diff line number Diff line change
Expand Up @@ -1320,12 +1320,15 @@ func healthConfigFrom(h config.AppHealthConfig) deploy.HealthConfig {
}

// isDigestPinned reports whether an image reference is content-addressed
// (`repo@sha256:...`). Such a reference names exactly one set of bytes forever,
// (`repo@sha256:...` or a full local `sha256:...` ID). Such a reference names exactly one set of bytes forever,
// so a local copy of it can never be out of date. Everything else — every tag,
// and a bare repo (which Docker resolves to `:latest`) — is mutable: the
// registry can move it under us at any time, and "looks like a git sha" is a
// convention nothing enforces, so tags are not special-cased here.
func isDigestPinned(image string) bool {
if deploy.ImageDigestFromRef(image) != "" {
return true
}
i := strings.LastIndex(image, "@")
if i < 0 {
return false
Expand Down Expand Up @@ -1366,6 +1369,9 @@ func ensureImage(ctx context.Context, dk *docker.Client, image string, out io.Wr
fmt.Fprintf(out, " Using local image %s (digest-pinned, cannot be stale)\n", image)
return nil
}
if strings.HasPrefix(image, "sha256:") && deploy.ImageDigestFromRef(image) != "" {
return fmt.Errorf("local image ID %s is absent from the server; build or load it there before deploying", image)
}
fmt.Fprintf(out, "Pulling image %s...\n", image)
if err := dk.Pull(ctx, image); err != nil {
if exists {
Expand Down
25 changes: 25 additions & 0 deletions internal/cli/deploy_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -208,3 +208,28 @@ func TestIsDigestPinned(t *testing.T) {
}
}
}

func TestEnsureImageLocalIDNeverPulls(t *testing.T) {
image := "sha256:" + strings.Repeat("b", 64)
for _, exists := range []bool{true, false} {
response := ""
if exists {
response = "exists\n"
}
mock := ssh.NewMockExecutor("host", ssh.MockCommand{Match: "err=$(mktemp); if docker image inspect", Output: response})
var out bytes.Buffer
err := ensureImage(context.Background(), docker.NewClient(mock), image, &out)
if exists && err != nil {
t.Fatal(err)
}
if !exists && (err == nil || !strings.Contains(err.Error(), "build or load")) {
t.Fatalf("missing image: %v", err)
}
if pullAttempted(mock) {
t.Fatal("local image ID must never cause a registry request")
}
if exists && !strings.Contains(out.String(), "digest-pinned") {
t.Fatalf("identity misclassified: %s", out.String())
}
}
}
8 changes: 6 additions & 2 deletions internal/deploy/deploy.go
Original file line number Diff line number Diff line change
Expand Up @@ -1762,14 +1762,18 @@ func containerPort(c Config) int {
}

// ImageDigestFromRef extracts the digest of a digest-pinned image
// reference ("repo@sha256:<64hex>"), or "" for every other reference
// reference ("repo@sha256:<64hex>") or a full local "sha256:<64hex>" ID,
// or "" for every other reference
// shape. Exported for the CLI's plan-time provenance, which must apply
// the SAME like-for-like rule the deployed record applies (a pinned ref
// is identified by its manifest digest, a mutable ref by docker's
// resolved content ID) or plan/receipt equality compares apples to
// oranges.
func ImageDigestFromRef(image string) string {
if _, digest, ok := strings.Cut(image, "@"); ok && strings.HasPrefix(digest, "sha256:") && len(digest) == len("sha256:")+64 {
if strings.HasPrefix(image, "sha256:") && len(image) == 71 && docker.IsImageID(image) {
return image
}
if repo, digest, ok := strings.Cut(image, "@"); ok && repo != "" && strings.HasPrefix(digest, "sha256:") && len(digest) == 71 && docker.IsImageID(digest) {
return digest
}
return ""
Expand Down
12 changes: 12 additions & 0 deletions internal/deploy/provenance_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -200,3 +200,15 @@ func TestDeploy_NoPlanDigestNoFalseAlarm(t *testing.T) {
t.Errorf("nothing to compare must not warn:\n%s", buf.String())
}
}

func TestFullLocalImageIDIsItsOwnProvenance(t *testing.T) {
id := "sha256:" + strings.Repeat("b", 64)
if got := plannedImageDigest(id, id, nil); got != id {
t.Fatalf("got %q, want %q", got, id)
}
for _, invalid := range []string{"sha256:" + strings.Repeat("z", 64), "sha256:abcdef123456", "@" + id, "repo@sha256:" + strings.Repeat("z", 64)} {
if got := ImageDigestFromRef(invalid); got != "" {
t.Fatalf("invalid identity %q returned %q", invalid, got)
}
}
}
Loading