{"record":{"id":"55d0712216458085","repo":"docker/compose","slug":"invalid-oci-artifact","errorCode":null,"errorMessage":"invalid OCI artifact","messagePattern":"invalid OCI artifact","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/remote/oci.go","lineNumber":52,"sourceCode":"\t\"github.com/docker/cli/cli/command\"\n\tspec \"github.com/opencontainers/image-spec/specs-go/v1\"\n\n\t\"github.com/docker/compose/v5/internal/desktop\"\n\t\"github.com/docker/compose/v5/internal/oci\"\n\t\"github.com/docker/compose/v5/pkg/api\"\n)\n\nconst (\n\tOCI_REMOTE_ENABLED = \"COMPOSE_EXPERIMENTAL_OCI_REMOTE\"\n\tOciPrefix          = \"oci://\"\n)\n\n// validatePathInBase ensures a file path is contained within the base directory,\n// as OCI artifact resources must all live within the same folder.\nfunc validatePathInBase(base, unsafePath string) error {\n\t// Reject paths with path separators regardless of OS\n\tif strings.ContainsAny(unsafePath, \"\\\\/\") {\n\t\treturn fmt.Errorf(\"invalid OCI artifact\")\n\t}\n\n\t// Join the base with the untrusted path\n\ttargetPath := filepath.Join(base, unsafePath)\n\n\t// Get the directory of the target path\n\ttargetDir := filepath.Dir(targetPath)\n\n\t// Clean both paths to resolve any .. or . components\n\tcleanBase := filepath.Clean(base)\n\tcleanTargetDir := filepath.Clean(targetDir)\n\n\t// Check if the target directory is the same as base directory\n\tif cleanTargetDir != cleanBase {\n\t\treturn fmt.Errorf(\"invalid OCI artifact\")\n\t}\n\n\treturn nil","sourceCodeStart":34,"sourceCodeEnd":70,"githubUrl":"https://github.com/docker/compose/blob/ddc4b044b62e9f715212ea4143fa830fac76382f/pkg/remote/oci.go#L34-L70","documentation":"First guard of `validatePathInBase` for `oci://` includes: OCI artifact resources must all live in a single flat directory, so any fragment containing `/` or `\\` is rejected outright with the generic 'invalid OCI artifact' message. This differs from the git loader, which allows nested subdirectories.","triggerScenarios":"Including `oci://registry/repo:tag#sub/dir/compose.yaml` — any `#fragment` with a path separator fails, because the extracted files must sit directly in the base directory.","commonSituations":"Reusing git-include muscle memory (`#subdir/path`) with oci:// includes; OCI artifacts packaged with nested layouts that Compose cannot address.","solutions":["Package the OCI artifact so the compose file sits at its root and reference it with no path fragment: `oci://registry/repo:tag`","Use a git:// include instead if you genuinely need nested subdirectory selection"],"exampleFix":"# before\ninclude:\n  - path: oci://registry.example.com/team/configs:1.0#prod/compose.yaml\n\n# after\ninclude:\n  - path: oci://registry.example.com/team/configs:1.0","handlingStrategy":"validation","validationCode":"# oci:// fragments must be a bare filename: no / or \\\npython3 - <<'EOF'\nimport sys\nfor arg in sys.argv[1:]:\n    if not arg.startswith('oci://'): continue\n    frag = arg.split('#',1)[1] if '#' in arg else ''\n    if '/' in frag or '\\\\' in frag:\n        sys.exit(f\"oci include fragment must be a flat filename: {arg}\")\nEOF","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Package OCI artifacts flat: the compose file at the artifact root, no nested directories","Remember the oci:// and git:// include rules differ — oci has no subdirectory support"],"tags":["compose","oci","include","path-validation"],"backgroundTag":null,"analyzedSha":"ddc4b044b62e9f715212ea4143fa830fac76382f","analyzedAt":"2026-08-15T13:31:42.319Z","schemaVersion":2},"datasetVersion":"2026-08-16T03:17:38.424Z"}