JuliusBrussee/caveman · error

awscreds: refusing plaintext IMDS endpoint at host

Error message

awscreds: refusing plaintext IMDS endpoint at host %q (allowed: loopback, 169.254.169.254, fd00:ec2::254)

What it means

checkIMDSEndpoint rejects an IMDS endpoint (AWS_EC2_METADATA_SERVICE_ENDPOINT) that uses plain http:// to a host not on its allowlist (loopback, 169.254.169.254, fd00:ec2::254). Like the container check, it prevents IMDS tokens/credentials from being sent over plaintext HTTP to arbitrary hosts.

Solutions

  1. Use the default endpoint (http://169.254.169.254/latest) or set AWS_EC2_METADATA_SERVICE_ENDPOINT to exactly that host.
  2. Serve the metadata endpoint over https:// if it is remote.
  3. Bind a custom IMDS simulator to 127.0.0.1 and point the env var at the loopback URL.
  4. Unset AWS_EC2_METADATA_SERVICE_ENDPOINT when running on real EC2 instances.

Example fix

// before
os.Setenv("AWS_EC2_METADATA_SERVICE_ENDPOINT", "http://imds.lab.internal")
// after
os.Setenv("AWS_EC2_METADATA_SERVICE_ENDPOINT", "http://127.0.0.1:8080") // simulator on loopback
Defensive patterns

Strategy: validation

Validate before calling

u, _ := url.Parse(os.Getenv("AWS_EC2_METADATA_SERVICE_ENDPOINT"))
imdsOK := map[string]bool{"169.254.169.254": true, "127.0.0.1": true, "::1": true, "fd00:ec2::254": true}
if u != nil && u.Scheme == "http" && !imdsOK[u.Hostname()] {
    return fmt.Errorf("IMDS endpoint %q not allowed over plaintext http", u.Host)
}

Try / catch

if err != nil && strings.Contains(err.Error(), "refusing plaintext IMDS") {
    log.Fatal("use the default IMDS endpoint or bind your simulator to loopback")
}

Prevention

When it happens

Trigger: AWS_EC2_METADATA_SERVICE_ENDPOINT is set to an http:// URL with a hostname other than loopback or the EC2 metadata link-local addresses; runs before fromIMDS makes any request.

Common situations: Redirecting IMDS at a test/staging metadata simulator on a LAN hostname over http; typos in the endpoint; a custom IMDS proxy bound to a non-loopback address; leftover endpoint config from a hybrid-cloud setup.

Understand the failure class

Background: "Invalid value" and "allowed values are" config errors: what your library rejected and how to fix it — this error's family across 41 libraries.

Related errors


AI-assisted analysis of JuliusBrussee/caveman@3ee70a1026 (2026-09-20). Data as JSON: /api/errors/a61cd7792e4e77dd. Report an issue: GitHub.

Appendix: source

Thrown at shared/platform/awscreds/awscreds.go:531

}

// checkIMDSEndpoint is checkContainerURI for AWS_EC2_METADATA_SERVICE_ENDPOINT.
// That variable was taken verbatim and then dialled with p.link — the client
// that deliberately ignores every proxy setting — so any host named there became
// a proxy-bypassing outbound request with the IMDSv2 token attached.
func checkIMDSEndpoint(raw string) error {
	u, err := url.Parse(raw)
	if err != nil || u.Host == "" {
		return errors.New("awscreds: AWS_EC2_METADATA_SERVICE_ENDPOINT is not a valid URL")
	}
	switch u.Scheme {
	case "https":
		return nil
	case "http":
		if plaintextHostAllowed(u.Hostname(), imdsHosts) {
			return nil
		}
		return fmt.Errorf("awscreds: refusing plaintext IMDS endpoint at host %q (allowed: loopback, 169.254.169.254, fd00:ec2::254)", u.Hostname())
	default:
		return fmt.Errorf("awscreds: unsupported IMDS endpoint scheme %q", u.Scheme)
	}
}

func (p *Provider) fromIMDS(ctx context.Context) (*result, error) {
	if strings.EqualFold(p.env("AWS_EC2_METADATA_DISABLED"), "true") {
		return nil, nil
	}
	base := p.env("AWS_EC2_METADATA_SERVICE_ENDPOINT")
	if base == "" {
		base = defaultIMDSBase
	}
	if err := checkIMDSEndpoint(base); err != nil {
		return nil, err
	}
	base = strings.TrimSuffix(base, "/")

View on GitHub (pinned to 3ee70a1026)