{"record":{"id":"3777ef0a4067aed8","repo":"elastic/elasticsearch","slug":"sts-assumerolewithwebidentity-response-did-not-inc","errorCode":null,"errorMessage":"STS AssumeRoleWithWebIdentity response did not include credentials","messagePattern":"STS AssumeRoleWithWebIdentity response did not include credentials","errorType":"exception","errorClass":"IllegalStateException","httpStatus":null,"severity":"error","filePath":"libs/workload-identity-aws/src/main/java/org/elasticsearch/workload/identity/aws/AsyncWebIdentityCredentialsProvider.java","lineNumber":198,"sourceCode":"    private CompletableFuture<String> requestToken() {\n        CompletableFuture<String> future = new CompletableFuture<>();\n        ActionListener.run(ActionListener.<String>wrap(future::complete, future::completeExceptionally), tokenSupplier::accept);\n        return future;\n    }\n\n    private CompletableFuture<AssumeRoleWithWebIdentityResponse> requestCredentials(String token) {\n        AssumeRoleWithWebIdentityRequest request = AssumeRoleWithWebIdentityRequest.builder()\n            .roleArn(roleArn)\n            .roleSessionName(roleSessionName)\n            .webIdentityToken(token)\n            .build();\n        return stsAsyncClient.assumeRoleWithWebIdentity(request);\n    }\n\n    private Cached toCached(AssumeRoleWithWebIdentityResponse response) {\n        Credentials credentials = response.credentials();\n        if (credentials == null) {\n            throw new IllegalStateException(\"STS AssumeRoleWithWebIdentity response did not include credentials\");\n        }\n        Instant expiry = credentials.expiration();\n        if (expiry == null) {\n            throw new IllegalStateException(\"STS AssumeRoleWithWebIdentity response did not include a credential expiry\");\n        }\n        Instant now = clock.instant();\n        if (expiry.isAfter(now) == false) {\n            throw new IllegalStateException(\"STS returned credentials that are already expired at [\" + expiry + \"] (now [\" + now + \"])\");\n        }\n        AwsSessionCredentials sessionCredentials = AwsSessionCredentials.builder()\n            .accessKeyId(credentials.accessKeyId())\n            .secretAccessKey(credentials.secretAccessKey())\n            .sessionToken(credentials.sessionToken())\n            .expirationTime(expiry)\n            .build();\n        return new Cached(sessionCredentials, expiry.minus(prefetchTime), expiry.minus(staleTime));\n    }\n","sourceCodeStart":180,"sourceCodeEnd":216,"githubUrl":"https://github.com/elastic/elasticsearch/blob/db6a809a667c081ca1dc7500389d26975573215f/libs/workload-identity-aws/src/main/java/org/elasticsearch/workload/identity/aws/AsyncWebIdentityCredentialsProvider.java#L180-L216","documentation":"toCached converts the STS AssumeRoleWithWebIdentityResponse into a Cached entry. AWS SDK v2 allows response.credentials() to be null on a malformed or partial response; the provider treats a null Credentials object as an illegal state (not a retryable client error).","triggerScenarios":"requestCredentials returns a response whose credentials() is null. This is reached after the async STS call completes and the future is being unwrapped.","commonSituations":"STS returned an error-shaped response without an exception (rare); a misbehaving test double returns a response with no Credentials; an SDK version mismatch where the response shape changed; IAM/permissions edge cases where STS acknowledges the call but returns an empty credentials block.","solutions":["Inspect the full STS response logging / SDK metrics to see why Credentials was absent","Verify the IAM trust policy on the role permits sts:AssumeRoleWithWebIdentity for the caller","If using a test stub, ensure the mocked response includes a non-null Credentials","File an issue if STS genuinely returns 200 with null Credentials - that indicates an upstream fault"],"exampleFix":"// before (mock)\nwhen(sts.assumeRoleWithWebIdentity(req)).thenReturn(AssumeRoleWithWebIdentityResponse.builder().build());\n// after\nwhen(sts.assumeRoleWithWebIdentity(req)).thenReturn(\n    AssumeRoleWithWebIdentityResponse.builder()\n        .credentials(Credentials.builder().accessKeyId(\"k\").secretAccessKey(\"s\").sessionToken(\"t\").expiration(Instant.now().plusSeconds(900)).build())\n        .build());","handlingStrategy":"try-catch","validationCode":"AssumeRoleWithWebIdentityResponse resp = requestCredentials(token).join();\nif (resp.credentials() == null) {\n    throw new IllegalStateException(\"STS returned no credentials for role \" + roleArn);\n}","typeGuard":"static boolean hasCredentials(AssumeRoleWithWebIdentityResponse r) {\n    return r != null && r.credentials() != null;\n}","tryCatchPattern":"try { toCached(resp); }\ncatch (IllegalStateException e) { /* log + surface as auth failure */ }","preventionTips":["Log the full STS response code/message when Credentials is absent","Validate IAM trust policy in preflight","In tests, always populate Credentials"],"tags":["aws","sts","credentials","runtime"],"backgroundTag":null,"analyzedSha":"db6a809a667c081ca1dc7500389d26975573215f","analyzedAt":"2026-08-12T01:39:14.192Z","schemaVersion":2},"datasetVersion":"2026-08-12T13:17:24.610Z"}