hashicorp/terraform · error

subdirectory path leads outside of the module package

Error message

subdirectory path %q leads outside of the module package

What it means

Returned by parseModuleSourceRemote after SplitPackageSubdir extracts a subdir that begins with '../'. Such a subdir would resolve outside the downloaded module package, so it is rejected as a safety/validity guard. The %q is the offending subdir.

Solutions

  1. Use a subdir that stays within the package, e.g. '//modules/vpc'.
  2. If you need a sibling package, declare it as a separate module source rather than traversing out.
  3. Re-examine the part after '//' and remove any leading '../'.

Example fix

# before (escapes package)
source = "github.com/org/repo//../other-module"

# after (within package)
source = "github.com/org/repo//modules/vpc"
Defensive patterns

Strategy: validation

Validate before calling

// Reject subdirs that escape the package before parsing.
// _, sub := SplitPackageSubdir(raw)
// if strings.HasPrefix(sub, "../") {
//     return fmt.Errorf("subdir %q escapes the module package", sub)
// }

Prevention

When it happens

Trigger: A module source like 'github.com/org/repo//../../escape' or any '...//../...' whose subdir portion starts with '../' reaches the remote parser.

Common situations: User tries to point a module at a sibling directory via parent traversal in a remote source; copy-paste of a relative path that worked locally but is meaningless for a remote package; attempted path-traversal.

Related errors


AI-assisted analysis of hashicorp/terraform@d32a084675 (2026-08-11). Data as JSON: /api/errors/b7610ad3326baa30. Report an issue: GitHub.

Appendix: source

Thrown at internal/getmodules/moduleaddrs/source_parsing.go:173

	if isModuleSourceLocal(raw) {
		return addrs.ModuleSourceRegistry{}, fmt.Errorf("can't use local directory %q as a module registry address", raw)
	}

	src, err := tfaddr.ParseModuleSource(raw)
	if err != nil {
		return nil, err
	}
	return addrs.ModuleSourceRegistry{
		Package: src.Package,
		Subdir:  src.Subdir,
	}, nil
}

func parseModuleSourceRemote(raw string) (addrs.ModuleSourceRemote, error) {
	var subDir string
	raw, subDir = SplitPackageSubdir(raw)
	if strings.HasPrefix(subDir, "../") {
		return addrs.ModuleSourceRemote{}, fmt.Errorf("subdirectory path %q leads outside of the module package", subDir)
	}

	// A remote source address is really just a go-getter address resulting
	// from go-getter's "detect" phase, which adds on the prefix specifying
	// which protocol it should use and possibly also adjusts the
	// protocol-specific part into different syntax.
	//
	// Note that for historical reasons this can potentially do network
	// requests in order to disambiguate certain address types, although
	// that's a legacy thing that is only for some specific, less-commonly-used
	// address types. Most just do local string manipulation. We should
	// aim to remove the network requests over time, if possible.
	norm, moreSubDir, err := NormalizePackageAddress(raw)
	if err != nil {
		// We must pass through the returned error directly here because
		// the getmodules package has some special error types it uses
		// for certain cases where the UI layer might want to include a
		// more helpful error message.

View on GitHub (pinned to d32a084675)