The rule

Access and continuity

You create the credentials: read-only, MFA, expiring on a date you set.

Boundary

The boundary

Access boundary
You grantNever received
Read on the billing exportsObject contents, rows, backups, secrets
Resource metadata: type, region, size, tagspods/log, pods/exec, configmaps, secrets
Token and GPU meteringPrompt and completion payloads, embeddings
Repositories you invite us toDefault-branch write. Changes arrive as pull requests

Continuity

Continuity

No named substitute engineer, and none claimed.

Continuity
TriggerAutomatically
From day oneEvery artifact lives in your account
Unavailable 5 business daysClocks pause, retainer months prorate
You terminate on itUnearned fees refunded pro rata

AWS

AWS policy

Data-plane reads denied, including bedrock:InvokeModel.

Trust policy: MFA, external ID, expiry
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "AssumeOnlyWithMfaExternalIdAndAnExpiry",
      "Effect": "Allow",
      "Principal": { "AWS": "arn:aws:iam::ENGINEER-ACCOUNT-ID:root" },
      "Action": "sts:AssumeRole",
      "Condition": {
        "Bool":         { "aws:MultiFactorAuthPresent": "true" },
        "StringEquals": { "sts:ExternalId": "ONE-TIME-VALUE-YOU-GENERATE" },
        "DateLessThan": { "aws:CurrentTime": "2026-09-30T00:00:00Z" }
      }
    }
  ]
}
Permissions policy
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "CurBucketRead",
      "Effect": "Allow",
      "Action": ["s3:ListBucket", "s3:GetObject"],
      "Resource": [
        "arn:aws:s3:::YOUR-CUR-BUCKET",
        "arn:aws:s3:::YOUR-CUR-BUCKET/cur2/*",
        "arn:aws:s3:::YOUR-CUR-BUCKET/focus/*"
      ]
    },
    {
      "Sid": "AthenaAgainstTheCurDatabaseOnly",
      "Effect": "Allow",
      "Action": [
        "athena:StartQueryExecution",
        "athena:GetQueryExecution",
        "athena:GetQueryResults",
        "athena:GetWorkGroup",
        "glue:GetDatabase",
        "glue:GetTable",
        "glue:GetTables",
        "glue:GetPartitions"
      ],
      "Resource": "*"
    },
    {
      "Sid": "BillingRatesAndCommitmentPosture",
      "Effect": "Allow",
      "Action": [
        "ce:GetCostAndUsage",
        "ce:GetCostAndUsageWithResources",
        "ce:GetSavingsPlansUtilization",
        "ce:GetSavingsPlansCoverage",
        "ce:GetReservationUtilization",
        "ce:GetReservationCoverage",
        "savingsplans:DescribeSavingsPlans",
        "pricing:GetProducts"
      ],
      "Resource": "*"
    },
    {
      "Sid": "TagAndResourceMetadataRead",
      "Effect": "Allow",
      "Action": [
        "tag:GetResources",
        "tag:GetTagKeys",
        "tag:GetTagValues",
        "organizations:ListAccounts",
        "organizations:ListTagsForResource",
        "cloudwatch:ListMetrics",
        "cloudwatch:GetMetricData",
        "compute-optimizer:GetEC2InstanceRecommendations",
        "eks:ListClusters",
        "eks:DescribeCluster",
        "eks:ListNodegroups",
        "eks:DescribeNodegroup",
        "bedrock:ListFoundationModels",
        "bedrock:GetModelInvocationLoggingConfiguration"
      ],
      "Resource": "*"
    },
    {
      "Sid": "DenyEveryDataPlaneReadIncludingModelCalls",
      "Effect": "Deny",
      "Action": [
        "s3:GetObject",
        "secretsmanager:GetSecretValue",
        "ssm:GetParameter",
        "ssm:GetParameters",
        "dynamodb:GetItem",
        "dynamodb:Query",
        "dynamodb:Scan",
        "rds-data:ExecuteStatement",
        "kms:Decrypt",
        "logs:GetLogEvents",
        "logs:FilterLogEvents",
        "bedrock:InvokeModel",
        "bedrock:InvokeModelWithResponseStream",
        "bedrock:Converse",
        "bedrock:ConverseStream"
      ],
      "NotResource": [
        "arn:aws:s3:::YOUR-CUR-BUCKET/cur2/*",
        "arn:aws:s3:::YOUR-CUR-BUCKET/focus/*"
      ]
    }
  ]
}

Attach AWSBillingReadOnlyAccess alongside.

Clusters

Read-only cluster access

apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: cost-mechanic-read
rules:
  # Capacity, requests and ownership. No pods/log, no pods/exec, no secrets.
  - apiGroups: [""]
    resources: ["nodes", "namespaces", "pods", "persistentvolumeclaims",
                "resourcequotas", "limitranges", "services"]
    verbs: ["get", "list"]
  - apiGroups: ["apps"]
    resources: ["deployments", "statefulsets", "daemonsets", "replicasets"]
    verbs: ["get", "list"]
  - apiGroups: ["batch"]
    resources: ["jobs", "cronjobs"]
    verbs: ["get", "list"]
  - apiGroups: ["metrics.k8s.io"]
    resources: ["nodes", "pods"]
    verbs: ["get", "list"]
  - apiGroups: ["autoscaling", "karpenter.sh"]
    resources: ["horizontalpodautoscalers", "nodepools", "nodeclaims"]
    verbs: ["get", "list"]

Azure

Azure roles, at the billing scope

# 1. Cost Management Reader at the billing scope, not the subscription.
#    Unused reservation charges land on the billing profile, so subscription
#    scope alone hides reservation waste.
az role assignment create \
  --assignee-object-id "$OBJECT_ID" --assignee-principal-type User \
  --role "Cost Management Reader" \
  --scope "/providers/Microsoft.Billing/billingAccounts/$BILLING_ACCOUNT"

# 2. Reader on the subscriptions in scope. Reader cannot write and cannot
#    read data-plane contents.
az role assignment create \
  --assignee-object-id "$OBJECT_ID" --assignee-principal-type User \
  --role "Reader" --scope "/subscriptions/$SUBSCRIPTION_ID"

# 3. Read on the container the Cost Management export writes to. Both
#    datasets: amortized AND actual.
az role assignment create \
  --assignee-object-id "$OBJECT_ID" --assignee-principal-type User \
  --role "Storage Blob Data Reader" \
  --scope "/subscriptions/$SUBSCRIPTION_ID/resourceGroups/$RG\
/providers/Microsoft.Storage/storageAccounts/$SA/blobServices/default\
/containers/$EXPORT_CONTAINER"

# 4. AKS: cluster user + RBAC Reader. Reader-only kubeconfig, never admin.
az role assignment create \
  --assignee-object-id "$OBJECT_ID" --assignee-principal-type User \
  --role "Azure Kubernetes Service Cluster User Role" \
  --scope "$AKS_RESOURCE_ID"
az role assignment create \
  --assignee-object-id "$OBJECT_ID" --assignee-principal-type User \
  --role "Azure Kubernetes Service RBAC Reader" --scope "$AKS_RESOURCE_ID"

# 5. Azure OpenAI: metrics only. Monitoring Reader reads
#    ProcessedPromptTokens and GeneratedTokens. It cannot call a deployment.
#    Cognitive Services User is NOT requested and must not be granted.
az role assignment create \
  --assignee-object-id "$OBJECT_ID" --assignee-principal-type User \
  --role "Monitoring Reader" --scope "$AOAI_RESOURCE_ID"

GCP

GCP bindings, time-bound

# A time-bound condition on every binding. Set the date to the last day of
# the engagement window; the binding stops working on its own.
COND='expression=request.time < timestamp("2026-09-30T00:00:00Z"),title=engagement-window'

# 1. Billing account viewer: rates, invoices, commitment inventory.
gcloud beta billing accounts add-iam-policy-binding "$BILLING_ACCOUNT" \
  --member="user:$ENGINEER" --role="roles/billing.viewer"

# 2. Read on the detailed usage export dataset only, not the whole project.
bq add-iam-policy-binding \
  --member="user:$ENGINEER" --role="roles/bigquery.dataViewer" \
  "$PROJECT:billing_export"

# 3. Permission to run the queries, scoped by condition.
gcloud projects add-iam-policy-binding "$PROJECT" \
  --member="user:$ENGINEER" --role="roles/bigquery.jobUser" --condition="$COND"

# 4. GKE, Vertex AI and metrics: viewer roles, no data-plane access.
gcloud projects add-iam-policy-binding "$PROJECT" \
  --member="user:$ENGINEER" --role="roles/container.clusterViewer" --condition="$COND"
gcloud projects add-iam-policy-binding "$PROJECT" \
  --member="user:$ENGINEER" --role="roles/aiplatform.viewer" --condition="$COND"
gcloud projects add-iam-policy-binding "$PROJECT" \
  --member="user:$ENGINEER" --role="roles/monitoring.viewer" --condition="$COND"

# 5. Revoke, on the last day. Run this yourself and confirm it returned clean.
gcloud projects get-iam-policy "$PROJECT" \
  --flatten="bindings[].members" --filter="bindings.members:$ENGINEER" \
  --format="table(bindings.role)"

Providers

Metering keys, not inference keys

SOC 2

SOC 2: none

None this year, either. If your review cannot waive it, we are the wrong fit.

Absent, not pending: SOC 2, ISO 27001, penetration test, insurance certificate, named substitute engineer.

The access appendix: grants by cloud, retention, insurance, compensating controls, capacity.

Next