Check Amazon Inspector Is Enabled in Every Region

A magnifying glass held over a green electronic circuit board

Photo by Vishnu Mohanan on Unsplash

To check Amazon Inspector is enabled in all Regions, call BatchGetAccountStatus in each Region your account uses and read the status of each scan type: EC2, ECR, Lambda standard and Lambda code. Inspector is a Regional service, so one Region showing ENABLED says nothing about the others, and each scan type can be on or off on its own.

Inspector scans EC2 instances, container images in ECR and Lambda functions for known vulnerabilities, but only in the Regions where you activated it and only for the scan types you turned on. A team that activated it in its home Region during a security review often has workloads in two more Regions that nobody scans.

This example is for security and platform engineers who want one table of Inspector status across every enabled Region, with coverage counts and the number of critical and high findings, and an opt-in way to close the gaps. It follows the same pattern as the checks to check GuardDuty is enabled in all Regions and check Security Hub is enabled in every Region.

What does each Amazon Inspector scan type cover?

Scan type (resourceTypes) What it scans Notes
EC2 OS and language packages, plus network reachability Agent-based through SSM Agent on managed instances; hybrid mode adds agentless scans from EBS snapshots for unmanaged ones
ECR Container images in your private registry Switches the registry from ECR basic scanning to enhanced scanning
LAMBDA Package dependencies of functions invoked or updated in the last 90 days The Lambda standard scan
LAMBDA_CODE Your function code itself Needs Lambda standard scanning turned on first

Inspector Code Security for source repositories is a separate scan type and isn’t covered by this script.

How does Inspector relate to ECR basic scanning?

Activating Inspector’s ECR scan type sets Inspector as the scanning service for your private registry, replacing basic scanning with enhanced scanning. Basic scanning is provided and billed through ECR; enhanced scanning through Inspector. If you only use basic scan-on-push today, the script to enable ECR image scanning on every repository compares the two and what enhanced scanning costs.

How is Amazon Inspector priced?

Inspector bills per scan type and per Region, and each scan type gets a 15-day free trial when you first activate it. The trial expires after 15 days even if you turn the scan type off again. The billing dimensions are:

  • EC2: the average number of instances covered over 30 days.
  • ECR: the number of initial image scans plus rescans, which happen when new CVEs affect an image.
  • Lambda standard and Lambda code: the average number of functions covered over 30 days, each billed separately.

Rates vary by Region, so this article doesn’t quote them. The Inspector console’s Usage page projects a 30-day cost per account from real coverage, as described in Monitoring usage and cost in Amazon Inspector; check it at the end of each trial before deciding to keep a scan type on.

What does the script do?

  1. Lists RegionsDescribeRegions returns the Regions enabled for the account, or you pass --regions.
  2. Reads account statusBatchGetAccountStatus returns the account state and a resourceState per scan type: ENABLED, ENABLING, DISABLED, DISABLING, SUSPENDED or SUSPENDING.
  3. Counts coverageListCoverageStatistics grouped by resource type, once for scan status ACTIVE and once for INACTIVE, shows resources scanned and resources Inspector sees but can’t scan.
  4. Counts findingsListFindingAggregations with the ACCOUNT aggregation type returns critical and high severity counts.
  5. Enables, if askedWith --apply, calls Enable with the missing types from --types (default EC2,ECR,LAMBDA) in each Region, adding LAMBDA whenever you ask for LAMBDA_CODE.

Prerequisites

Which IAM permissions does it need?

The first statement is enough for the report. The other two are only for --apply: the first activation in an account creates Inspector’s service-linked roles.

inspector-check-policy.json

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "ReportInspectorStatus",
      "Effect": "Allow",
      "Action": [
        "ec2:DescribeRegions",
        "inspector2:BatchGetAccountStatus",
        "inspector2:ListCoverageStatistics",
        "inspector2:ListFindingAggregations"
      ],
      "Resource": "*"
    },
    {
      "Sid": "EnableOnlyWithApply",
      "Effect": "Allow",
      "Action": "inspector2:Enable",
      "Resource": "*"
    },
    {
      "Sid": "InspectorServiceLinkedRoles",
      "Effect": "Allow",
      "Action": "iam:CreateServiceLinkedRole",
      "Resource": "*",
      "Condition": {
        "StringEquals": {
          "iam:AWSServiceName": ["inspector2.amazonaws.com", "agentless.inspector2.amazonaws.com"]
        }
      }
    }
  ]
}

The IAM policy generator for TypeScript SDK code produces a starting point like this from any script you adapt.

The script to check Amazon Inspector is enabled in all Regions

check-amazon-inspector-enabled.ts

// check-amazon-inspector-enabled.ts
// For every enabled Region (or --regions): which Amazon Inspector scan types are on for this account
// (EC2, ECR, Lambda standard, Lambda code), how many resources are covered or not, and the account's
// critical and high finding counts. Changes nothing unless you pass --apply, which enables the scan types
// in --types (default EC2,ECR,LAMBDA) in every Region where any of them is off.
// Usage:
//   npx tsx check-amazon-inspector-enabled.ts [--regions us-east-1,eu-west-1] [--apply] [--types EC2,ECR,LAMBDA,LAMBDA_CODE]
import { EC2Client, DescribeRegionsCommand } from "@aws-sdk/client-ec2";
import {
  Inspector2Client,
  BatchGetAccountStatusCommand,
  EnableCommand,
  ListFindingAggregationsCommand,
  paginateListCoverageStatistics,
  type ResourceScanType,
  type ResourceState,
} from "@aws-sdk/client-inspector2";

const args = process.argv.slice(2);
const flag = (name: string): string | undefined => {
  const i = args.indexOf(name);
  return i >= 0 ? args[i + 1] : undefined;
};
const apply = args.includes("--apply");
const TYPES: ResourceScanType[] = ["EC2", "ECR", "LAMBDA", "LAMBDA_CODE"];
const wanted = (flag("--types") ?? "EC2,ECR,LAMBDA")
  .split(",")
  .map((t) => t.trim().toUpperCase())
  .filter((t): t is ResourceScanType => (TYPES as string[]).includes(t));

interface Row {
  Region: string;
  Account: string;
  EC2: string;
  ECR: string;
  Lambda: string;
  LambdaCode: string;
  Covered: string;
  NotCovered: string;
  Critical: string;
  High: string;
}

const errName = (err: unknown): string => (err instanceof Error ? err.name : String(err));
const STATE_KEY: Record<ResourceScanType, keyof ResourceState> = {
  EC2: "ec2",
  ECR: "ecr",
  LAMBDA: "lambda",
  LAMBDA_CODE: "lambdaCode",
  CODE_REPOSITORY: "codeRepository",
};

async function listRegions(): Promise<string[]> {
  const arg = flag("--regions");
  if (arg) return arg.split(",").map((r) => r.trim()).filter(Boolean);
  const out = await new EC2Client({}).send(new DescribeRegionsCommand({})); // Regions enabled for the account
  return (out.Regions ?? []).map((r) => r.RegionName ?? "").filter(Boolean).sort();
}

// Sums coverage statistics grouped by resource type; scanStatusCode ACTIVE means the resource is being scanned.
async function coverage(client: Inspector2Client, code: "ACTIVE" | "INACTIVE"): Promise<string> {
  const parts: string[] = [];
  const pages = paginateListCoverageStatistics(
    { client },
    { groupBy: "RESOURCE_TYPE", filterCriteria: { scanStatusCode: [{ comparison: "EQUALS", value: code }] } },
  );
  for await (const page of pages) {
    for (const c of page.countsByGroup ?? []) {
      if (c.count) parts.push(`${c.groupKey ?? "?"}=${c.count}`);
    }
  }
  return parts.join(" ") || "0";
}

async function severityCounts(client: Inspector2Client): Promise<{ critical: string; high: string }> {
  const out = await client.send(new ListFindingAggregationsCommand({ aggregationType: "ACCOUNT" }));
  const counts = out.responses?.[0]?.accountAggregation?.severityCounts;
  return { critical: String(counts?.critical ?? 0), high: String(counts?.high ?? 0) };
}

async function checkRegion(region: string): Promise<{ row: Row; missing: ResourceScanType[] }> {
  const client = new Inspector2Client({ region });
  const status = await client.send(new BatchGetAccountStatusCommand({}));
  const account = status.accounts?.[0];
  const state = account?.resourceState;
  const show = (t: ResourceScanType) => state?.[STATE_KEY[t]]?.status ?? "DISABLED";
  const missing = wanted.filter((t) => show(t) !== "ENABLED" && show(t) !== "ENABLING");
  const anyOn = TYPES.some((t) => show(t) === "ENABLED");
  const findings = anyOn ? await severityCounts(client) : { critical: "-", high: "-" };
  return {
    missing,
    row: {
      Region: region,
      Account: account?.state?.status ?? "?",
      EC2: show("EC2"),
      ECR: show("ECR"),
      Lambda: show("LAMBDA"),
      LambdaCode: show("LAMBDA_CODE"),
      Covered: anyOn ? await coverage(client, "ACTIVE") : "-",
      NotCovered: anyOn ? await coverage(client, "INACTIVE") : "-",
      Critical: findings.critical,
      High: findings.high,
    },
  };
}

async function main(): Promise<void> {
  const regions = await listRegions();
  const rows: Row[] = [];
  const gaps: { region: string; types: ResourceScanType[] }[] = [];
  for (const region of regions) {
    try {
      const { row, missing } = await checkRegion(region);
      rows.push(row);
      if (missing.length) gaps.push({ region, types: missing });
    } catch (err) {
      rows.push({ Region: region, Account: `ERROR ${errName(err)}`, EC2: "", ECR: "", Lambda: "", LambdaCode: "", Covered: "", NotCovered: "", Critical: "", High: "" });
    }
  }
  console.table(rows);
  console.log(`${gaps.length} of ${regions.length} Regions missing one of ${wanted.join(", ")}: ${gaps.map((g) => g.region).join(", ") || "none"}`);

  if (!apply) {
    console.log("Report only: nothing was modified. Add --apply to enable the missing scan types.");
    return;
  }
  for (const gap of gaps) {
    // Lambda code scanning needs Lambda standard scanning, so enable LAMBDA whenever LAMBDA_CODE is requested.
    const types = gap.types.includes("LAMBDA_CODE") && !gap.types.includes("LAMBDA")
      ? gap.types.flatMap((t): ResourceScanType[] => (t === "LAMBDA_CODE" ? ["LAMBDA", "LAMBDA_CODE"] : [t]))
      : gap.types;
    try {
      const out = await new Inspector2Client({ region: gap.region }).send(new EnableCommand({ resourceTypes: types }));
      const failed = out.failedAccounts ?? [];
      if (failed.length) {
        console.error(`${gap.region}: ${failed.map((f) => `${f.errorCode}: ${f.errorMessage}`).join("; ")}`);
        process.exitCode = 1;
      } else {
        console.log(`${gap.region}: enabling ${types.join(", ")}`);
      }
    } catch (err) {
      console.error(`${gap.region}: ${errName(err)} ${err instanceof Error ? err.message : ""}`);
      process.exitCode = 1;
    }
  }
}

main().catch((err) => {
  console.error(err instanceof Error ? `${err.name}: ${err.message}` : String(err));
  process.exit(1);
});

How do you run it?

Terminal

npm install @aws-sdk/client-inspector2 @aws-sdk/client-ec2
npm install --save-dev tsx typescript @types/node

# Report on every enabled Region
AWS_PROFILE=security-audit npx tsx check-amazon-inspector-enabled.ts

# Enable EC2, ECR and Lambda standard scanning where any of them is off
AWS_PROFILE=security-admin npx tsx check-amazon-inspector-enabled.ts --apply --types EC2,ECR,LAMBDA

Sample output

Output (with –apply)

┌─────────┬──────────────┬────────────┬────────────┬────────────┬────────────┬────────────┬────────────────────────────────────────────────────────────────────────┬──────────────────────┬──────────┬──────┐
│ (index) │ Region       │ Account    │ EC2        │ ECR        │ Lambda     │ LambdaCode │ Covered                                                                │ NotCovered           │ Critical │ High │
├─────────┼──────────────┼────────────┼────────────┼────────────┼────────────┼────────────┼────────────────────────────────────────────────────────────────────────┼──────────────────────┼──────────┼──────┤
│ 0       │ 'ap-south-1' │ 'DISABLED' │ 'DISABLED' │ 'DISABLED' │ 'DISABLED' │ 'DISABLED' │ '-'                                                                    │ '-'                  │ '-'      │ '-'  │
│ 1       │ 'eu-west-1'  │ 'ENABLED'  │ 'ENABLED'  │ 'DISABLED' │ 'DISABLED' │ 'DISABLED' │ 'AWS_EC2_INSTANCE=6'                                                   │ 'AWS_EC2_INSTANCE=2' │ '1'      │ '9'  │
│ 2       │ 'us-east-1'  │ 'ENABLED'  │ 'ENABLED'  │ 'ENABLED'  │ 'ENABLED'  │ 'DISABLED' │ 'AWS_EC2_INSTANCE=14 AWS_ECR_CONTAINER_IMAGE=38 AWS_LAMBDA_FUNCTION=8' │ 'AWS_EC2_INSTANCE=3' │ '4'      │ '27' │
└─────────┴──────────────┴────────────┴────────────┴────────────┴────────────┴────────────┴────────────────────────────────────────────────────────────────────────┴──────────────────────┴──────────┴──────┘
2 of 3 Regions missing one of EC2, ECR, LAMBDA: ap-south-1, eu-west-1
ap-south-1: enabling EC2, ECR, LAMBDA
eu-west-1: enabling ECR, LAMBDA

Values are illustrative. ap-south-1 had nothing on, and eu-west-1 scanned only EC2, so its container images and functions had no coverage. In us-east-1, three instances are INACTIVE: usually unmanaged instances that don’t qualify for agentless scans, or instances excluded with the InspectorEc2Exclusion tag. The four critical findings are the next thing to open.

Troubleshooting

  • A Region shows ERROR AccessDeniedException. The profile lacks an Inspector permission there, or a service control policy blocks the Region. Check the SCP before the IAM policy.
  • --apply reports BLOCKED_BY_ORGANIZATION_POLICY. An AWS Organizations policy manages Inspector for this account; member accounts can’t change policy-managed scan types. Enable them from the delegated administrator instead.
  • A scan type stays ENABLING. Activation is asynchronous. Run the report again a few minutes later.
  • EC2 instances stay INACTIVE. Make them SSM managed instances, or use hybrid scan mode for EBS-backed instances with supported file systems.

In an organization, Inspector is usually run from a delegated administrator that enables it for member accounts. This script checks one account; run it with a role in each account, or check from the administrator. For the rest of the Regional baseline, check CloudTrail is logging in all Regions and check AWS Config is recording in every Region. Old Lambda runtimes also show up as Inspector findings; the script to find Lambda functions on deprecated runtimes lists them directly. Sensitive data in S3 has the same per-Region gap; the script to check Amazon Macie is enabled in every Region covers it.

Ask ChatWithCloud instead

To check a single Region, ask ChatWithCloud “Is Amazon Inspector enabled in eu-west-1, and for which scan types?” It writes AWS SDK for JavaScript v2 code, runs it with your profile on your machine and explains the answer, one profile and Region per session, so a full sweep is quicker with the script. SDK v2 reached end of support in September 2025, so recent Inspector fields may be missing. Analyze your AWS security posture with an AI CLI has more questions of this kind, and connect ChatWithCloud to a read-only AWS profile first, since it runs changes without confirmation.

Frequently asked questions

How do I check Amazon Inspector is enabled in all Regions?

Call BatchGetAccountStatus in every enabled Region, or run aws inspector2 batch-get-account-status --region <region> in a loop, and read the status of each scan type in resourceState.

Is Amazon Inspector enabled per Region?

Yes. Activation and each scan type apply to one Region at a time; the console asks you to repeat activation in each Region you use.

Does enabling Amazon Inspector turn off ECR basic scanning?

Activating Inspector’s ECR scan type switches your private registry from basic scanning to enhanced scanning, which Inspector provides and bills.

Is there a free trial for Amazon Inspector?

Yes. Each scan type has a 15-day free trial that starts when you first activate it and ends after 15 days even if you turn it off. CIS scans aren’t included.

Related guides

Ask your AWS account in plain English

Your first 15 runs are free, with no OpenAI key needed.

npx chatwithcloud