{"record":{"id":"b7610ad3326baa30","repo":"hashicorp/terraform","slug":"subdirectory-path-q-leads-outside-of-the-module-p","errorCode":null,"errorMessage":"subdirectory path %q leads outside of the module package","messagePattern":"subdirectory path %q leads outside of the module package","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/getmodules/moduleaddrs/source_parsing.go","lineNumber":173,"sourceCode":"\tif isModuleSourceLocal(raw) {\n\t\treturn addrs.ModuleSourceRegistry{}, fmt.Errorf(\"can't use local directory %q as a module registry address\", raw)\n\t}\n\n\tsrc, err := tfaddr.ParseModuleSource(raw)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn addrs.ModuleSourceRegistry{\n\t\tPackage: src.Package,\n\t\tSubdir:  src.Subdir,\n\t}, nil\n}\n\nfunc parseModuleSourceRemote(raw string) (addrs.ModuleSourceRemote, error) {\n\tvar subDir string\n\traw, subDir = SplitPackageSubdir(raw)\n\tif strings.HasPrefix(subDir, \"../\") {\n\t\treturn addrs.ModuleSourceRemote{}, fmt.Errorf(\"subdirectory path %q leads outside of the module package\", subDir)\n\t}\n\n\t// A remote source address is really just a go-getter address resulting\n\t// from go-getter's \"detect\" phase, which adds on the prefix specifying\n\t// which protocol it should use and possibly also adjusts the\n\t// protocol-specific part into different syntax.\n\t//\n\t// Note that for historical reasons this can potentially do network\n\t// requests in order to disambiguate certain address types, although\n\t// that's a legacy thing that is only for some specific, less-commonly-used\n\t// address types. Most just do local string manipulation. We should\n\t// aim to remove the network requests over time, if possible.\n\tnorm, moreSubDir, err := NormalizePackageAddress(raw)\n\tif err != nil {\n\t\t// We must pass through the returned error directly here because\n\t\t// the getmodules package has some special error types it uses\n\t\t// for certain cases where the UI layer might want to include a\n\t\t// more helpful error message.","sourceCodeStart":155,"sourceCodeEnd":191,"githubUrl":"https://github.com/hashicorp/terraform/blob/c9def3e214014c1188faabfc4a5bde5095139765/internal/getmodules/moduleaddrs/source_parsing.go#L155-L191","documentation":"Raised in parseModuleSourceRemote (internal/getmodules/moduleaddrs/source_parsing.go:173). The source is split into package + subdir (the part after '//'); if that user-supplied subdir begins with '../', it would traverse above the package root, which is forbidden. Terraform blocks this to prevent path-escape / arbitrary file access.","triggerScenarios":"A remote source with an explicit subdir that climbs out of the package, e.g. git::https://example.com/repo.git//../../etc or https://example.com/repo.zip//../sibling. The leading '../' in the cleaned subdir is the trigger.","commonSituations":"Deliberate or accidental parent traversal in a subdir; attempts to reference a sibling package via relative paths; malicious input in a multi-tenant wrapper.","solutions":["Remove leading '../' from the subdir so it resolves inside the package root.","If you need a sibling package, reference it by its own full source address rather than traversing out.","Use path.Clean and assert the result stays within the package before submitting the source."],"exampleFix":"// before\nmodule \"x\" { source = \"git::https://example.com/repo.git//../other-repo\" }\n// after\nmodule \"x\" { source = \"git::https://example.com/repo.git//modules/other\" }","handlingStrategy":"validation","validationCode":"// Reject a subdir that escapes the package via parent traversal.\nfunc subdirStaysInPackage(subDir string) bool {\n\tif subDir == \"\" {\n\t\treturn true\n\t}\n\tcleaned := path.Clean(subDir)\n\treturn cleaned != \"..\" && !strings.HasPrefix(cleaned, \"../\")\n}","typeGuard":null,"tryCatchPattern":"remoteAddr, err := moduleaddrs.ParseModuleSourceRemote(raw)\nif err != nil && strings.Contains(err.Error(), \"leads outside of the module package\") {\n    return fmt.Errorf(\"subdir in %q escapes the package; use a path inside the repo root\", raw)\n}","preventionTips":["Never start a subdir with ../ ; keep it within the package root.","Reference sibling packages by their own full source address.","Lint module sources for parent-traversal sequences in CI."],"tags":["module-source","security","path-traversal","subdir"],"analyzedSha":"c9def3e214014c1188faabfc4a5bde5095139765","analyzedAt":"2026-08-07T15:39:49.278Z","schemaVersion":2},"datasetVersion":"2026-08-07T21:17:07.882Z"}