carlos / platform Public

rootaws: root also drops AWS endpoint overrides (AWS_ENDPOINT_URL, AWS_ENDPOINT_URL_*, IMDS endpoint) and Go's SSL_CERT_FILE/SSL_CERT_DIR (#267)

An inherited override could redirect root's signed requests or replace
IMDS as the credential source; the certificate selectors would let an
unvalidated file into the TLS trust store the AWS transports use.
Nothing in the platform sets any of them.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Paul Campbell pushed by claude agent, operated by paul@keymail.dev 3eeb9ccbd52e6baba5103a9b771ac26dcffa2c05 parent 337f4ea
2 files changed, +28 −4
  • internal/rootaws/environment.go +21 −4
  • internal/rootaws/environment_test.go +7 −0

CI passed — run details

diff --git a/internal/rootaws/environment.go b/internal/rootaws/environment.go
index 13e0c2037..d4a7055aa 100644
--- a/internal/rootaws/environment.go
+++ b/internal/rootaws/environment.go
@@ -3,13 +3,14 @@ package rootaws
import (
"log"
"os"
+ "strings"
)
// A child of a device cannot exist, even if a service user creates directories.
const disabledSharedFile = "/dev/null/carlos-root-aws"
-// rootScrubbedEnvironment names every variable that selects a file, a profile
-// or a credential endpoint the SDK would read on root's behalf. Root's own
+// rootScrubbedEnvironment names every variable that selects a file, a profile,
+// an endpoint or a trust store the SDK would use on root's behalf. Root's own
// credentials come from the environment keys, the instance profile, or the
// validated off-cloud file, never from these.
var rootScrubbedEnvironment = []string{
@@ -26,8 +27,18 @@ var rootScrubbedEnvironment = []string{
"AWS_CONTAINER_CREDENTIALS_RELATIVE_URI",
"AWS_CONTAINER_AUTHORIZATION_TOKEN",
"AWS_CONTAINER_AUTHORIZATION_TOKEN_FILE",
+ "AWS_ENDPOINT_URL",
+ "AWS_EC2_METADATA_SERVICE_ENDPOINT",
+ "AWS_EC2_METADATA_SERVICE_ENDPOINT_MODE",
+ // Go's TLS trust store reads these unvalidated; AWS transports use it.
+ "SSL_CERT_FILE",
+ "SSL_CERT_DIR",
}
+// Service-specific endpoint overrides (AWS_ENDPOINT_URL_S3 and the rest)
+// are matched by prefix.
+const rootScrubbedEndpointPrefix = "AWS_ENDPOINT_URL_"
+
// ScrubEnvironment must run before dispatch or goroutines in each executable.
// It also protects SDK loads in dependencies that do not call this package.
func ScrubEnvironment() {
@@ -39,7 +50,13 @@ func scrubEnvironment(euid int) {
return
}
changed := false
- for _, key := range rootScrubbedEnvironment {
+ keys := append([]string(nil), rootScrubbedEnvironment...)
+ for _, kv := range os.Environ() {
+ if key, _, _ := strings.Cut(kv, "="); strings.HasPrefix(key, rootScrubbedEndpointPrefix) {
+ keys = append(keys, key)
+ }
+ }
+ for _, key := range keys {
if _, set := os.LookupEnv(key); set {
changed = true
}
@@ -50,6 +67,6 @@ func scrubEnvironment(euid int) {
_ = os.Setenv("AWS_CONFIG_FILE", disabledSharedFile)
_ = os.Setenv("AWS_SHARED_CREDENTIALS_FILE", disabledSharedFile)
if changed {
- log.Print("ALERT: root AWS file, profile and credential-endpoint environment overrides ignored")
+ log.Print("ALERT: root AWS file, profile, endpoint and trust-store environment overrides ignored")
}
}
diff --git a/internal/rootaws/environment_test.go b/internal/rootaws/environment_test.go
index 574556a81..1f56a3d98 100644
--- a/internal/rootaws/environment_test.go
+++ b/internal/rootaws/environment_test.go
@@ -24,6 +24,8 @@ func TestScrubEnvironment(t *testing.T) {
for _, key := range rootScrubbedEnvironment {
t.Setenv(key, "untrusted")
}
+ t.Setenv("AWS_ENDPOINT_URL_S3", "untrusted")
+ t.Setenv("AWS_ENDPOINT_URL_STS", "untrusted")
t.Setenv("AWS_ACCESS_KEY_ID", "KEEP")
t.Setenv("AWS_SECRET_ACCESS_KEY", "KEEP_SECRET")
t.Setenv("AWS_SESSION_TOKEN", "KEEP_TOKEN")
@@ -45,6 +47,11 @@ func TestScrubEnvironment(t *testing.T) {
t.Errorf("%s = %q, set=%v; want %q", key, got, set, want)
}
}
+ for _, key := range []string{"AWS_ENDPOINT_URL_S3", "AWS_ENDPOINT_URL_STS"} {
+ if _, set := os.LookupEnv(key); set != (tc.euid != 0) {
+ t.Errorf("%s set=%v for euid %d", key, set, tc.euid)
+ }
+ }
wantLogs := 0
if tc.euid == 0 {
wantLogs = 1