We read configuration. Never your data.
A security tool asking for access to your cloud has to earn it. This page lists exactly what Basalt reads, what it keeps, and what it will never do, so your team can check it line by line.
Read-only, enforced in code
The AWS connector only allows a fixed list of operations. Anything else, including data reads like s3:GetObject, is blocked before the request leaves the scanner. An automated test checks the scanner never calls anything outside that list.
Temporary credentials
We assume a role you create, with a unique ExternalId per customer against the confused-deputy problem. Credentials come from STS and expire in one hour. No access keys are stored.
Secret values never stored
Lambda and ECS return environment values with their configuration. We classify each one in memory (looks like a secret: yes or no) and drop it. A test plants fake secrets and checks they appear in no output.
Every read logged
Each API call of a scan is recorded with service, operation, region and time, and kept with the scan, so we can show you exactly what was read in your account.
Code is parsed, never run
Repositories are analysed from their syntax tree. Nothing from your code is imported or executed on our side.
Isolation between customers
Every customer row in our database is protected by Postgres row-level security bound to the workspace, on top of the checks in the application.
The exact AWS permissions
Fourteen configuration reads, plus an explicit deny on data reads as a second guarantee, even if the role is later given more access by mistake.
| Action | Why |
|---|---|
iam:GetAccountAuthorizationDetails | Roles, inline and managed policies, trust relationships |
iam:GetPolicy, iam:GetPolicyVersion | Content of AWS-managed policies attached to your roles |
s3:ListAllMyBuckets | List of buckets |
s3:GetBucketPolicy, s3:GetBucketPublicAccessBlock | Whether a bucket is public |
s3:GetBucketTagging | Sensitivity from your own tags |
dynamodb:ListTables | Table names, no items |
secretsmanager:ListSecrets | Secret names and tags, never values |
lambda:ListFunctions, lambda:GetFunctionUrlConfig | Functions, their role, public URLs |
ecs:ListTaskDefinitionFamilies, ecs:DescribeTaskDefinition | Task roles and environment variable names |
ec2:DescribeInstances | Instance profiles (roles) of instances |
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "BasaltReadConfiguration",
"Effect": "Allow",
"Action": [
"iam:GetAccountAuthorizationDetails",
"iam:GetPolicy",
"iam:GetPolicyVersion",
"s3:ListAllMyBuckets",
"s3:GetBucketPolicy",
"s3:GetBucketPublicAccessBlock",
"s3:GetBucketTagging",
"dynamodb:ListTables",
"secretsmanager:ListSecrets",
"lambda:ListFunctions",
"lambda:GetFunctionUrlConfig",
"ecs:ListTaskDefinitionFamilies",
"ecs:DescribeTaskDefinition",
"ec2:DescribeInstances"
],
"Resource": "*"
},
{
"Sid": "BasaltNeverReadsData",
"Effect": "Deny",
"Action": [
"s3:GetObject*",
"secretsmanager:GetSecretValue",
"dynamodb:GetItem",
"dynamodb:BatchGetItem",
"dynamodb:Query",
"dynamodb:Scan",
"ssm:GetParameter*",
"kms:Decrypt",
"rds-data:ExecuteStatement"
],
"Resource": "*"
}
]
}
{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Principal": { "AWS": "arn:aws:iam::<BASALT_ACCOUNT_ID>:root" },
"Action": "sts:AssumeRole",
"Condition": { "StringEquals": { "sts:ExternalId": "<YOUR_EXTERNAL_ID>" } }
}]
}
Prefer not to share environment values at all? Remove lambda:ListFunctions and ecs:DescribeTaskDefinition. The scan still runs without the compute analysis, and the report lists the missing permission.
What we keep
Only what the analysis needs to show you a path and prove it.
| Data | Kept |
|---|---|
| Resource names and ARNs, roles, policies, trust relationships | Yes |
| The graph: components and what each can reach | Yes |
| Evidence: file path, line number, resource | Yes |
| Secret and environment variable names | Names only |
| Secret values, environment values, keys and tokens | Never |
| Objects, rows or items from your data stores | Never read |
| AWS access keys | Never; temporary STS credentials only |
Run it on your side
The scanner works without our cloud. Run the CLI against a repository or Terraform, or against AWS with your own credentials and the same read-only lock. Nothing leaves your machine unless you link it to a workspace.
# same allow-list, your own credentials
basalt scan-aws --use-current-credentials --profile auditKnown limits
We publish what the analysis does not cover yet. When one of these could change a result, the step is marked uncertain instead of reported with high confidence.
- Organization SCPs, session policies and most resource policies are not evaluated yet (public bucket policies are).
- Block Public Access is checked per bucket, not at account level.
- Lambda function URLs are checked on
$LATESTonly.
About us, plainly
We are an early-stage company. Here is where we stand.
Do you have SOC 2?
Not yet. We plan SOC 2 Type I as our first enterprise customers need it. Until then, we answer your vendor questionnaire in full and share how each control works today.
Which subprocessors handle our data?
AI features such as the terminal assistant use the Claude API from Anthropic, which does not train models on API data under its commercial terms. Our hosting and database providers are listed here before customer data is stored with them.
How long do you keep scan results?
For as long as your workspace exists, and we delete them on request. Shorter retention can be set in your agreement.
Will Basalt ever change our infrastructure?
No. We never write to your cloud account. Fixes are code changes, applied on a branch by the CLI only after you approve the diff, and never on your default branch.
Found a vulnerability in Basalt?
Tell us privately and we will confirm receipt within two business days. Please give us a reasonable time to fix it before you publish. We won't take action against good-faith research.