Discovery scans require AWS credentials to query Resource Explorer and describe live resources. By default the SDK uses the AWS SDK v3 default credential chain, so you do not configure credentials in your code. Every discovery method also accepts explicit credentials for one call, covered in Scoping credentials per call.
Credential resolution order
The SDK resolves credentials in this order, stopping at the first match:
- Environment variables —
AWS_ACCESS_KEY_ID,AWS_SECRET_ACCESS_KEY, and optionallyAWS_SESSION_TOKEN - Shared credentials file —
~/.aws/credentials, profile selected viaAWS_PROFILE - SSO credentials — configured via
aws sso login - IAM instance profile / ECS task role — when running on EC2 or in an ECS task
- EC2 instance metadata (IMDS) — when running on EC2
Region resolution
Region resolves in this order:
AWS_REGIONenvironment variableAWS_DEFAULT_REGIONenvironment variable- AWS SDK chain (shared config file, instance metadata)
Using environment variables
export AWS_ACCESS_KEY_ID=AKIA...
export AWS_SECRET_ACCESS_KEY=...
export AWS_REGION=us-east-1
Using AWS profiles
export AWS_PROFILE=my-profile
Then run your SDK code. The SDK picks up the profile automatically.
For SSO profiles:
aws sso login --profile my-sso-profile
export AWS_PROFILE=my-sso-profile
Using IAM roles
When running in AWS (EC2, ECS, Lambda, CodeBuild), attach an IAM role to the compute resource. The SDK picks up the role credentials from the instance metadata or task role endpoint with no additional configuration.
Minimum IAM permissions
For discovery scans with catalog-backed rules, which includes the AWS Core preset, the IAM principal needs read-only access to Resource Explorer, STS for account identity, and the services you want to scan. A run that enables only account-scoped rules, such as the Cost Optimization Hub rules, never queries Resource Explorer and needs only STS plus the services those rules read. At minimum for a catalog-backed run:
{
"Effect": "Allow",
"Action": [
"resource-explorer-2:Search",
"resource-explorer-2:GetView",
"resource-explorer-2:ListViews",
"resource-explorer-2:ListIndexes",
"sts:GetCallerIdentity",
"ec2:Describe*",
"rds:Describe*",
"elasticache:Describe*"
],
"Resource": "*"
}
sts:GetCallerIdentity resolves the signing account once per run. CloudBurn uses that account to scope shared AWS request quotas and, when evidence reuse is enabled, to scope cached evidence. If the lookup fails, collectors fall back to isolated in-memory request limits for that run.
Rules that read account-level AWS services need their own read permissions on Resource: "*". Several AWS Core rules do, for example the Cost Explorer, Cost Guardrails, and CloudTrail rules, so grant the read actions for every service whose rules stay enabled; a denied call is reported as an access diagnostic for that dataset instead of a result. The opt-in Cost Optimization Hub rules additionally need cost-optimization-hub:ListEnrollmentStatuses, cost-optimization-hub:ListRecommendations, and cost-optimization-hub:GetRecommendation. CloudBurn only reads enrollment status; it never enrolls an account or changes a resource.
For initializeDiscovery(), additional write permissions are required:
{
"Effect": "Allow",
"Action": [
"resource-explorer-2:CreateIndex",
"resource-explorer-2:CreateView",
"resource-explorer-2:AssociateDefaultView",
"iam:CreateServiceLinkedRole"
],
"Resource": "*"
}
Scoping credentials per call
By default, discover() resolves credentials from the ambient AWS SDK v3 credential chain described above. Pass options.aws.credentials to use specific credentials for one call instead. This is useful when a single process scans multiple AWS accounts, such as assumed roles in a multi-account organization.
The example below uses fromTemporaryCredentials from @aws-sdk/credential-providers, which is not a dependency of @cloudburn/sdk. Install it in your project first (npm install @aws-sdk/credential-providers):
import { CloudBurnClient } from '@cloudburn/sdk';
import { fromTemporaryCredentials } from '@aws-sdk/credential-providers';
const client = new CloudBurnClient();
const accountRoleArns = [
'arn:aws:iam::111111111111:role/cloudburn-readonly',
'arn:aws:iam::222222222222:role/cloudburn-readonly',
];
for (const roleArn of accountRoleArns) {
const result = await client.discover({
target: { mode: 'current' },
aws: { credentials: fromTemporaryCredentials({ params: { RoleArn: roleArn } }) },
});
console.log(`${roleArn}: ${result.providers.flatMap((p) => p.rules).length} rule groups with findings`);
}
options.aws.credentials accepts anything typed AwsClientCredentials: a static AwsCredentialIdentity object or an AwsCredentialIdentityProvider function, both re-exported from @aws-sdk/types. All four discovery methods accept it, since they share the same AwsDiscoveryExecutionOptions:
const auditRoleArn = 'arn:aws:iam::111111111111:role/cloudburn-readonly';
const status = await client.getDiscoveryStatus({
aws: { credentials: fromTemporaryCredentials({ params: { RoleArn: auditRoleArn } }) },
});
To scope credentials across code that calls multiple SDK methods without repeating the option, wrap the calls in withAwsClientCredentials():
import { withAwsClientCredentials } from '@cloudburn/sdk';
const result = await withAwsClientCredentials(credentials, async () => {
await client.initializeDiscovery();
return client.discover({ target: { mode: 'current' } });
});
See withAwsClientCredentials for the full reference.
Credentials and evidence reuse
Whose credentials ran a scan is part of the identity of any evidence it collected, so discover({ cache }) will not reuse evidence across credential scopes it cannot prove are equivalent.
- Temporary credentials (assumed roles, SSO, IMDS) carry a session token. The SDK derives a session-specific reuse scope from it, so distinct sessions and distinct session policies never share evidence.
- Long-term credentials (static IAM access keys) carry no session token. They reuse nothing unless you pass
cache.authorizationContext, because an account or role ARN alone cannot prove that effective permissions are unchanged.
authorizationContext is an opaque revision string you control, for example 'production-readonly-policy-v3'. Change it whenever effective permissions or relevant execution conditions change, and the next scan collects fresh evidence instead of reusing evidence gathered under the old permissions. An empty or whitespace-only value throws.
Expired credentials reject the scan rather than reusing evidence. Every scan also revalidates the caller identity and the current Resource Explorer view before reuse.
const result = await client.discover({
aws: { credentials: staticIamCredentials },
cache: {
directory: '/home/example/.cache/cloudburn/evidence',
authorizationContext: 'production-readonly-policy-v3',
},
});
See AwsEvidenceCacheOptions for the rest of the cache contract.
Handling access errors
If the SDK cannot access certain services or regions, it records them as diagnostics rather than failing the scan:
const result = await client.discover();
for (const diag of result.diagnostics ?? []) {
if (diag.status === 'access_denied') {
console.warn(`No access to ${diag.service} in ${diag.region}: ${diag.message}`);
}
}
This lets you get results for what you can access while identifying gaps in permissions.
When an AWS error names the source of a denial, the diagnostic message says so instead of reporting generic missing permissions: it names a service control policy (SCP) or a resource-based policy. That tells you whether to fix the principal's IAM policy or escalate to whoever owns the organization or resource policy.
A dataset whose regional loads were all denied is marked unavailable. Rules that depend on it are then skipped, with a status: 'skipped' diagnostic naming the rule and the missing datasets, rather than reported as passing against an empty dataset.
What's next
| Quickstart: Discovery Scan | Run your first discovery scan |
| CloudBurnClient Reference | initializeDiscovery() and discover() reference |
| Types Reference | ScanDiagnostic and AwsDiscoveryExecutionOptions |