{"record":{"id":"beaade1e5c3d1559","repo":"wagoodman/dive","slug":"failed-to-unmarshal-manifest-w","errorCode":null,"errorMessage":"failed to unmarshal manifest: %w","messagePattern":"failed to unmarshal manifest: %w","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"dive/image/docker/manifest.go","lineNumber":18,"sourceCode":"package docker\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n)\n\ntype manifest struct {\n\tConfigPath    string   `json:\"Config\"`\n\tRepoTags      []string `json:\"RepoTags\"`\n\tLayerTarPaths []string `json:\"Layers\"`\n}\n\nfunc newManifest(manifestBytes []byte) manifest {\n\tvar manifest []manifest\n\terr := json.Unmarshal(manifestBytes, &manifest)\n\tif err != nil {\n\t\tpanic(fmt.Errorf(\"failed to unmarshal manifest: %w\", err))\n\t}\n\treturn manifest[0]\n}\n","sourceCodeStart":1,"sourceCodeEnd":22,"githubUrl":"https://github.com/wagoodman/dive/blob/d6c691947f8fda635c952a17ee3b7555379d58f0/dive/image/docker/manifest.go#L1-L22","documentation":"newManifest unmarshals manifest.json from a docker image archive and panics if json.Unmarshal fails. The expected shape is a JSON array of manifests of which element [0] is taken, so any non-array JSON (or invalid JSON at all) aborts the process instead of returning an error. Because it is a panic (not a returned error), it will crash the caller unless recovered.","triggerScenarios":"Calling NewImageArchive on a reader whose payload is not a well-formed 'docker save' archive: manifest.json missing, empty, truncated, or a single JSON object instead of an array (some tools and hand-crafted archives emit {}). Note also that an empty array '[]' passes Unmarshal but then panics on manifest[0] with an index-out-of-range.","commonSituations":"Feeding dive a random tarball that is not a docker-save archive (e.g. a source tarball, an OCI layout directory tarred up); partial downloads; archives generated by tools that write a different manifest schema; piping a compressed stream (gzip) where the reader expects raw tar.","solutions":["Ensure the input is a real 'docker save' / 'podman image save' tarball: 'docker save <img> -o img.tar' and pass img.tar","Inspect the archive before loading: 'tar -xOf img.tar manifest.json' and confirm it is a JSON array like [{\"Config\":..., \"Layers\":[...]}]","If the input is gzip-compressed, wrap it in a gzip.Reader before passing it to NewImageArchive","Wrap calls in a defer/recover (see defense) because this failure mode is a panic, not an error value"],"exampleFix":"// before\nfunc load(f io.Reader) {\n    img, err := docker.NewImageArchive(f) // panics inside on bad manifest.json\n    ...\n}\n\n// after: guard against the panic at the call site\nfunc load(f io.Reader) (img *docker.ImageArchive, err error) {\n    defer func() {\n        if r := recover(); r != nil {\n            err = fmt.Errorf(\"invalid image archive (bad manifest.json): %v\", r)\n        }\n    }()\n    return docker.NewImageArchive(f)\n}","handlingStrategy":"try-catch","validationCode":"// Pre-validate that the payload contains a manifest.json that is a non-empty JSON array.\nfunc hasValidManifest(r io.ReaderAt) bool {\n    tr := tar.NewReader(io.NewSectionReader(r, 0, r.Size()))\n    for {\n        h, err := tr.Next()\n        if err == io.EOF { return false }\n        if err != nil { return false }\n        if path.Clean(h.Name) == \"manifest.json\" {\n            var m []map[string]any\n            return json.NewDecoder(tr).Decode(&m) == nil && len(m) > 0\n        }\n    }\n}","typeGuard":null,"tryCatchPattern":"// This failure is a panic inside the library, so guard with defer/recover at the call boundary.\nfunc safeNewImageArchive(f io.Reader) (img *docker.ImageArchive, err error) {\n    defer func() {\n        if r := recover(); r != nil {\n            err = fmt.Errorf(\"invalid image archive (manifest.json): %v\", r)\n        }\n    }()\n    return docker.NewImageArchive(f)\n}","preventionTips":["Always pre-check the input is a docker-save tarball ('tar -tf x.tar manifest.json')","Decompress gzip inputs before passing readers to dive","Never pass arbitrary user-supplied tars straight into NewImageArchive without the recover wrapper"],"tags":["panic","json","manifest","docker-archive","dive"],"backgroundTag":null,"analyzedSha":"d6c691947f8fda635c952a17ee3b7555379d58f0","analyzedAt":"2026-08-15T09:42:35.293Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}