Gap cloud-25 · missing-coverage (plus an adaptation-decision on hostname resolution) · confidence: verified · tracked in #242
Baseline: Midge 7d39f86 (0.1.1) vs Pants 2507c66. Midge paths are relative to the midge repo, Pants paths to this repo. Midge behavior is used as the proxy for the not-yet-extracted lsm-spec behavioral contract.
Prior: G10 (partially fixed: code is present, tests are missing)
Midge: src/storage/providers/s3.rs tests should_accept_bracketed_eks_ipv6_container_credential_endpoint (:1918),
should_reject_container_relative_uri_that_can_replace_credential_host (:1956),
should_try_every_validated_container_credential_address (:1930),
should_reject_temporary_aws_credentials_without_valid_expiration (:1980). Implementation at
s3.rs:108-225: the RELATIVE_URI is re-parsed and the host must remain 169.254.170.2 with no fragment. An HTTP
FULL_URI with a DNS host is resolved, every address must be loopback, 169.254.170.2, 169.254.170.23, or fd00:ec2::23,
and the connection is pinned to those addresses (resolve_to_addrs, :465-467). Expiry parsing is strict RFC3339 and must be in the future (:803-832).
Pants: src/Pants.Core/Cloud/Internal/Providers/Credentials/S3/RefreshingS3CredentialProvider.cs. The RELATIVE_URI
prefix check is at :273-281; it does not re-check the host afterwards, but the prefix rule already blocks host replacement. FULL_URI
validation is at :285-298. IPv6 brackets are trimmed at :441. Only literal allowed IPs or the literal name localhost are accepted
(:434-450); any other hostname is rejected instead of being resolved and pinned. Multi-address connection for localhost is left to
SocketsHttpHandler. Expiration is required and must be in the future (:418-432), but DateTimeOffset.TryParse is laxer than RFC3339.
Tests in test/Pants.Tests/Cloud/S3CredentialSourceTests.cs cover only the happy paths with
http://127.0.0.1/credentials (ShouldResolveAwsContainerCredentialsWithAuthorizationTokenFile). There is no RELATIVE_URI, IPv6, or
expired/missing-expiration test.
Minor: the container token-file read catches only IOException (:168). UnauthorizedAccessException escapes without being wrapped.
Why it matters: credential endpoints are an SSRF and credential-exfiltration surface. EKS Pod Identity uses the bracketed
IPv6 endpoint. Temporary credentials that have no usable expiry would be cached forever.
Acceptance criteria:
http://[fd00:ec2::23]/v1/credentials is accepted, and http://192.0.2.1/... is rejected as insecure.
- RELATIVE_URI values
//192.0.2.1/x, x, and \\evil are rejected and no request is sent. A valid relative URI targets 169.254.170.2.
localhost FULL_URI succeeds when only one of ::1 or 127.0.0.1 is listening.
- JSON (container/IMDS) and XML (STS) responses with a missing, non-RFC3339, or past Expiration fail with PantsIOException.
- Decision recorded: either resolve, validate, and pin arbitrary HTTP hostnames as Midge does, or keep the stricter literal-only rule and document it in lsm-spec behavior/.
Adaptation note: the literal-only rule is stricter than Midge and is safe, but it rejects configurations Midge accepts.
Prior: G10 (partially fixed: code is present, tests are missing)
Midge:
src/storage/providers/s3.rstestsshould_accept_bracketed_eks_ipv6_container_credential_endpoint(:1918),should_reject_container_relative_uri_that_can_replace_credential_host(:1956),should_try_every_validated_container_credential_address(:1930),should_reject_temporary_aws_credentials_without_valid_expiration(:1980). Implementation ats3.rs:108-225: the RELATIVE_URI is re-parsed and the host must remain 169.254.170.2 with no fragment. An HTTP
FULL_URI with a DNS host is resolved, every address must be loopback, 169.254.170.2, 169.254.170.23, or fd00:ec2::23,
and the connection is pinned to those addresses (
resolve_to_addrs, :465-467). Expiry parsing is strict RFC3339 and must be in the future (:803-832).Pants:
src/Pants.Core/Cloud/Internal/Providers/Credentials/S3/RefreshingS3CredentialProvider.cs. The RELATIVE_URIprefix check is at :273-281; it does not re-check the host afterwards, but the prefix rule already blocks host replacement. FULL_URI
validation is at :285-298. IPv6 brackets are trimmed at :441. Only literal allowed IPs or the literal name
localhostare accepted(:434-450); any other hostname is rejected instead of being resolved and pinned. Multi-address connection for
localhostis left toSocketsHttpHandler. Expiration is required and must be in the future (:418-432), but
DateTimeOffset.TryParseis laxer than RFC3339.Tests in
test/Pants.Tests/Cloud/S3CredentialSourceTests.cscover only the happy paths withhttp://127.0.0.1/credentials(ShouldResolveAwsContainerCredentialsWithAuthorizationTokenFile). There is no RELATIVE_URI, IPv6, orexpired/missing-expiration test.
Minor: the container token-file read catches only IOException (:168). UnauthorizedAccessException escapes without being wrapped.
Why it matters: credential endpoints are an SSRF and credential-exfiltration surface. EKS Pod Identity uses the bracketed
IPv6 endpoint. Temporary credentials that have no usable expiry would be cached forever.
Acceptance criteria:
http://[fd00:ec2::23]/v1/credentialsis accepted, andhttp://192.0.2.1/...is rejected as insecure.//192.0.2.1/x,x, and\\evilare rejected and no request is sent. A valid relative URI targets 169.254.170.2.localhostFULL_URI succeeds when only one of ::1 or 127.0.0.1 is listening.Adaptation note: the literal-only rule is stricter than Midge and is safe, but it rejects configurations Midge accepts.