From b8aff967a8060a3920931e5df785c0585f4fab3b Mon Sep 17 00:00:00 2001 From: Agasthi Kothurkar Date: Sat, 6 Jun 2026 09:38:19 -0400 Subject: [PATCH 01/30] Add multi-region scanning support with parallel execution MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Enable security assessments to scan multiple AWS regions in parallel using a Step Functions Map state. Each region gets its own Lambda invocation for Bedrock, SageMaker, and AgentCore assessments, providing linear performance scaling regardless of region count. Configuration: - Add TargetRegions CloudFormation parameter to all deployment templates (empty = single region, comma-separated list, or 'all') - Add ResolveRegions Lambda to resolve target regions for Map state - Use ItemSelector on Map state to inject region into each iteration Assessment changes: - Add Region field to all finding schemas and CSV reports - Refactor all assessment Lambdas to accept region from event payload and create boto3 clients with explicit region_name - Add graceful service-unavailability handling (N/A finding when a service is not available in a region) - Include region in S3 report filenames for per-region CSV outputs Report changes: - Add Region column, filter dropdown, and Risk by Region section to HTML report template - Update consolidate_html_reports.py to pass region data through Infrastructure: - Restructure Step Functions state machine with Map state - Fail CodeBuild when Step Function execution fails (previously only logged a warning) Documentation: - Streamline README (603 → 208 lines) by moving cleanup and troubleshooting content to dedicated docs - Add docs/CLEANUP.md with step-by-step removal instructions - Add upgrade guide and stack identification to Troubleshooting - Update Developer Guide with region-aware patterns - Correct check count from 52 to 51 (13 Bedrock + 25 SageMaker + 13 AgentCore) --- README.md | 505 ++-------- .../security/agentcore_assessments/app.py | 110 ++- .../security/agentcore_assessments/schema.py | 8 +- .../security/bedrock_assessments/app.py | 192 +++- .../security/bedrock_assessments/schema.py | 8 +- .../generate_consolidated_report/app.py | 5 + .../report_template.py | 153 ++- .../generate_consolidated_report/schema.py | 19 +- .../functions/security/resolve_regions/app.py | 61 ++ .../security/sagemaker_assessments/app.py | 308 ++++-- .../security/sagemaker_assessments/schema.py | 8 +- .../statemachine/assessments.asl.json | 233 +++-- .../template-multi-account.yaml | 34 +- aiml-security-assessment/template.yaml | 38 +- buildspec.yml | 30 +- consolidate_html_reports.py | 6 + deployment/2-aiml-security-codebuild.yaml | 12 + deployment/aiml-security-single-account.yaml | 12 + docs/CLEANUP.md | 105 +++ docs/DEVELOPER_GUIDE.md | 111 ++- docs/TROUBLESHOOTING.md | 32 + ...curity_assessment_multi_region_sample.html | 873 ++++++++++++++++++ 22 files changed, 2117 insertions(+), 746 deletions(-) create mode 100644 aiml-security-assessment/functions/security/resolve_regions/app.py create mode 100644 docs/CLEANUP.md create mode 100644 sample-reports/security_assessment_multi_region_sample.html diff --git a/README.md b/README.md index 78173da..e4da99d 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ **Open-source automated security scanner for Amazon Bedrock, Amazon SageMaker AI, and Amazon Bedrock AgentCore** — Built on [AWS Well-Architected Framework (Generative AI Lens)](https://docs.aws.amazon.com/wellarchitected/latest/generative-ai-lens/generative-ai-lens.html) -Cloud security automation with **[52 security checks](docs/SECURITY_CHECKS.md)** for your generative AI and machine learning workloads. Identify IAM misconfigurations, encryption gaps, network isolation issues, and compliance violations with interactive HTML reports and actionable remediation guidance. +Cloud security automation with **[51 security checks](docs/SECURITY_CHECKS.md)** for your generative AI and machine learning workloads. Identify IAM misconfigurations, encryption gaps, network isolation issues, and compliance violations with interactive HTML reports and actionable remediation guidance. --- @@ -35,83 +35,17 @@ The framework generates professional, interactive security assessment reports wi ### Key Features -- **Executive Summary** with severity counts and service breakdown -- **Priority Recommendations** highlighting critical issues requiring immediate attention -- **[52 Security Checks](docs/SECURITY_CHECKS.md)** across Amazon Bedrock, Amazon SageMaker AI, and Amazon Bedrock AgentCore -- **Interactive Filtering** by account, service, severity, and status -- **Light/Dark Mode Toggle** with persistent user preference -- **Text Search** across all findings with real-time results -- **Direct AWS Documentation Links** for each finding with remediation guidance -- **Multi-Account Support** with consolidated reporting across your organization -- **Fully Automated** deployment and execution through AWS CloudFormation and AWS CodeBuild - ---- - -## Table of Contents - -- [What It Does](#what-it-does) -- [Why Use This Framework?](#why-use-this-framework) -- [Scope and Limitations](#scope-and-limitations) -- [Quick Start](#quick-start) -- [Architecture](#architecture) -- [Prerequisites](#prerequisites) -- [Single-Account Deployment](#single-account-deployment) -- [Multi-Account Deployment](#multi-account-deployment) -- [How It Works](#how-it-works) -- [Permissions Required](#permissions-required) -- [Viewing Assessment Results](#viewing-assessment-results) -- [Customization](#customization) -- [Cleanup](#cleanup) -- [Documentation](#documentation) -- [Contributing](#contributing) -- [Security](#security) -- [License](#license) - ---- - -## What It Does - -This serverless assessment framework automatically evaluates your AI/ML workloads against AWS security best practices. It uses AWS serverless services to gather data from the control plane and generate reports containing the status of various security checks, severity levels, and recommended actions. - -Designed for workloads using [Amazon Bedrock](https://aws.amazon.com/bedrock/), [Amazon Bedrock AgentCore](https://aws.github.io/bedrock-agentcore-starter-toolkit/), or [Amazon SageMaker AI](https://aws.amazon.com/sagemaker/ai/). - -### Why Use This Framework? - -| Challenge | How This Framework Helps | -|-----------|-------------------------| -| **Manual security audits are time-consuming** | Fully automated scanning with one-click CloudFormation deployment | -| **Inconsistent security checks across teams** | Standardized 52-check assessment based on AWS Well-Architected best practices | -| **Difficulty tracking AI/ML security posture** | Interactive HTML dashboards with severity breakdown and per-account visibility | -| **Multi-account complexity** | Consolidated reporting across AWS Organizations with cross-account role assumption | -| **Compliance and audit support** | Exportable reports to supplement your compliance program, with remediation guidance linked to AWS documentation | -| **Generative AI security gaps** | Purpose-built checks for LLM guardrails, model access controls, and prompt injection prevention | +- **[51 Security Checks](docs/SECURITY_CHECKS.md)** across Amazon Bedrock, Amazon SageMaker AI, and Amazon Bedrock AgentCore +- **Multi-Region Support** — parallel scanning across AWS regions with per-region risk breakdown +- **Multi-Account Support** — consolidated reporting across AWS Organizations +- **Interactive Filtering** by account, region, service, severity, and status +- **Light/Dark Mode** with persistent user preference +- **Fully Automated** — one-click CloudFormation deployment and execution **Services Covered:** -- **[Amazon Bedrock](docs/SECURITY_CHECKS.md#amazon-bedrock-security-checks-14)** (14 checks) - Guardrails, encryption, Amazon VPC endpoints, AWS IAM permissions, model invocation logging -- **[Amazon SageMaker AI](docs/SECURITY_CHECKS.md#amazon-sagemaker-ai-security-checks-25)** (25 checks) - AWS Security Hub controls (SageMaker.1-5), encryption, network isolation, AWS IAM, MLOps -- **[Amazon Bedrock AgentCore](docs/SECURITY_CHECKS.md#amazon-bedrock-agentcore-security-checks-13)** (13 checks) - Amazon VPC configuration, encryption, observability, resource policies - -**Deployment Options:** -- **Single-Account**: Assess security in one AWS account -- **Multi-Account**: Scan entire AWS Organizations with consolidated reporting - -**How It Works:** -1. Deploy through AWS CloudFormation (one-click deployment) -2. Framework automatically scans your AI/ML resources -3. Generates interactive HTML reports stored in your Amazon S3 bucket -4. All data stays in your AWS account - no external dependencies - ---- - -## Scope and Limitations - -This tool operates within the [AWS Shared Responsibility Model](https://aws.amazon.com/compliance/shared-responsibility-model/). It assesses **your configuration responsibilities** (IAM policies, encryption settings, network isolation, logging) for AI/ML services. It does not assess AWS-managed infrastructure, physical security, or the underlying service platform. - -**Point-in-time assessment.** Each run captures your security posture at the moment of execution. Resource configurations can change immediately after an assessment completes. Run assessments regularly and after significant changes to maintain visibility. - -**No guarantee of security or compliance.** This framework identifies common misconfigurations based on AWS best practices and the AWS Well-Architected Framework. It does not cover all possible security risks, does not replace formal compliance audits (SOC 2, HIPAA, and similar), and does not guarantee that your workloads are secure. Use the results as one input into your broader security program. - -**52 checks across three services.** The assessment covers Amazon Bedrock, Amazon SageMaker AI, and Amazon Bedrock AgentCore. Other AI/ML services (Amazon Comprehend, Amazon Rekognition, Amazon Textract, and others) are not currently assessed. +- **[Amazon Bedrock](docs/SECURITY_CHECKS.md#amazon-bedrock-security-checks-13)** (13 checks) — Guardrails, encryption, VPC endpoints, IAM, model invocation logging +- **[Amazon SageMaker AI](docs/SECURITY_CHECKS.md#amazon-sagemaker-ai-security-checks-25)** (25 checks) — Security Hub controls, encryption, network isolation, IAM, MLOps +- **[Amazon Bedrock AgentCore](docs/SECURITY_CHECKS.md#amazon-bedrock-agentcore-security-checks-13)** (13 checks) — VPC configuration, encryption, observability, resource policies --- @@ -126,371 +60,113 @@ This tool operates within the [AWS Shared Responsibility Model](https://aws.amaz ## Prerequisites -- Python 3.12+ - [Install Python](https://www.python.org/downloads/) -- AWS SAM CLI - [Install the AWS SAM CLI](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-install.html) -- Docker (optional) - [Install Docker community edition](https://hub.docker.com/search/?type=edition&offering=community) - Only required for local development and testing, not for AWS deployment +- Python 3.12+ — [Install Python](https://www.python.org/downloads/) +- AWS SAM CLI — [Install the AWS SAM CLI](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-install.html) +- Docker (optional) — [Install Docker](https://hub.docker.com/search/?type=edition&offering=community) — Only required for local development + +--- ## Single-Account Deployment -1. Download the [aiml-security-single-account.yaml](deployment/aiml-security-single-account.yaml) AWS CloudFormation template. +1. Download the [aiml-security-single-account.yaml](deployment/aiml-security-single-account.yaml) CloudFormation template. 2. **[Deploy to AWS CloudFormation](https://console.aws.amazon.com/cloudformation/home#/stacks/create/template?stackName=aiml-security-single-account)** -3. Upload the AWS CloudFormation template from step 1. -4. Provide a stack name and optionally specify your email address to receive notifications. -5. Leave all other parameters at their default values. -6. Navigate to the next page, read and acknowledge the notice, and click **Next**. -7. Review the information and click **Submit**. -8. Wait for the AWS CloudFormation stack to complete. -9. Once complete, AWS CodeBuild automatically deploys the assessment stack and runs the assessment. -10. To view results: - - Navigate to the AWS CloudFormation console - - Open the stack you deployed (for example, `aiml-security-single-account` or your custom name) - - Go to the **Outputs** tab - - Copy the `AssessmentBucket` value - - Navigate to that Amazon S3 bucket and open the `{account_id}/security_assessment_*.html` file - -### Understanding Stack Names - -> **Important**: The deployment creates **TWO** AWS CloudFormation stacks. Only one contains your results! +3. Upload the template and provide a stack name. +4. Optionally specify your email address to receive notifications. +5. **(Optional) Multi-Region**: Set `TargetRegions` to scan multiple regions: + - Leave empty to scan only the deployment region (default) + - Comma-separated list (for example, `us-east-1,us-west-2,eu-west-1`) + - `all` to scan all regions where the services are available +6. Acknowledge IAM capabilities and click **Submit**. +7. Once complete, CodeBuild automatically runs the assessment. +8. View results: go to the stack **Outputs** tab → copy `AssessmentBucket` → open the HTML report in that S3 bucket. + +> **Tip**: The deployment creates two stacks. Your results are in the stack *you named*, not the auto-generated `aiml-sec-*` stack. See [Troubleshooting](docs/TROUBLESHOOTING.md#8-confused-by-multiple-cloudformation-stacks) for details. - - - - - - - - - - - - - - - - - - - -
Stack TypeHow to IdentifyWhat It ContainsWhat to Do
Infrastructure Stack
(This is the one you need)
-The name you chose
-Examples:
- - my-aiml-assessment
- - aiml-security-prod
- - aiml-security-single-account -
-AWS CodeBuild project
-Amazon S3 bucket for results
-AWS IAM roles
-The "AssessmentBucket" output -
-Use this stack to view results!

-1. Open this stack in console
-2. Go to Outputs tab
-3. Copy AssessmentBucket value -
Assessment Stack
(Auto-generated - ignore this)
-Auto-generated name:
-Single-account: aiml-sec-{account_id}
-Multi-account: aiml-security-{account_id} per member account, plus aiml-security-mgmt for the management account
-Examples:
-aiml-sec-123456789012 (single)
-aiml-security-123456789012 (multi) -
-AWS Lambda functions
-AWS Step Functions
-Internal resources
-No outputs you need -
-Don't use this stack!

-It's for internal operations only.
-Created automatically by AWS CodeBuild. -
- -**Quick Check**: If you see a stack name starting with `aiml-sec-` or `aiml-security-` followed by numbers (or `aiml-security-mgmt`), that's an **auto-generated assessment stack**. Look for the stack name you originally chose during deployment. +--- ## Multi-Account Deployment -### Prerequisites - -- AWS Organizations setup with management account access or delegated administrator privileges. +### Step 1: Deploy Member Roles -The deployment follows a two-step approach: +Deploy [1-aiml-security-member-roles.yaml](deployment/1-aiml-security-member-roles.yaml) to all target accounts using CloudFormation StackSets with service-managed permissions. -### Step 1: Deploy Member Roles (AWS CloudFormation StackSets) - -Deploy [1-aiml-security-member-roles.yaml](deployment/1-aiml-security-member-roles.yaml) to all target accounts using AWS CloudFormation StackSets with service-managed permissions. - -#### AWS Console Deployment - -1. Navigate to **AWS CloudFormation** > **StackSets** in the management account -2. Click **Create StackSet** -3. Select **Upload a template file** and upload [1-aiml-security-member-roles.yaml](deployment/1-aiml-security-member-roles.yaml) -4. Enter a StackSet name (for example, `aiml-security-member-roles`) -5. Set the `ManagementAccountID` parameter to your management account ID -6. Under **Permissions**, select **Service-managed permissions** -7. Under **Deployment targets**, select the Organizational Units (OUs) containing your target accounts -8. Select **us-east-1** (or your target region) under **Specify regions** -9. Review and click **Submit** - -This uses AWS Organizations to deploy the member role to all accounts in the selected OUs. New accounts added to those OUs will automatically receive the role. +1. Navigate to **CloudFormation** > **StackSets** in the management account +2. Upload the template and set `ManagementAccountID` to your management account +3. Select **Service-managed permissions** and target your OUs +4. Select your target region and submit ### Step 2: Deploy Central Infrastructure -Deploy [2-aiml-security-codebuild.yaml](deployment/2-aiml-security-codebuild.yaml) in your central management account or delegated administrator member account. - -#### AWS Console Deployment - -1. Navigate to [AWS CloudFormation](https://console.aws.amazon.com/cloudformation/home#/stacks/create/template?stackName=aiml-security-multi-account) -2. Select **Upload a template file** and upload the [2-aiml-security-codebuild.yaml](deployment/2-aiml-security-codebuild.yaml) file. -3. Set the `MultiAccountScan` parameter to `true`. -4. Optionally, provide your email address in the `EmailAddress` parameter for completion notifications. -5. Leave the remaining parameters at their default values. -6. Navigate to the next page, read and acknowledge the notice, and click **Next**. -7. Review the information and click **Submit**. -8. Stack creation automatically triggers AWS CodeBuild, which deploys the assessment to each account and runs it. - -## How It Works - -### Single-Account Mode (`MultiAccountScan=false`) - -- Creates a local `AIMLSecurityMemberRole` -- Runs the assessment in the same account -- Uses a local Amazon S3 bucket for results - -### Multi-Account Mode (`MultiAccountScan=true`) - -- Lists all active accounts in AWS Organizations -- Assumes the `AIMLSecurityMemberRole` in each target account -- Deploys selected assessment modules in each account with a shared Amazon S3 bucket -- Executes AWS Step Functions for each deployed module in each account -- Consolidates results by assessment type in a central Amazon S3 bucket - -### Assessment Execution Process - -#### Automatic Trigger - -- The AWS CodeBuild project starts automatically after central stack creation -- An AWS Lambda trigger function initiates the assessment workflow +Deploy [2-aiml-security-codebuild.yaml](deployment/2-aiml-security-codebuild.yaml) in your management account. -#### Multi-Account Orchestration +1. Upload the template and set `MultiAccountScan` to `true` +2. Optionally set `TargetRegions` for multi-region scanning +3. Optionally provide an email address for notifications +4. Acknowledge IAM capabilities and submit +5. Stack creation automatically triggers the assessment across all accounts -1. **Account Discovery**: AWS CodeBuild queries AWS Organizations for active accounts -2. **Role Assumption**: Assumes `AIMLSecurityMemberRole` in each target account -3. **Module Deployment**: Deploys the AI/ML assessment module: - - Amazon Bedrock Assessment AWS Lambda - - Amazon SageMaker AI Assessment AWS Lambda - - Amazon Bedrock AgentCore Assessment AWS Lambda - - AWS IAM Permission Caching AWS Lambda - - Consolidated Report Generation AWS Lambda -4. **Assessment Execution**: AWS Step Functions orchestrate parallel AWS Lambda execution -5. **Results Collection**: Individual AWS Lambda functions store results in local Amazon S3 buckets -6. **Consolidation**: AWS CodeBuild collects and consolidates results from all accounts -7. **Reporting**: Generates multi-account HTML and CSV reports -8. **Notification**: Sends completion notification through Amazon SNS (if configured) - -## Permissions Required - -### Central Account Role (`MultiAccountCodeBuildRole`) - -- Assumes roles in member accounts -- Lists AWS Organizations accounts -- Deploys AWS CloudFormation/AWS SAM applications -- Executes AWS Step Functions -- Writes to the Amazon S3 bucket - -### Member Account Role (`AIMLSecurityMemberRole`) - -- Read-only access to AI/ML services (Amazon Bedrock, Amazon SageMaker AI, Amazon Bedrock AgentCore) -- AWS IAM read permissions for security assessment -- AWS CloudTrail, Amazon GuardDuty, and AWS Lambda read permissions -- Amazon VPC and Amazon EC2 read permissions -- Amazon ECR, Amazon CloudWatch Logs, and AWS X-Ray read permissions (for Amazon Bedrock AgentCore) +--- -## Monitoring and Results +## Multi-Region Scanning -- **Amazon S3 Bucket**: Central storage for all assessment results -- **Amazon CloudWatch Logs**: AWS CodeBuild execution logs -- **Amazon SNS Notifications**: Email alerts on completion/failure -- **Amazon EventBridge Rules**: Automated workflow triggers +Both deployment modes support scanning multiple AWS regions in parallel via the `TargetRegions` parameter: -## Viewing Assessment Results +| Value | Behavior | +|-------|----------| +| Empty (default) | Scans deployment region only — fully backward compatible | +| Comma-separated (for example, `us-east-1,us-west-2`) | Scans those regions in parallel | +| `all` | Discovers and scans all regions where assessed services are available | -You can check the AWS CodeBuild console to confirm the assessment completed successfully before accessing the results. +Scanning uses a Step Functions Map state, so multiple regions execute in parallel with no additional time cost. Services unavailable in a region produce an informational N/A finding. -### Accessing Results +The HTML report includes a Region column, filter dropdown, and "Risk by Region" summary. -1. **Find the Amazon S3 Bucket Name**: - - Navigate to **AWS CloudFormation** > **Stacks** in the AWS Console - - For single-account deployments using the standalone template (`aiml-security-single-account.yaml`), select the stack you deployed (for example, `aiml-security-single-account`) and find the `AssessmentBucket` output. Results are synced to this bucket under the `{account_id}/` prefix. - - For multi-account deployments, select the `aiml-security-multi-account` stack created in [Step 2: Deploy Central Infrastructure](#step-2-deploy-central-infrastructure) and find the `AssessmentBucket` output - - Go to the **Outputs** tab - - Copy the Amazon S3 bucket name +> **Upgrading an existing deployment?** See [Troubleshooting](docs/TROUBLESHOOTING.md#9-upgrading-an-existing-deployment-to-multi-region) — it's a simple stack parameter update with no teardown. - > **Note**: The deployment creates multiple Amazon S3 buckets. Only use the bucket from the `AssessmentBucket` output above. Other buckets (such as `aiml-sec-*-aimlassessmentbucket-*` from nested stacks or `aws-sam-cli-managed-*` for deployment artifacts) are for internal use and can be ignored. +--- -2. **Navigate to the Amazon S3 Bucket**: - - Go to **Amazon S3** in the AWS Console - - Search for and open your assessment bucket - - For single-account deployments, open the `security_assessment_XXXXX.html` report - - For multi-account deployments, follow the [Report Structure](#report-structure) guidance below +## How It Works -### Report Structure +1. **Deploy** — CloudFormation creates CodeBuild, S3, IAM roles, and a Lambda trigger +2. **CodeBuild runs** — builds and deploys the SAM assessment stack (per account in multi-account mode) +3. **Step Functions execute** — orchestrates: S3 cleanup → IAM permission caching → resolve regions → Map state fans out per-region assessments (Bedrock, SageMaker, AgentCore in parallel) → generate consolidated report +4. **Results** — HTML and CSV reports are stored in your S3 bucket -#### Consolidated Reports +For detailed architecture, execution flow, and extension guidance, see the [Developer Guide](docs/DEVELOPER_GUIDE.md). -- **Location**: `consolidated-reports/` folder in the bucket -- **Content**: Multi-account HTML report combining all account assessments -- **File Format**: `multi_account_report_YYYYMMDD_HHMMSS.html` -- **Features**: - - Executive summary with metrics (Total, High, Medium, Low severity counts) - - Service breakdown (Amazon Bedrock, Amazon SageMaker AI, Amazon Bedrock AgentCore) - - Priority recommendations - - Light/dark mode toggle (persists through localStorage) - - Dropdown filters for Account ID, Severity, Status - - Text search filter for findings - - "View Docs" buttons for reference links +--- -#### Individual Account Reports +## Viewing Results -- **Location**: Folders named with account IDs (for example, `123456789012/`) -- **Content**: Account-specific CSV and HTML files for AI/ML assessments -- **Files Include**: - - `bedrock_security_report_{execution_id}.csv` - Amazon Bedrock security assessment results - - `sagemaker_security_report_{execution_id}.csv` - Amazon SageMaker AI security assessment results - - `agentcore_security_report_{execution_id}.csv` - Amazon Bedrock AgentCore security assessment results - - `permissions_cache_{execution_id}.json` - IAM permissions cache - - `security_assessment_{timestamp}_{execution_id}.html` - Consolidated HTML report (same features as multi-account report) +1. Open your **infrastructure stack** in CloudFormation → **Outputs** tab → copy `AssessmentBucket` +2. Navigate to that S3 bucket +3. Open the `security_assessment_*.html` file (single-account) or check `consolidated-reports/` (multi-account) ### Understanding Results -| Severity | Description | -|----------|-------------| -| **High** | Critical security issues requiring immediate attention | -| **Medium** | Important security improvements recommended | -| **Low** | Minor optimizations suggested | -| **Informational** | Advisory information, no action required | -| **N/A** | Check not applicable (no resources to assess) | - -| Status | Description | -|--------|-------------| -| **Failed** | Security issue identified that requires remediation | -| **Passed** | Checked resources met the assessed best practice at time of scan | -| **N/A** | No resources exist to check (for example, no notebooks, no guardrails configured) | - -## Customization - -### Adding New Accounts - -#### Option A: AWS Console - -1. Navigate to **AWS CloudFormation** > **StackSets** -2. Select `aiml-security-member-roles` AWS CloudFormation StackSet -3. Click **Add stacks to StackSet** -4. Choose deployment targets: - - **Deploy to accounts**: Enter specific account IDs - - **Regions**: Select target regions -5. Review and click **Submit** - -### Modifying Assessment Scope - -To add or remove service permissions, edit the member role permissions in `1-aiml-security-member-roles.yaml`. - -### Concurrent Scanning - -Adjust the `ConcurrentAccountScans` parameter based on your organization size and cost considerations. - -## Cleanup +| Severity | Meaning | +|----------|---------| +| **High** | Critical — immediate action required | +| **Medium** | Important — should be addressed | +| **Low** | Minor — best practice optimization | +| **Informational** | Advisory — no action required | -### Single-Account Cleanup +| Status | Meaning | +|--------|---------| +| **Failed** | Security issue identified | +| **Passed** | Resource meets best practice | +| **N/A** | No resources to assess or service not available in region | -To remove all resources deployed for single-account assessment: - -1. **Delete the AWS SAM-deployed assessment stack**: - - Navigate to **AWS CloudFormation** > **Stacks** - - Select the `aiml-sec-{account_id}` stack (for example, `aiml-sec-123456789012`) - - Click **Delete** - - Wait for stack deletion to complete - -2. **Delete the AWS CodeBuild infrastructure stack**: - - Select the `aiml-security-single-account` stack (or your custom stack name) - - Click **Delete** - - Wait for stack deletion to complete - -3. **Clean up Amazon S3 buckets** (if stack deletion fails due to non-empty buckets): - ```bash - # Empty the assessment bucket - aws s3 rm s3:// --recursive - - # If versioning is enabled, delete version markers - aws s3api delete-objects --bucket --delete \ - "$(aws s3api list-object-versions --bucket \ - --query '{Objects: Versions[].{Key:Key,VersionId:VersionId}}')" - - # Delete the bucket - aws s3 rb s3:// - ``` - -### Multi-Account Cleanup - -To remove all resources deployed for multi-account assessment: - -1. **Delete AWS SAM-deployed stacks in each member account**: - - For each account that was scanned, navigate to **AWS CloudFormation** > **Stacks** - - Select the `aiml-security-{account_id}` stack (for example, `aiml-security-123456789012`) - - For the management account, select `aiml-security-mgmt` - - Click **Delete** - - Alternatively, use the AWS CLI to delete across accounts: - ```bash - # Assume role in member account and delete stack - aws cloudformation delete-stack --stack-name aiml-security- \ - --region - ``` - -2. **Delete the central AWS CodeBuild infrastructure stack**: - - In the management account, navigate to **AWS CloudFormation** > **Stacks** - - Select the `aiml-security-multi-account` stack - - Click **Delete** - - Wait for stack deletion to complete - -3. **Delete the AWS CloudFormation StackSet member roles**: - - Navigate to **AWS CloudFormation** > **StackSets** - - Select the `aiml-security-member-roles` AWS CloudFormation StackSet - - Click **Actions** > **Delete stacks from StackSet** - - Select all deployment targets (OUs or accounts) - - Wait for stack instances to be deleted - - Once all stack instances are removed, delete the AWS CloudFormation StackSet itself - -4. **Clean up Amazon S3 buckets** (if stack deletion fails due to non-empty buckets): - ```bash - # List and identify assessment buckets - aws s3 ls | grep aiml-security - - # Empty each bucket - aws s3 rm s3:// --recursive - - # Delete version markers if versioning was enabled - aws s3api delete-objects --bucket --delete \ - "$(aws s3api list-object-versions --bucket \ - --query '{Objects: Versions[].{Key:Key,VersionId:VersionId}}')" - - # Delete the bucket - aws s3 rb s3:// - ``` - -### Cleanup Order - -For a clean removal, delete resources in this order: - -1. **Assessment stacks** (auto-created by SAM): - - Single-account: `aiml-sec-{account_id}` (for example, `aiml-sec-123456789012`) - - Multi-account: `aiml-security-{account_id}` per member account, plus `aiml-security-mgmt` for management account - -2. **Infrastructure stack** (the stack you deployed manually): - - Single-account: Your chosen stack name (for example, `my-aiml-assessment`) - - Multi-account: `aiml-security-multi-account` or your chosen name +--- -3. AWS CloudFormation StackSet member roles (multi-account only) +## Customization -4. Any remaining Amazon S3 buckets manually +| Task | How | +|------|-----| +| Add new accounts | Add to StackSet deployment targets | +| Modify permissions scope | Edit `1-aiml-security-member-roles.yaml` | +| Adjust concurrency | Change `ConcurrentAccountScans` parameter | +| Add new service checks | See [Developer Guide](docs/DEVELOPER_GUIDE.md#adding-new-aiml-service-assessments) | --- @@ -498,9 +174,10 @@ For a clean removal, delete resources in this order: | Document | Description | |----------|-------------| -| [Security Checks Reference](docs/SECURITY_CHECKS.md) | Complete reference for all 52 security checks with severity levels | -| [Troubleshooting Guide](docs/TROUBLESHOOTING.md) | Common issues, debugging tips, and FAQ | -| [Developer Guide](docs/DEVELOPER_GUIDE.md) | Architecture details, adding custom checks, and contributing | +| [Security Checks Reference](docs/SECURITY_CHECKS.md) | Complete reference for all 52 security checks | +| [Troubleshooting Guide](docs/TROUBLESHOOTING.md) | Common issues, stack identification, upgrade guide, debugging | +| [Developer Guide](docs/DEVELOPER_GUIDE.md) | Architecture details, adding custom checks, contributing | +| [Cleanup Guide](docs/CLEANUP.md) | Step-by-step resource removal instructions | --- @@ -510,17 +187,17 @@ GitHub Actions workflows run automatically on pull requests and pushes to `main` | Workflow | Trigger | What It Checks | |----------|---------|----------------| -| **Python Code Quality** | PR | Runs `ruff check` and `ruff format --check` on changed Python files | +| **Python Code Quality** | PR | `ruff check` and `ruff format --check` on changed Python files | | **CloudFormation Lint** | PR | Validates deployment and SAM templates with `cfn-lint` | -| **SAM Validate & Build** | PR | Runs `sam validate --lint` and `sam build` on SAM templates | -| **ASH Security Scan** | PR | Scans changed files for secrets, dependency vulnerabilities, and IaC misconfigurations | -| **ASH Full Repository Scan** | Push to main, monthly | Full repository security scan with results uploaded as artifacts | +| **SAM Validate & Build** | PR | `sam validate --lint` and `sam build` on SAM templates | +| **ASH Security Scan** | PR | Scans for secrets, dependency vulnerabilities, and IaC misconfigurations | +| **ASH Full Repository Scan** | Push to main, monthly | Full repository security scan | --- ## Contributing -We welcome community contributions! Please see [Developer Guide](docs/DEVELOPER_GUIDE.md) for guidelines. +We welcome community contributions! See the [Developer Guide](docs/DEVELOPER_GUIDE.md) for guidelines. ## Security diff --git a/aiml-security-assessment/functions/security/agentcore_assessments/app.py b/aiml-security-assessment/functions/security/agentcore_assessments/app.py index b06b88f..23f427f 100644 --- a/aiml-security-assessment/functions/security/agentcore_assessments/app.py +++ b/aiml-security-assessment/functions/security/agentcore_assessments/app.py @@ -15,7 +15,7 @@ from datetime import datetime, timezone from typing import Dict, List, Any from botocore.config import Config -from botocore.exceptions import ClientError +from botocore.exceptions import ClientError, EndpointConnectionError from schema import create_finding, SeverityEnum, StatusEnum @@ -26,22 +26,17 @@ # Configure boto3 with adaptive retry mode boto3_config = Config(retries=dict(max_attempts=10, mode="adaptive")) -# Initialize AWS clients +# Initialize S3 client (always uses Lambda's region for bucket operations) s3_client = boto3.client("s3", config=boto3_config) -iam_client = boto3.client("iam", config=boto3_config) -ec2_client = boto3.client("ec2", config=boto3_config) -ecr_client = boto3.client("ecr", config=boto3_config) -logs_client = boto3.client("logs", config=boto3_config) -xray_client = boto3.client("xray", config=boto3_config) -cloudwatch_client = boto3.client("cloudwatch", config=boto3_config) - -# Initialize AgentCore client -try: - agentcore_client = boto3.client("bedrock-agentcore-control", config=boto3_config) - logger.info("Successfully initialized bedrock-agentcore-control client") -except Exception as e: - logger.warning(f"Failed to initialize bedrock-agentcore-control client: {e}") - agentcore_client = None + +# Regional clients — initialized in lambda_handler with target region +iam_client = None +ec2_client = None +ecr_client = None +logs_client = None +xray_client = None +cloudwatch_client = None +agentcore_client = None # Environment variables BUCKET_NAME = os.environ.get("AIML_ASSESSMENT_BUCKET_NAME") @@ -137,6 +132,7 @@ def generate_csv_report(findings: List[Dict[str, Any]]) -> str: "Reference", "Severity", "Status", + "Region", ], ) writer.writeheader() @@ -153,6 +149,7 @@ def generate_csv_report(findings: List[Dict[str, Any]]) -> str: "Reference", "Severity", "Status", + "Region", ], ) writer.writeheader() @@ -166,7 +163,7 @@ def generate_csv_report(findings: List[Dict[str, Any]]) -> str: return csv_content -def write_to_s3(execution_id: str, csv_content: str, bucket_name: str) -> str: +def write_to_s3(execution_id: str, csv_content: str, bucket_name: str, region: str = "") -> str: """ Upload CSV report to S3. @@ -174,6 +171,7 @@ def write_to_s3(execution_id: str, csv_content: str, bucket_name: str) -> str: execution_id: Unique execution identifier csv_content: CSV content to upload bucket_name: S3 bucket name + region: AWS region identifier for the report filename Returns: S3 URL of uploaded file @@ -182,7 +180,10 @@ def write_to_s3(execution_id: str, csv_content: str, bucket_name: str) -> str: Exception: If upload fails """ try: - key = f"agentcore_security_report_{execution_id}.csv" + if region: + key = f"agentcore_security_report_{execution_id}_{region}.csv" + else: + key = f"agentcore_security_report_{execution_id}.csv" s3_client.put_object( Bucket=bucket_name, @@ -2243,16 +2244,76 @@ def lambda_handler(event, context): Lambda handler for AgentCore security assessment. Args: - event: Lambda event containing execution_id + event: Lambda event containing execution_id and Region context: Lambda context Returns: Response with status and S3 URL """ - global start_time + global start_time, iam_client, ec2_client, ecr_client, logs_client + global xray_client, cloudwatch_client, agentcore_client start_time = time.time() try: + # Extract target region from Step Functions Map state + region = event.get("Region", os.environ.get("AWS_REGION", "us-east-1")) + logger.info(f"Scanning region: {region}") + + # Initialize regional clients + iam_client = boto3.client("iam", config=boto3_config) + ec2_client = boto3.client("ec2", config=boto3_config, region_name=region) + ecr_client = boto3.client("ecr", config=boto3_config, region_name=region) + logs_client = boto3.client("logs", config=boto3_config, region_name=region) + xray_client = boto3.client("xray", config=boto3_config, region_name=region) + cloudwatch_client = boto3.client("cloudwatch", config=boto3_config, region_name=region) + + try: + agentcore_client = boto3.client("bedrock-agentcore-control", config=boto3_config, region_name=region) + # Test service availability with a lightweight call + agentcore_client.list_agent_runtimes(maxResults=1) + logger.info("Successfully initialized bedrock-agentcore-control client") + except EndpointConnectionError: + logger.info(f"AgentCore service not available in region {region}, skipping") + agentcore_client = None + except ClientError as e: + if "Could not connect" in str(e): + logger.info(f"AgentCore service not available in region {region}, skipping") + agentcore_client = None + else: + # Service is available but returned an API error (e.g., access denied) — proceed + logger.info("AgentCore client initialized (API accessible)") + except Exception as e: + if "Could not connect to the endpoint URL" in str(e): + logger.info(f"AgentCore service not available in region {region}, skipping") + agentcore_client = None + else: + logger.warning(f"Failed to initialize bedrock-agentcore-control client: {e}") + agentcore_client = None + + # If AgentCore not available, produce a single N/A report and exit early + if agentcore_client is None: + execution_id = event.get("Execution", {}).get("Name", "unknown") + na_findings = [ + create_finding( + check_id="AC-00", + finding_name="AgentCore Service Availability", + finding_details=f"Amazon Bedrock AgentCore is not available in region {region}. No checks performed.", + resolution="No action required. AgentCore is not deployed in this region.", + reference="https://aws.github.io/bedrock-agentcore-starter-toolkit/", + severity=SeverityEnum.INFORMATIONAL, + status=StatusEnum.NA, + region=region, + ) + ] + for finding in na_findings: + finding["Region"] = region + csv_content = generate_csv_report(na_findings) + s3_url = write_to_s3(execution_id, csv_content, BUCKET_NAME, region=region) + return { + "statusCode": 200, + "body": json.dumps({"message": f"AgentCore not available in {region}", "s3_url": s3_url}), + } + # Extract execution ID execution_id = event.get("Execution", {}).get("Name", "unknown") logger.info( @@ -2310,7 +2371,6 @@ def lambda_handler(event, context): except Exception as e: logger.error(f"Error in check '{check_name}': {e}") - # Add error finding all_findings.append( create_finding( check_id="AC-00", @@ -2320,15 +2380,21 @@ def lambda_handler(event, context): reference="https://aws.github.io/bedrock-agentcore-starter-toolkit/", severity=SeverityEnum.HIGH, status=StatusEnum.FAILED, + region=region, ) ) + # Inject region into all findings that don't have it set + for finding in all_findings: + if not finding.get("Region"): + finding["Region"] = region + # Generate CSV report logger.info(f"Generating CSV report with {len(all_findings)} total findings") csv_content = generate_csv_report(all_findings) # Upload to S3 - s3_url = write_to_s3(execution_id, csv_content, BUCKET_NAME) + s3_url = write_to_s3(execution_id, csv_content, BUCKET_NAME, region=region) # Calculate execution metrics total_duration = time.time() - start_time diff --git a/aiml-security-assessment/functions/security/agentcore_assessments/schema.py b/aiml-security-assessment/functions/security/agentcore_assessments/schema.py index 229c163..56f63b5 100644 --- a/aiml-security-assessment/functions/security/agentcore_assessments/schema.py +++ b/aiml-security-assessment/functions/security/agentcore_assessments/schema.py @@ -24,6 +24,7 @@ class Finding(BaseModel): Reference: str = Field(..., description="Documentation reference URL") Severity: SeverityEnum = Field(..., description="Severity level of the finding") Status: StatusEnum = Field(..., description="Current status of the finding") + Region: str = Field(default="", description="AWS region where the finding was identified") @validator('Check_ID') def validate_check_id(cls, v): @@ -61,7 +62,8 @@ def create_finding( resolution: str, reference: str, severity: SeverityEnum, - status: StatusEnum + status: StatusEnum, + region: str = "" ) -> Dict[str, Any]: """ Create a validated finding object @@ -74,6 +76,7 @@ def create_finding( reference: Documentation URL severity: Severity level status: Current status + region: AWS region where the finding was identified Returns: Dict[str, Any]: Validated finding as dictionary @@ -88,6 +91,7 @@ def create_finding( Resolution=resolution, Reference=reference, Severity=severity, - Status=status + Status=status, + Region=region ) return dict(finding.model_dump()) # Convert to regular dictionary diff --git a/aiml-security-assessment/functions/security/bedrock_assessments/app.py b/aiml-security-assessment/functions/security/bedrock_assessments/app.py index 2f62915..c59fda7 100644 --- a/aiml-security-assessment/functions/security/bedrock_assessments/app.py +++ b/aiml-security-assessment/functions/security/bedrock_assessments/app.py @@ -7,7 +7,7 @@ from typing import Dict, List, Any, Optional from io import StringIO from botocore.config import Config -from botocore.exceptions import ClientError +from botocore.exceptions import ClientError, EndpointConnectionError import random import json from schema import create_finding @@ -79,7 +79,7 @@ def get_permissions_cache(execution_id: str) -> Optional[Dict[str, Any]]: return None -def check_marketplace_subscription_access(permission_cache) -> Dict[str, Any]: +def check_marketplace_subscription_access(permission_cache, region: str = "") -> Dict[str, Any]: logger.debug("Starting check for overly permissive Marketplace subscription access") try: findings = { @@ -165,6 +165,7 @@ def check_policy_for_subscription_access(policy_doc: Any) -> bool: reference="https://docs.aws.amazon.com/bedrock/latest/userguide/security-iam-awsmanpol.html#security-iam-awsmanpol-bedrock-marketplace", severity="High", status="Failed", + region=region, ) ) else: @@ -180,6 +181,7 @@ def check_policy_for_subscription_access(policy_doc: Any) -> bool: reference="https://docs.aws.amazon.com/bedrock/latest/userguide/security-iam-awsmanpol.html#security-iam-awsmanpol-bedrock-marketplace", severity="Medium", status="Passed", + region=region, ) ) @@ -202,6 +204,7 @@ def check_policy_for_subscription_access(policy_doc: Any) -> bool: reference="https://docs.aws.amazon.com/bedrock/latest/userguide/security.html", severity="High", status="Failed", + region=region, ) ], } @@ -443,7 +446,7 @@ def check_stale_bedrock_access(permission_cache) -> Dict[str, Any]: } -def check_bedrock_full_access_roles(permission_cache) -> Dict[str, Any]: +def check_bedrock_full_access_roles(permission_cache, region: str = "") -> Dict[str, Any]: """ Check for roles with AmazonBedrockFullAccess policy using cached permissions """ @@ -478,6 +481,7 @@ def check_bedrock_full_access_roles(permission_cache) -> Dict[str, Any]: reference="https://docs.aws.amazon.com/bedrock/latest/userguide/security_iam_id-based-policy-examples-agent.html#iam-agents-ex-all\nhttps://docs.aws.amazon.com/bedrock/latest/userguide/security_iam_id-based-policy-examples-br-studio.html", severity="High", status="Failed", + region=region, ) ) else: @@ -491,6 +495,7 @@ def check_bedrock_full_access_roles(permission_cache) -> Dict[str, Any]: reference="https://docs.aws.amazon.com/bedrock/latest/userguide/security_iam_id-based-policy-examples-agent.html#iam-agents-ex-all\nhttps://docs.aws.amazon.com/bedrock/latest/userguide/security_iam_id-based-policy-examples-br-studio.html", severity="High", status="Passed", + region=region, ) ) @@ -535,13 +540,13 @@ def get_role_usage(role_name: str) -> str: return result -def check_bedrock_vpc_endpoints() -> Dict[str, bool]: +def check_bedrock_vpc_endpoints(region: str = "") -> Dict[str, bool]: """ Check if any VPC has Bedrock VPC endpoints """ logger.debug("Checking for Bedrock VPC endpoints") try: - ec2_client = boto3.client("ec2", config=boto3_config) + ec2_client = boto3.client("ec2", config=boto3_config, region_name=region) bedrock_endpoints = [ "com.amazonaws.region.bedrock", @@ -551,8 +556,7 @@ def check_bedrock_vpc_endpoints() -> Dict[str, bool]: ] # Get current region - session = boto3.session.Session() - current_region = session.region_name + current_region = region logger.debug(f"Current region: {current_region}") # Get list of all VPCs @@ -661,7 +665,7 @@ def handle_aws_throttling(func, *args, **kwargs): raise -def check_bedrock_access_and_vpc_endpoints(permission_cache) -> Dict[str, Any]: +def check_bedrock_access_and_vpc_endpoints(permission_cache, region: str = "") -> Dict[str, Any]: logger.debug("Starting check for Bedrock access and VPC endpoints") try: findings = { @@ -686,7 +690,7 @@ def check_bedrock_access_and_vpc_endpoints(permission_cache) -> Dict[str, Any]: break if bedrock_access_found: - vpc_endpoint_check = check_bedrock_vpc_endpoints() + vpc_endpoint_check = check_bedrock_vpc_endpoints(region=region) if not vpc_endpoint_check["has_endpoints"]: findings["status"] = "WARN" @@ -708,6 +712,7 @@ def check_bedrock_access_and_vpc_endpoints(permission_cache) -> Dict[str, Any]: reference="https://docs.aws.amazon.com/bedrock/latest/userguide/vpc-interface-endpoints.html", severity="Medium", status="Failed", + region=region, ) ) else: @@ -728,6 +733,7 @@ def check_bedrock_access_and_vpc_endpoints(permission_cache) -> Dict[str, Any]: reference="https://docs.aws.amazon.com/bedrock/latest/userguide/vpc-interface-endpoints.html", severity="High", status="Passed", + region=region, ) ) else: @@ -752,12 +758,13 @@ def check_bedrock_access_and_vpc_endpoints(permission_cache) -> Dict[str, Any]: reference="https://docs.aws.amazon.com/bedrock/latest/userguide/security.html", severity="High", status="Failed", + region=region, ) ], } -def check_bedrock_guardrails() -> Dict[str, Any]: +def check_bedrock_guardrails(region: str = "") -> Dict[str, Any]: """ Check if Amazon Bedrock Guardrails are configured and being used """ @@ -770,7 +777,7 @@ def check_bedrock_guardrails() -> Dict[str, Any]: "csv_data": [], } - bedrock_client = boto3.client("bedrock", config=boto3_config) + bedrock_client = boto3.client("bedrock", config=boto3_config, region_name=region) try: # List all guardrails @@ -792,6 +799,7 @@ def check_bedrock_guardrails() -> Dict[str, Any]: reference="https://docs.aws.amazon.com/bedrock/latest/userguide/guardrails.html", severity="High", status="Passed", + region=region, ) ) else: @@ -806,6 +814,7 @@ def check_bedrock_guardrails() -> Dict[str, Any]: reference="https://docs.aws.amazon.com/bedrock/latest/userguide/guardrails.html", severity="Medium", status="Failed", + region=region, ) ) @@ -821,6 +830,7 @@ def check_bedrock_guardrails() -> Dict[str, Any]: reference="https://docs.aws.amazon.com/bedrock/latest/userguide/guardrails.html", severity="High", status="Failed", + region=region, ) ) @@ -841,12 +851,13 @@ def check_bedrock_guardrails() -> Dict[str, Any]: reference="https://docs.aws.amazon.com/bedrock/latest/userguide/security.html", severity="High", status="Failed", + region=region, ) ], } -def check_bedrock_logging_configuration() -> Dict[str, Any]: +def check_bedrock_logging_configuration(region: str = "") -> Dict[str, Any]: """ Check if model invocation logging is enabled for Amazon Bedrock """ @@ -859,7 +870,7 @@ def check_bedrock_logging_configuration() -> Dict[str, Any]: "csv_data": [], } - bedrock_client = boto3.client("bedrock", config=boto3_config) + bedrock_client = boto3.client("bedrock", config=boto3_config, region_name=region) try: # Get current logging configuration @@ -895,6 +906,7 @@ def check_bedrock_logging_configuration() -> Dict[str, Any]: reference="https://docs.aws.amazon.com/bedrock/latest/userguide/model-invocation-logging.html", severity="Medium", status="Passed", + region=region, ) ) else: @@ -909,6 +921,7 @@ def check_bedrock_logging_configuration() -> Dict[str, Any]: reference="https://docs.aws.amazon.com/bedrock/latest/userguide/model-invocation-logging.html", severity="Medium", status="Failed", + region=region, ) ) @@ -924,6 +937,7 @@ def check_bedrock_logging_configuration() -> Dict[str, Any]: reference="https://docs.aws.amazon.com/bedrock/latest/userguide/model-invocation-logging.html", severity="Medium", status="Failed", + region=region, ) ) @@ -946,12 +960,13 @@ def check_bedrock_logging_configuration() -> Dict[str, Any]: reference="https://docs.aws.amazon.com/bedrock/latest/userguide/security.html", severity="High", status="Failed", + region=region, ) ], } -def check_bedrock_cloudtrail_logging() -> Dict[str, Any]: +def check_bedrock_cloudtrail_logging(region: str = "") -> Dict[str, Any]: """ Check if CloudTrail is configured to log Amazon Bedrock API calls """ @@ -964,7 +979,7 @@ def check_bedrock_cloudtrail_logging() -> Dict[str, Any]: "csv_data": [], } - cloudtrail_client = boto3.client("cloudtrail", config=boto3_config) + cloudtrail_client = boto3.client("cloudtrail", config=boto3_config, region_name=region) try: # Get all trails @@ -1034,6 +1049,7 @@ def check_bedrock_cloudtrail_logging() -> Dict[str, Any]: reference="https://docs.aws.amazon.com/bedrock/latest/userguide/logging-using-cloudtrail.html", severity="Medium", status="Passed", + region=region, ) ) else: @@ -1052,6 +1068,7 @@ def check_bedrock_cloudtrail_logging() -> Dict[str, Any]: reference="https://docs.aws.amazon.com/bedrock/latest/userguide/logging-using-cloudtrail.html", severity="High", status="Failed", + region=region, ) ) @@ -1067,6 +1084,7 @@ def check_bedrock_cloudtrail_logging() -> Dict[str, Any]: reference="https://docs.aws.amazon.com/bedrock/latest/userguide/logging-using-cloudtrail.html", severity="High", status="Failed", + region=region, ) ) @@ -1089,12 +1107,13 @@ def check_bedrock_cloudtrail_logging() -> Dict[str, Any]: reference="https://docs.aws.amazon.com/bedrock/latest/userguide/security.html", severity="High", status="Failed", + region=region, ) ], } -def check_bedrock_prompt_management() -> Dict[str, Any]: +def check_bedrock_prompt_management(region: str = "") -> Dict[str, Any]: """ Check if Amazon Bedrock Prompt Management feature is being used """ @@ -1107,7 +1126,7 @@ def check_bedrock_prompt_management() -> Dict[str, Any]: "csv_data": [], } - bedrock_client = boto3.client("bedrock-agent", config=boto3_config) + bedrock_client = boto3.client("bedrock-agent", config=boto3_config, region_name=region) try: # List all prompts @@ -1131,6 +1150,7 @@ def check_bedrock_prompt_management() -> Dict[str, Any]: reference="https://docs.aws.amazon.com/bedrock/latest/userguide/prompt-management.html", severity="Low", status="Passed", + region=region, ) ) @@ -1159,6 +1179,7 @@ def check_bedrock_prompt_management() -> Dict[str, Any]: reference="https://docs.aws.amazon.com/bedrock/latest/userguide/prompt-management.html", severity="Low", status="Failed", + region=region, ) ) else: @@ -1177,6 +1198,7 @@ def check_bedrock_prompt_management() -> Dict[str, Any]: reference="https://docs.aws.amazon.com/bedrock/latest/userguide/prompt-management.html", severity="Informational", status="N/A", + region=region, ) ) @@ -1192,6 +1214,7 @@ def check_bedrock_prompt_management() -> Dict[str, Any]: reference="https://docs.aws.amazon.com/bedrock/latest/userguide/prompt-management.html", severity="High", status="Failed", + region=region, ) ) @@ -1214,12 +1237,13 @@ def check_bedrock_prompt_management() -> Dict[str, Any]: reference="https://docs.aws.amazon.com/bedrock/latest/userguide/security.html", severity="High", status="Failed", + region=region, ) ], } -def check_bedrock_knowledge_base_encryption() -> Dict[str, Any]: +def check_bedrock_knowledge_base_encryption(region: str = "") -> Dict[str, Any]: """ Check if Amazon Bedrock Knowledge Bases have proper encryption configured including customer-managed KMS keys for data at rest @@ -1233,7 +1257,7 @@ def check_bedrock_knowledge_base_encryption() -> Dict[str, Any]: "csv_data": [], } - bedrock_agent_client = boto3.client("bedrock-agent", config=boto3_config) + bedrock_agent_client = boto3.client("bedrock-agent", config=boto3_config, region_name=region) try: # List all knowledge bases @@ -1253,6 +1277,7 @@ def check_bedrock_knowledge_base_encryption() -> Dict[str, Any]: reference="https://docs.aws.amazon.com/bedrock/latest/userguide/encryption-kb.html", severity="Informational", status="N/A", + region=region, ) ) return findings @@ -1299,6 +1324,7 @@ def check_bedrock_knowledge_base_encryption() -> Dict[str, Any]: reference="https://docs.aws.amazon.com/bedrock/latest/userguide/encryption-kb.html", severity="Informational", status="N/A", + region=region, ) ) else: @@ -1311,6 +1337,7 @@ def check_bedrock_knowledge_base_encryption() -> Dict[str, Any]: reference="https://docs.aws.amazon.com/bedrock/latest/userguide/encryption-kb.html", severity="High", status="Passed", + region=region, ) ) @@ -1328,6 +1355,7 @@ def check_bedrock_knowledge_base_encryption() -> Dict[str, Any]: reference="https://docs.aws.amazon.com/bedrock/latest/userguide/encryption-kb.html", severity="High", status="Failed", + region=region, ) ) @@ -1350,12 +1378,13 @@ def check_bedrock_knowledge_base_encryption() -> Dict[str, Any]: reference="https://docs.aws.amazon.com/bedrock/latest/userguide/security.html", severity="High", status="Failed", + region=region, ) ], } -def check_bedrock_guardrail_iam_enforcement(permission_cache) -> Dict[str, Any]: +def check_bedrock_guardrail_iam_enforcement(permission_cache, region: str = "") -> Dict[str, Any]: """ Check if IAM policies enforce the use of specific guardrails via the bedrock:GuardrailIdentifier condition key @@ -1369,7 +1398,7 @@ def check_bedrock_guardrail_iam_enforcement(permission_cache) -> Dict[str, Any]: "csv_data": [], } - bedrock_client = boto3.client("bedrock", config=boto3_config) + bedrock_client = boto3.client("bedrock", config=boto3_config, region_name=region) # First check if any guardrails exist try: @@ -1386,6 +1415,7 @@ def check_bedrock_guardrail_iam_enforcement(permission_cache) -> Dict[str, Any]: reference="https://docs.aws.amazon.com/bedrock/latest/userguide/guardrails-permissions-id.html", severity="Informational", status="N/A", + region=region, ) ) return findings @@ -1485,6 +1515,7 @@ def check_bedrock_guardrail_iam_enforcement(permission_cache) -> Dict[str, Any]: reference="https://docs.aws.amazon.com/bedrock/latest/userguide/guardrails-permissions-id.html", severity="High", status="Failed", + region=region, ) ) else: @@ -1499,6 +1530,7 @@ def check_bedrock_guardrail_iam_enforcement(permission_cache) -> Dict[str, Any]: reference="https://docs.aws.amazon.com/bedrock/latest/userguide/guardrails-permissions-id.html", severity="Informational", status="N/A", + region=region, ) ) else: @@ -1512,6 +1544,7 @@ def check_bedrock_guardrail_iam_enforcement(permission_cache) -> Dict[str, Any]: reference="https://docs.aws.amazon.com/bedrock/latest/userguide/guardrails-permissions-id.html", severity="Medium", status="Passed", + region=region, ) ) @@ -1534,12 +1567,13 @@ def check_bedrock_guardrail_iam_enforcement(permission_cache) -> Dict[str, Any]: reference="https://docs.aws.amazon.com/bedrock/latest/userguide/security.html", severity="High", status="Failed", + region=region, ) ], } -def check_bedrock_custom_model_encryption() -> Dict[str, Any]: +def check_bedrock_custom_model_encryption(region: str = "") -> Dict[str, Any]: """ Check if custom/fine-tuned Bedrock models have proper encryption configured """ @@ -1552,7 +1586,7 @@ def check_bedrock_custom_model_encryption() -> Dict[str, Any]: "csv_data": [], } - bedrock_client = boto3.client("bedrock", config=boto3_config) + bedrock_client = boto3.client("bedrock", config=boto3_config, region_name=region) try: # List custom models @@ -1572,6 +1606,7 @@ def check_bedrock_custom_model_encryption() -> Dict[str, Any]: reference="https://docs.aws.amazon.com/bedrock/latest/userguide/encryption-custom-job.html", severity="Informational", status="N/A", + region=region, ) ) return findings @@ -1639,6 +1674,7 @@ def check_bedrock_custom_model_encryption() -> Dict[str, Any]: reference="https://docs.aws.amazon.com/bedrock/latest/userguide/encryption-custom-job.html", severity="Medium", status="Failed", + region=region, ) ) else: @@ -1651,6 +1687,7 @@ def check_bedrock_custom_model_encryption() -> Dict[str, Any]: reference="https://docs.aws.amazon.com/bedrock/latest/userguide/encryption-custom-job.html", severity="High", status="Passed", + region=region, ) ) @@ -1665,6 +1702,7 @@ def check_bedrock_custom_model_encryption() -> Dict[str, Any]: reference="https://docs.aws.amazon.com/bedrock/latest/userguide/model-customization-iam-role.html", severity="Low", status="N/A", + region=region, ) ) @@ -1687,12 +1725,13 @@ def check_bedrock_custom_model_encryption() -> Dict[str, Any]: reference="https://docs.aws.amazon.com/bedrock/latest/userguide/security.html", severity="High", status="Failed", + region=region, ) ], } -def check_bedrock_invocation_log_encryption() -> Dict[str, Any]: +def check_bedrock_invocation_log_encryption(region: str = "") -> Dict[str, Any]: """ Check if S3 buckets used for model invocation logging have proper encryption """ @@ -1705,8 +1744,8 @@ def check_bedrock_invocation_log_encryption() -> Dict[str, Any]: "csv_data": [], } - bedrock_client = boto3.client("bedrock", config=boto3_config) - s3_client = boto3.client("s3", config=boto3_config) + bedrock_client = boto3.client("bedrock", config=boto3_config, region_name=region) + s3_client = boto3.client("s3", config=boto3_config, region_name=region) try: # Get logging configuration @@ -1725,6 +1764,7 @@ def check_bedrock_invocation_log_encryption() -> Dict[str, Any]: reference="https://docs.aws.amazon.com/bedrock/latest/userguide/model-invocation-logging.html", severity="Informational", status="N/A", + region=region, ) ) return findings @@ -1768,6 +1808,7 @@ def check_bedrock_invocation_log_encryption() -> Dict[str, Any]: reference="https://docs.aws.amazon.com/bedrock/latest/userguide/model-invocation-logging.html", severity="Medium", status="Passed", + region=region, ) ) else: @@ -1781,6 +1822,7 @@ def check_bedrock_invocation_log_encryption() -> Dict[str, Any]: reference="https://docs.aws.amazon.com/bedrock/latest/userguide/model-invocation-logging.html", severity="Medium", status="Failed", + region=region, ) ) @@ -1799,6 +1841,7 @@ def check_bedrock_invocation_log_encryption() -> Dict[str, Any]: reference="https://docs.aws.amazon.com/bedrock/latest/userguide/model-invocation-logging.html", severity="High", status="Failed", + region=region, ) ) elif e.response["Error"]["Code"] == "AccessDenied": @@ -1811,6 +1854,7 @@ def check_bedrock_invocation_log_encryption() -> Dict[str, Any]: reference="https://docs.aws.amazon.com/bedrock/latest/userguide/model-invocation-logging.html", severity="Medium", status="Failed", + region=region, ) ) else: @@ -1826,6 +1870,7 @@ def check_bedrock_invocation_log_encryption() -> Dict[str, Any]: reference="https://docs.aws.amazon.com/bedrock/latest/userguide/model-invocation-logging.html", severity="Informational", status="N/A", + region=region, ) ) @@ -1848,12 +1893,13 @@ def check_bedrock_invocation_log_encryption() -> Dict[str, Any]: reference="https://docs.aws.amazon.com/bedrock/latest/userguide/security.html", severity="High", status="Failed", + region=region, ) ], } -def check_bedrock_flows_guardrails() -> Dict[str, Any]: +def check_bedrock_flows_guardrails(region: str = "") -> Dict[str, Any]: """ Check if Bedrock Flows have guardrails configured on prompt and knowledge base nodes """ @@ -1866,7 +1912,7 @@ def check_bedrock_flows_guardrails() -> Dict[str, Any]: "csv_data": [], } - bedrock_agent_client = boto3.client("bedrock-agent", config=boto3_config) + bedrock_agent_client = boto3.client("bedrock-agent", config=boto3_config, region_name=region) try: # List all flows @@ -1886,6 +1932,7 @@ def check_bedrock_flows_guardrails() -> Dict[str, Any]: reference="https://docs.aws.amazon.com/bedrock/latest/userguide/flows-guardrails.html", severity="Informational", status="N/A", + region=region, ) ) return findings @@ -1970,6 +2017,7 @@ def check_bedrock_flows_guardrails() -> Dict[str, Any]: reference="https://docs.aws.amazon.com/bedrock/latest/userguide/flows-guardrails.html", severity="High", status="Failed", + region=region, ) ) else: @@ -1984,6 +2032,7 @@ def check_bedrock_flows_guardrails() -> Dict[str, Any]: reference="https://docs.aws.amazon.com/bedrock/latest/userguide/flows-guardrails.html", severity="Medium", status="Passed", + region=region, ) ) else: @@ -1997,6 +2046,7 @@ def check_bedrock_flows_guardrails() -> Dict[str, Any]: reference="https://docs.aws.amazon.com/bedrock/latest/userguide/flows-guardrails.html", severity="Informational", status="N/A", + region=region, ) ) @@ -2011,6 +2061,7 @@ def check_bedrock_flows_guardrails() -> Dict[str, Any]: reference="https://docs.aws.amazon.com/bedrock/latest/userguide/flows-guardrails.html", severity="Low", status="N/A", + region=region, ) ) @@ -2033,12 +2084,13 @@ def check_bedrock_flows_guardrails() -> Dict[str, Any]: reference="https://docs.aws.amazon.com/bedrock/latest/userguide/security.html", severity="High", status="Failed", + region=region, ) ], } -def check_bedrock_agent_roles(permission_cache) -> Dict[str, Any]: +def check_bedrock_agent_roles(permission_cache, region: str = "") -> Dict[str, Any]: """ Check IAM roles associated with Bedrock agents for least privilege access """ @@ -2051,7 +2103,7 @@ def check_bedrock_agent_roles(permission_cache) -> Dict[str, Any]: "csv_data": [], } - bedrock_client = boto3.client("bedrock-agent", config=boto3_config) + bedrock_client = boto3.client("bedrock-agent", config=boto3_config, region_name=region) try: # Get all Bedrock agents @@ -2069,6 +2121,7 @@ def check_bedrock_agent_roles(permission_cache) -> Dict[str, Any]: reference="https://docs.aws.amazon.com/bedrock/latest/userguide/security_iam_service-with-iam.html", severity="Informational", status="N/A", + region=region, ) ) return findings @@ -2169,6 +2222,7 @@ def check_bedrock_agent_roles(permission_cache) -> Dict[str, Any]: reference="https://docs.aws.amazon.com/wellarchitected/latest/generative-ai-lens/gensec05-bp01.html", severity="High", status="Failed", + region=region, ) ) else: @@ -2184,6 +2238,7 @@ def check_bedrock_agent_roles(permission_cache) -> Dict[str, Any]: reference="https://docs.aws.amazon.com/wellarchitected/latest/generative-ai-lens/gensec05-bp01.html", severity="Medium", status="Passed", + region=region, ) ) @@ -2199,6 +2254,7 @@ def check_bedrock_agent_roles(permission_cache) -> Dict[str, Any]: reference="https://docs.aws.amazon.com/wellarchitected/latest/generative-ai-lens/gensec05-bp01.html", severity="High", status="Failed", + region=region, ) ) @@ -2219,6 +2275,7 @@ def check_bedrock_agent_roles(permission_cache) -> Dict[str, Any]: reference="https://docs.aws.amazon.com/bedrock/latest/userguide/security.html", severity="High", status="Failed", + region=region, ) ], } @@ -2238,6 +2295,7 @@ def generate_csv_report(findings: List[Dict[str, Any]]) -> str: "Reference", "Severity", "Status", + "Region", ] writer = csv.DictWriter(csv_buffer, fieldnames=fieldnames) @@ -2254,14 +2312,17 @@ def get_current_utc_date(): return datetime.now(timezone.utc).strftime("%Y/%m/%d") -def write_to_s3(execution_id, csv_content: str, bucket_name: str) -> str: +def write_to_s3(execution_id, csv_content: str, bucket_name: str, region: str = "") -> str: """ Write CSV report to S3 bucket """ logger.debug(f"Writing CSV report to S3 bucket: {bucket_name}") try: s3_client = boto3.client("s3", config=boto3_config) - file_name = f"bedrock_security_report_{execution_id}.csv" + if region: + file_name = f"bedrock_security_report_{execution_id}_{region}.csv" + else: + file_name = f"bedrock_security_report_{execution_id}.csv" s3_client.put_object( Bucket=bucket_name, Key=file_name, Body=csv_content, ContentType="text/csv" @@ -2283,6 +2344,41 @@ def lambda_handler(event, context): all_findings = [] try: + # Extract target region from Step Functions Map state + region = event.get("Region", os.environ.get("AWS_REGION", "us-east-1")) + logger.info(f"Scanning region: {region}") + + # Verify Bedrock is available in this region + try: + test_client = boto3.client("bedrock", config=boto3_config, region_name=region) + test_client.get_model_invocation_logging_configuration() + except (EndpointConnectionError, Exception) as e: + error_msg = str(e) + if "Could not connect to the endpoint URL" in error_msg or "EndpointConnectionError" in type(e).__name__: + logger.info(f"Bedrock service not available in region {region}, skipping") + all_findings.append({ + "check_name": "Bedrock Service Availability", + "status": "N/A", + "details": f"Bedrock is not available in region {region}", + "csv_data": [ + create_finding( + check_id="BR-00", + finding_name="Bedrock Service Availability", + finding_details=f"Amazon Bedrock is not available in region {region}. No checks performed.", + resolution="No action required. Bedrock is not deployed in this region.", + reference="https://docs.aws.amazon.com/bedrock/latest/userguide/bedrock-regions.html", + severity="Informational", + status="N/A", + region=region, + ) + ], + }) + execution_id = event["Execution"]["Name"] + csv_content = generate_csv_report(all_findings) + bucket_name = os.environ.get("AIML_ASSESSMENT_BUCKET_NAME") + s3_url = write_to_s3(execution_id, csv_content, bucket_name, region=region) + return {"statusCode": 200, "body": {"message": f"Bedrock not available in {region}", "report_url": s3_url}} + # Initialize permission cache logger.info("Initializing IAM permission cache") execution_id = event["Execution"]["Name"] @@ -2296,65 +2392,61 @@ def lambda_handler(event, context): # Run all checks using the cached permissions logger.info("Running AmazonBedrockFullAccess check") - bedrock_full_access_findings = check_bedrock_full_access_roles(permission_cache) + bedrock_full_access_findings = check_bedrock_full_access_roles(permission_cache, region=region) all_findings.append(bedrock_full_access_findings) logger.info("Running Bedrock access and VPC endpoints check") bedrock_access_vpc_findings = check_bedrock_access_and_vpc_endpoints( - permission_cache + permission_cache, region=region ) all_findings.append(bedrock_access_vpc_findings) - # logger.info("Running stale access check") - # stale_access_findings = check_stale_bedrock_access(permission_cache) - # all_findings.append(stale_access_findings) - logger.info("Running marketplace subscription access check") marketplace_access_findings = check_marketplace_subscription_access( - permission_cache + permission_cache, region=region ) all_findings.append(marketplace_access_findings) logger.info("Running Bedrock logging findings check") - bedrock_logging_findings = check_bedrock_logging_configuration() + bedrock_logging_findings = check_bedrock_logging_configuration(region=region) all_findings.append(bedrock_logging_findings) logger.info("Running Bedrock Guardrails check") - bedrock_guardrails_findings = check_bedrock_guardrails() + bedrock_guardrails_findings = check_bedrock_guardrails(region=region) all_findings.append(bedrock_guardrails_findings) logger.info("Running Bedrock CloudTrail logging check") - bedrock_cloudtrail_findings = check_bedrock_cloudtrail_logging() + bedrock_cloudtrail_findings = check_bedrock_cloudtrail_logging(region=region) all_findings.append(bedrock_cloudtrail_findings) logger.info("Running Bedrock Prompt Management check") - bedrock_prompt_management_findings = check_bedrock_prompt_management() + bedrock_prompt_management_findings = check_bedrock_prompt_management(region=region) all_findings.append(bedrock_prompt_management_findings) logger.info("Running Bedrock agent IAM roles check") - bedrock_agent_roles_findings = check_bedrock_agent_roles(permission_cache) + bedrock_agent_roles_findings = check_bedrock_agent_roles(permission_cache, region=region) all_findings.append(bedrock_agent_roles_findings) logger.info("Running Bedrock Knowledge Base encryption check") - kb_encryption_findings = check_bedrock_knowledge_base_encryption() + kb_encryption_findings = check_bedrock_knowledge_base_encryption(region=region) all_findings.append(kb_encryption_findings) logger.info("Running Bedrock Guardrail IAM enforcement check") guardrail_iam_findings = check_bedrock_guardrail_iam_enforcement( - permission_cache + permission_cache, region=region ) all_findings.append(guardrail_iam_findings) logger.info("Running Bedrock custom model encryption check") - custom_model_encryption_findings = check_bedrock_custom_model_encryption() + custom_model_encryption_findings = check_bedrock_custom_model_encryption(region=region) all_findings.append(custom_model_encryption_findings) logger.info("Running Bedrock invocation log encryption check") - invocation_log_encryption_findings = check_bedrock_invocation_log_encryption() + invocation_log_encryption_findings = check_bedrock_invocation_log_encryption(region=region) all_findings.append(invocation_log_encryption_findings) logger.info("Running Bedrock Flows guardrails check") - flows_guardrails_findings = check_bedrock_flows_guardrails() + flows_guardrails_findings = check_bedrock_flows_guardrails(region=region) all_findings.append(flows_guardrails_findings) # Generate and upload report @@ -2368,7 +2460,7 @@ def lambda_handler(event, context): ) logger.info("Writing report to S3") - s3_url = write_to_s3(execution_id, csv_content, bucket_name) + s3_url = write_to_s3(execution_id, csv_content, bucket_name, region=region) return { "statusCode": 200, diff --git a/aiml-security-assessment/functions/security/bedrock_assessments/schema.py b/aiml-security-assessment/functions/security/bedrock_assessments/schema.py index d2a713a..6b48996 100644 --- a/aiml-security-assessment/functions/security/bedrock_assessments/schema.py +++ b/aiml-security-assessment/functions/security/bedrock_assessments/schema.py @@ -24,6 +24,7 @@ class Finding(BaseModel): Reference: str = Field(..., description="Documentation reference URL") Severity: SeverityEnum = Field(..., description="Severity level of the finding") Status: StatusEnum = Field(..., description="Current status of the finding") + Region: str = Field(default="", description="AWS region where the finding was identified") @validator('Check_ID') def validate_check_id(cls, v): @@ -61,7 +62,8 @@ def create_finding( resolution: str, reference: str, severity: SeverityEnum, - status: StatusEnum + status: StatusEnum, + region: str = "" ) -> Dict[str, Any]: """ Create a validated finding object @@ -74,6 +76,7 @@ def create_finding( reference: Documentation URL severity: Severity level status: Current status + region: AWS region where the finding was identified Returns: Dict[str, Any]: Validated finding as dictionary @@ -88,6 +91,7 @@ def create_finding( Resolution=resolution, Reference=reference, Severity=severity, - Status=status + Status=status, + Region=region ) return dict(finding.model_dump()) # Convert to regular dictionary \ No newline at end of file diff --git a/aiml-security-assessment/functions/security/generate_consolidated_report/app.py b/aiml-security-assessment/functions/security/generate_consolidated_report/app.py index 7626a42..555c491 100644 --- a/aiml-security-assessment/functions/security/generate_consolidated_report/app.py +++ b/aiml-security-assessment/functions/security/generate_consolidated_report/app.py @@ -198,6 +198,7 @@ def generate_html_report(assessment_results: Dict[str, Any]) -> str: "agentcore": {"passed": 0, "failed": 0, "na": 0}, } service_findings = {"bedrock": [], "sagemaker": [], "agentcore": []} + regions = set() for service in ["bedrock", "sagemaker", "agentcore"]: if service in assessment_results: @@ -213,6 +214,9 @@ def generate_html_report(assessment_results: Dict[str, Any]) -> str: service_stats[service]["failed"] += 1 elif status == "n/a": service_stats[service]["na"] += 1 + region = finding.get("Region", "") + if region: + regions.add(region) account_id = assessment_results.get("account_id", "Unknown") timestamp = assessment_results.get( @@ -227,6 +231,7 @@ def generate_html_report(assessment_results: Dict[str, Any]) -> str: mode="single", account_id=account_id, timestamp=timestamp, + regions=sorted(regions) if regions else None, ) except Exception as e: logger.error(f"Error generating HTML report: {str(e)}", exc_info=True) diff --git a/aiml-security-assessment/functions/security/generate_consolidated_report/report_template.py b/aiml-security-assessment/functions/security/generate_consolidated_report/report_template.py index 7f705df..8e79d07 100644 --- a/aiml-security-assessment/functions/security/generate_consolidated_report/report_template.py +++ b/aiml-security-assessment/functions/security/generate_consolidated_report/report_template.py @@ -33,6 +33,7 @@ def generate_table_rows(findings: List[Dict], include_data_attrs: bool = True) - ) service = finding.get("_service", "bedrock") account_id = finding.get("account_id", finding.get("Account_ID", "")) + region = finding.get("region", finding.get("Region", "")) check_id = finding.get("check_id", finding.get("Check_ID", "")) finding_name = finding.get("finding", finding.get("Finding", "")) details = finding.get("details", finding.get("Finding_Details", "")) @@ -45,7 +46,7 @@ def generate_table_rows(findings: List[Dict], include_data_attrs: bool = True) - ref_html = '-' data_attrs = ( - f'data-service="{service}" data-severity="{severity}" data-status="{status}" data-account="{account_id}"' + f'data-service="{service}" data-severity="{severity}" data-status="{status}" data-account="{account_id}" data-region="{region}"' if include_data_attrs else "" ) @@ -57,6 +58,7 @@ def generate_table_rows(findings: List[Dict], include_data_attrs: bool = True) - row = f""" {account_id} + {region} {check_id} {finding_name} {details} @@ -70,7 +72,7 @@ def generate_table_rows(findings: List[Dict], include_data_attrs: bool = True) - return ( "\n".join(rows) if rows - else 'No findings to display' + else 'No findings to display' ) @@ -180,15 +182,16 @@ def get_html_template() -> str: .alert-domain {{ font-weight: 600; font-size: 14px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }} .alert-category {{ font-size: 12px; color: var(--text-2); margin-top: 2px; }} .table-wrap {{ overflow-x: auto; max-height: 900px; overflow-y: auto; }} - table {{ width: 100%; border-collapse: collapse; font-size: 13px; table-layout: fixed; min-width: 900px; }} - table th:nth-child(1) {{ width: 11%; }} - table th:nth-child(2) {{ width: 7%; }} - table th:nth-child(3) {{ width: 13%; }} - table th:nth-child(4) {{ width: 20%; }} - table th:nth-child(5) {{ width: 20%; }} - table th:nth-child(6) {{ width: 7%; }} - table th:nth-child(7) {{ width: 10%; }} - table th:nth-child(8) {{ width: 10%; }} + table {{ width: 100%; border-collapse: collapse; font-size: 13px; table-layout: fixed; min-width: 1000px; }} + table th:nth-child(1) {{ width: 10%; }} + table th:nth-child(2) {{ width: 9%; }} + table th:nth-child(3) {{ width: 6%; }} + table th:nth-child(4) {{ width: 12%; }} + table th:nth-child(5) {{ width: 18%; }} + table th:nth-child(6) {{ width: 18%; }} + table th:nth-child(7) {{ width: 6%; }} + table th:nth-child(8) {{ width: 9%; }} + table th:nth-child(9) {{ width: 9%; }} th {{ text-align: left; padding: 14px 16px; font-weight: 700; font-size: 11px; text-transform: uppercase; letter-spacing: 0.5px; color: var(--text); background: var(--surface-2); border-bottom: 3px solid var(--accent); white-space: nowrap; position: sticky; top: 0; }} th.sortable {{ cursor: pointer; user-select: none; transition: background 0.15s; }} th.sortable:hover {{ background: var(--border); }} @@ -298,7 +301,7 @@ def get_html_template() -> str:
-
Security Checks
{security_checks}
Evaluated per account
+
Security Checks
{security_checks}
{security_checks_sub}
Total Findings
{total_findings}
{findings_sub}
Actionable Findings
{actionable_findings}
High, Medium, and Low severity
High Severity
{high_passed}/{high_count}
{high_pass_rate}% passed · Immediate action required
@@ -326,12 +329,13 @@ def get_html_template() -> str:
{account_filter} + {region_filter}
-
{all_rows}
Account IDCheck IDFindingDetailsResolutionReferenceSeverityStatus
+
{all_rows}
Account IDRegionCheck IDFindingDetailsResolutionReferenceSeverityStatus
Risk Distribution
@@ -343,6 +347,7 @@ def get_html_template() -> str:
Overall
{pass_rate}%
{passed_count} of {actionable_findings} actionable checks
{account_risk_section} + {region_risk_section}

Findings by Service

Bedrock
{bedrock_total}
{bedrock_failed} Failed · {bedrock_passed} Passed
@@ -355,33 +360,36 @@ def get_html_template() -> str:
{bedrock_account_filter} + {bedrock_region_filter}
-
{bedrock_rows}
Account IDCheck IDFindingDetailsResolutionReferenceSeverityStatus
+
{bedrock_rows}
Account IDRegionCheck IDFindingDetailsResolutionReferenceSeverityStatus
Amazon SageMaker Findings
{sagemaker_account_filter} + {sagemaker_region_filter}
-
{sagemaker_rows}
Account IDCheck IDFindingDetailsResolutionReferenceSeverityStatus
+
{sagemaker_rows}
Account IDRegionCheck IDFindingDetailsResolutionReferenceSeverityStatus
Amazon Bedrock AgentCore Findings
{agentcore_account_filter} + {agentcore_region_filter}
-
{agentcore_rows}
Account IDCheck IDFindingDetailsResolutionReferenceSeverityStatus
+
{agentcore_rows}
Account IDRegionCheck IDFindingDetailsResolutionReferenceSeverityStatus
Assessment Methodology
@@ -417,6 +425,7 @@ def get_html_template() -> str: function applyFilters() {{ const searchText = document.getElementById('searchInput').value.toLowerCase(); const accountFilter = document.getElementById('accountFilter')?.value.toLowerCase() || ''; + const regionFilter = document.getElementById('regionFilter')?.value.toLowerCase() || ''; const serviceFilter = document.getElementById('serviceFilter').value.toLowerCase(); const severityFilter = document.getElementById('severityFilter').value.toLowerCase(); const statusFilter = document.getElementById('statusFilter').value.toLowerCase(); @@ -424,12 +433,14 @@ def get_html_template() -> str: rows.forEach(row => {{ const rowText = row.textContent.toLowerCase(); const rowAccount = row.dataset.account || ''; + const rowRegion = row.dataset.region || ''; const rowService = row.dataset.service || ''; const rowSeverity = row.dataset.severity || ''; const rowStatus = row.dataset.status || ''; let show = true; if (searchText && !rowText.includes(searchText)) show = false; if (accountFilter && rowAccount !== accountFilter) show = false; + if (regionFilter && rowRegion.toLowerCase() !== regionFilter) show = false; if (serviceFilter && rowService !== serviceFilter) show = false; if (severityFilter && rowSeverity !== severityFilter) show = false; if (statusFilter && rowStatus !== statusFilter) show = false; @@ -439,6 +450,7 @@ def get_html_template() -> str: document.getElementById('resetFilters').addEventListener('click', function() {{ document.getElementById('searchInput').value = ''; if (document.getElementById('accountFilter')) document.getElementById('accountFilter').value = ''; + if (document.getElementById('regionFilter')) document.getElementById('regionFilter').value = ''; document.getElementById('serviceFilter').value = ''; document.getElementById('severityFilter').value = ''; document.getElementById('statusFilter').value = ''; @@ -446,6 +458,7 @@ def get_html_template() -> str: }}); document.getElementById('searchInput').addEventListener('input', applyFilters); if (document.getElementById('accountFilter')) document.getElementById('accountFilter').addEventListener('change', applyFilters); + if (document.getElementById('regionFilter')) document.getElementById('regionFilter').addEventListener('change', applyFilters); document.getElementById('serviceFilter').addEventListener('change', applyFilters); document.getElementById('severityFilter').addEventListener('change', applyFilters); document.getElementById('statusFilter').addEventListener('change', applyFilters); @@ -486,9 +499,13 @@ def get_html_template() -> str: aVal = a.dataset.account || ''; bVal = b.dataset.account || ''; break; + case 'region': + aVal = a.dataset.region || ''; + bVal = b.dataset.region || ''; + break; case 'checkId': - aVal = a.querySelector('td:nth-child(2) code')?.textContent || ''; - bVal = b.querySelector('td:nth-child(2) code')?.textContent || ''; + aVal = a.querySelector('td:nth-child(3) code')?.textContent || ''; + bVal = b.querySelector('td:nth-child(3) code')?.textContent || ''; break; case 'finding': aVal = a.querySelector('.col-domain')?.textContent.toLowerCase() || ''; @@ -511,28 +528,32 @@ def get_html_template() -> str: }}); }}); // Service-specific filter functions - function createServiceFilter(tableId, searchId, accountId, severityId, statusId, resetId) {{ + function createServiceFilter(tableId, searchId, accountId, regionId, severityId, statusId, resetId) {{ const table = document.getElementById(tableId); if (!table) return; const searchInput = document.getElementById(searchId); const accountFilter = document.getElementById(accountId); + const regionFilter = document.getElementById(regionId); const severityFilter = document.getElementById(severityId); const statusFilter = document.getElementById(statusId); const resetBtn = document.getElementById(resetId); function applyServiceFilters() {{ const searchText = searchInput?.value.toLowerCase() || ''; const accountValue = accountFilter?.value.toLowerCase() || ''; + const regionValue = regionFilter?.value.toLowerCase() || ''; const severityValue = severityFilter?.value.toLowerCase() || ''; const statusValue = statusFilter?.value.toLowerCase() || ''; const rows = table.querySelectorAll('tbody tr'); rows.forEach(row => {{ const rowText = row.textContent.toLowerCase(); const rowAccount = row.dataset.account || ''; + const rowRegion = row.dataset.region || ''; const rowSeverity = row.dataset.severity || ''; const rowStatus = row.dataset.status || ''; let show = true; if (searchText && !rowText.includes(searchText)) show = false; if (accountValue && rowAccount !== accountValue) show = false; + if (regionValue && rowRegion.toLowerCase() !== regionValue) show = false; if (severityValue && rowSeverity !== severityValue) show = false; if (statusValue && rowStatus !== statusValue) show = false; row.style.display = show ? '' : 'none'; @@ -540,19 +561,21 @@ def get_html_template() -> str: }} searchInput?.addEventListener('input', applyServiceFilters); accountFilter?.addEventListener('change', applyServiceFilters); + regionFilter?.addEventListener('change', applyServiceFilters); severityFilter?.addEventListener('change', applyServiceFilters); statusFilter?.addEventListener('change', applyServiceFilters); resetBtn?.addEventListener('click', function() {{ if (searchInput) searchInput.value = ''; if (accountFilter) accountFilter.value = ''; + if (regionFilter) regionFilter.value = ''; if (severityFilter) severityFilter.value = ''; if (statusFilter) statusFilter.value = ''; applyServiceFilters(); }}); }} - createServiceFilter('bedrockTable', 'bedrockSearchInput', 'bedrockAccountFilter', 'bedrockSeverityFilter', 'bedrockStatusFilter', 'bedrockResetFilters'); - createServiceFilter('sagemakerTable', 'sagemakerSearchInput', 'sagemakerAccountFilter', 'sagemakerSeverityFilter', 'sagemakerStatusFilter', 'sagemakerResetFilters'); - createServiceFilter('agentcoreTable', 'agentcoreSearchInput', 'agentcoreAccountFilter', 'agentcoreSeverityFilter', 'agentcoreStatusFilter', 'agentcoreResetFilters'); + createServiceFilter('bedrockTable', 'bedrockSearchInput', 'bedrockAccountFilter', 'bedrockRegionFilter', 'bedrockSeverityFilter', 'bedrockStatusFilter', 'bedrockResetFilters'); + createServiceFilter('sagemakerTable', 'sagemakerSearchInput', 'sagemakerAccountFilter', 'sagemakerRegionFilter', 'sagemakerSeverityFilter', 'sagemakerStatusFilter', 'sagemakerResetFilters'); + createServiceFilter('agentcoreTable', 'agentcoreSearchInput', 'agentcoreAccountFilter', 'agentcoreRegionFilter', 'agentcoreSeverityFilter', 'agentcoreStatusFilter', 'agentcoreResetFilters'); // Apply initial filters for main table applyFilters(); @@ -569,6 +592,7 @@ def generate_html_report( account_id: Optional[str] = None, account_ids: Optional[List[str]] = None, timestamp: Optional[str] = None, + regions: list = None, ) -> str: """ Generate HTML report from findings data. @@ -581,6 +605,7 @@ def generate_html_report( account_id: Account ID (for single-account mode) account_ids: List of account IDs (for multi-account mode) timestamp: Optional timestamp string + regions: Optional list of region strings for multi-region filtering Returns: Complete HTML report string @@ -716,14 +741,38 @@ def generate_html_report( service_findings.get("agentcore", []), include_data_attrs=True ) + # Build region filter HTML (shared across modes, only shown when multiple regions) + if regions and len(regions) > 1: + region_options = "".join( + [ + f'' + for r in sorted(regions) + ] + ) + region_filter = f'
' + bedrock_region_filter = f'
' + sagemaker_region_filter = f'
' + agentcore_region_filter = f'
' + else: + region_filter = "" + bedrock_region_filter = "" + sagemaker_region_filter = "" + agentcore_region_filter = "" + # Mode-specific content num_accounts = len(account_ids) if account_ids else 1 + num_regions = len(regions) if regions else 1 if mode == "multi": title = "Multi-Account AI/ML Security Assessment Report" sidebar_subtitle = "Multi-Account Assessment" account_info = f"Accounts: {num_accounts}" header_account_info = f"{num_accounts} Accounts" - findings_sub = f"Across {num_accounts} accounts" + if num_regions > 1: + findings_sub = f"Across {num_accounts} accounts · {num_regions} regions" + security_checks_sub = f"Evaluated across {num_regions} regions" + else: + findings_sub = f"Across {num_accounts} accounts" + security_checks_sub = "Evaluated per account" account_options = "".join( [ f'' @@ -782,14 +831,62 @@ def generate_html_report( title = "AI/ML Security Assessment Report" sidebar_subtitle = "Assessment Report" account_info = f"Account: {account_id or 'Unknown'}" - header_account_info = f"Account: {account_id or 'Unknown'}" - findings_sub = "Across 1 account" + if num_regions > 1: + header_account_info = f"Account: {account_id or 'Unknown'} · {num_regions} Regions" + findings_sub = f"Across {num_regions} regions" + security_checks_sub = f"Evaluated across {num_regions} regions" + else: + header_account_info = f"Account: {account_id or 'Unknown'}" + findings_sub = "Across 1 account" + security_checks_sub = "Evaluated per account" account_filter = "" bedrock_account_filter = "" sagemaker_account_filter = "" agentcore_account_filter = "" account_risk_section = "" + # Build region risk section (shown when multiple regions) + if regions and len(regions) > 1: + region_metrics_html = "" + for reg in sorted(regions): + reg_findings = [ + f for f in all_findings + if f.get("region", f.get("Region", "")) == reg + ] + reg_high = sum( + 1 for f in reg_findings + if f.get("severity", f.get("Severity", "")).lower() == "high" + and f.get("status", f.get("Status", "")).lower() == "failed" + ) + reg_medium = sum( + 1 for f in reg_findings + if f.get("severity", f.get("Severity", "")).lower() == "medium" + and f.get("status", f.get("Status", "")).lower() == "failed" + ) + reg_low = sum( + 1 for f in reg_findings + if f.get("severity", f.get("Severity", "")).lower() == "low" + and f.get("status", f.get("Status", "")).lower() == "failed" + ) + reg_total_failed = reg_high + reg_medium + reg_low + + if reg_high > 0: + risk_class = "danger" + border_color = "var(--danger)" + elif reg_medium > 0: + risk_class = "warning" + border_color = "var(--warning)" + else: + risk_class = "" + border_color = "var(--success)" + + region_metrics_html += f"""
{reg}
{reg_total_failed}
{reg_high} High · {reg_medium} Med · {reg_low} Low
""" + + region_risk_section = f"""

Risk by Region

+
{region_metrics_html}
""" + else: + region_risk_section = "" + # Fill template html_template = get_html_template() @@ -799,9 +896,11 @@ def generate_html_report( account_info=account_info, header_account_info=header_account_info, account_filter=account_filter, + region_filter=region_filter, timestamp=timestamp, date_display=date_display, security_checks=security_checks, + security_checks_sub=security_checks_sub, total_findings=total_findings, findings_sub=findings_sub, actionable_findings=actionable_findings, @@ -840,5 +939,9 @@ def generate_html_report( bedrock_account_filter=bedrock_account_filter, sagemaker_account_filter=sagemaker_account_filter, agentcore_account_filter=agentcore_account_filter, + bedrock_region_filter=bedrock_region_filter, + sagemaker_region_filter=sagemaker_region_filter, + agentcore_region_filter=agentcore_region_filter, account_risk_section=account_risk_section, + region_risk_section=region_risk_section, ) diff --git a/aiml-security-assessment/functions/security/generate_consolidated_report/schema.py b/aiml-security-assessment/functions/security/generate_consolidated_report/schema.py index d745e06..19aec8f 100644 --- a/aiml-security-assessment/functions/security/generate_consolidated_report/schema.py +++ b/aiml-security-assessment/functions/security/generate_consolidated_report/schema.py @@ -22,6 +22,7 @@ class Finding(BaseModel): Reference: str = Field(..., description="Documentation reference URL") Severity: SeverityEnum = Field(..., description="Severity level of the finding") Status: StatusEnum = Field(..., description="Current status of the finding") + Region: str = Field(default="", description="AWS region where the finding was identified") @validator('Reference') def validate_reference_url(cls, v): @@ -42,18 +43,18 @@ def validate_status(cls, v): raise ValueError('Status must be one of the allowed values') return v -# Example usage: def create_finding( finding_name: str, finding_details: str, resolution: str, reference: str, severity: SeverityEnum, - status: StatusEnum -) -> Finding: + status: StatusEnum, + region: str = "" +) -> dict: """ Create a validated finding object - + Args: finding_name: Name of the finding finding_details: Detailed description @@ -61,10 +62,11 @@ def create_finding( reference: Documentation URL severity: Severity level status: Current status - + region: AWS region where the finding was identified + Returns: - Finding: Validated finding object - + Dict: Validated finding as dictionary + Raises: ValidationError: If any field fails validation """ @@ -74,6 +76,7 @@ def create_finding( Resolution=resolution, Reference=reference, Severity=severity, - Status=status + Status=status, + Region=region ) return dict(finding.model_dump()) # Convert to regular dictionary \ No newline at end of file diff --git a/aiml-security-assessment/functions/security/resolve_regions/app.py b/aiml-security-assessment/functions/security/resolve_regions/app.py new file mode 100644 index 0000000..ba65b78 --- /dev/null +++ b/aiml-security-assessment/functions/security/resolve_regions/app.py @@ -0,0 +1,61 @@ +""" +Resolve Target Regions Lambda Function + +Resolves the list of AWS regions to scan based on the TARGET_REGIONS +environment variable. Returns a list for the Step Functions Map state +to iterate over. +""" + +import os +import logging +import boto3 + +logger = logging.getLogger() +logger.setLevel(logging.INFO) + +BEDROCK_SERVICE = "bedrock" +SAGEMAKER_SERVICE = "sagemaker" +AGENTCORE_SERVICE = "bedrock-agent" + +SERVICES = [BEDROCK_SERVICE, SAGEMAKER_SERVICE, AGENTCORE_SERVICE] + + +def get_available_regions(): + """Get the union of all regions where assessed services are available.""" + session = boto3.Session() + all_regions = set() + for service in SERVICES: + try: + regions = session.get_available_regions(service) + all_regions.update(regions) + except Exception as e: + logger.warning(f"Could not get regions for {service}: {e}") + return sorted(all_regions) + + +def resolve_regions(): + """Resolve target regions from environment variable.""" + target_regions = os.environ.get("TARGET_REGIONS", "").strip() + current_region = os.environ.get("AWS_REGION", os.environ.get("AWS_DEFAULT_REGION", "us-east-1")) + + if not target_regions: + return [current_region] + + if target_regions.lower() == "all": + regions = get_available_regions() + if not regions: + logger.warning("No regions discovered, falling back to current region") + return [current_region] + return regions + + return [r.strip() for r in target_regions.split(",") if r.strip()] + + +def lambda_handler(event, context): + """Main Lambda handler. Returns region list for Map state.""" + logger.info(f"Event: {event}") + + regions = resolve_regions() + logger.info(f"Resolved {len(regions)} target regions: {regions}") + + return {"regions": regions} diff --git a/aiml-security-assessment/functions/security/sagemaker_assessments/app.py b/aiml-security-assessment/functions/security/sagemaker_assessments/app.py index 9e52720..fec78d9 100644 --- a/aiml-security-assessment/functions/security/sagemaker_assessments/app.py +++ b/aiml-security-assessment/functions/security/sagemaker_assessments/app.py @@ -7,7 +7,7 @@ from typing import Dict, List, Any, Optional from io import StringIO from botocore.config import Config -from botocore.exceptions import ClientError +from botocore.exceptions import ClientError, EndpointConnectionError import random import json @@ -80,7 +80,7 @@ def get_permissions_cache(execution_id: str) -> Optional[Dict[str, Any]]: return None -def check_sagemaker_internet_access() -> Dict[str, Any]: +def check_sagemaker_internet_access(region: str = "") -> Dict[str, Any]: """ Check if SageMaker notebook instances and domains have direct internet access """ @@ -93,7 +93,7 @@ def check_sagemaker_internet_access() -> Dict[str, Any]: total_resources_checked = 0 # Create SageMaker client - sagemaker_client = boto3.client("sagemaker") + sagemaker_client = boto3.client("sagemaker", config=boto3_config, region_name=region) # Check Notebook Instances try: @@ -167,6 +167,7 @@ def check_sagemaker_internet_access() -> Dict[str, Any]: reference="https://docs.aws.amazon.com/sagemaker/latest/dg/infrastructure-security.html", severity="High", status="Failed", + region=region, ) ) @@ -181,6 +182,7 @@ def check_sagemaker_internet_access() -> Dict[str, Any]: reference="https://docs.aws.amazon.com/sagemaker/latest/dg/infrastructure-security.html", severity="High", status="Failed", + region=region, ) ) else: @@ -197,6 +199,7 @@ def check_sagemaker_internet_access() -> Dict[str, Any]: reference="https://docs.aws.amazon.com/sagemaker/latest/dg/infrastructure-security.html", severity="High", status="Passed", + region=region, ) ) else: @@ -209,6 +212,7 @@ def check_sagemaker_internet_access() -> Dict[str, Any]: reference="https://docs.aws.amazon.com/sagemaker/latest/dg/infrastructure-security.html", severity="Informational", status="N/A", + region=region, ) ) @@ -228,12 +232,13 @@ def check_sagemaker_internet_access() -> Dict[str, Any]: reference="https://docs.aws.amazon.com/sagemaker/latest/dg/security.html", severity="High", status="Failed", + region=region, ) ] } -def check_guardduty_enabled() -> Dict[str, Any]: +def check_guardduty_enabled(region: str = "") -> Dict[str, Any]: """ Check if GuardDuty is enabled in the account to monitor SageMaker security issues @@ -248,7 +253,7 @@ def check_guardduty_enabled() -> Dict[str, Any]: } try: - guardduty_client = boto3.client("guardduty") + guardduty_client = boto3.client("guardduty", config=boto3_config, region_name=region) # Get list of detectors in the current region detectors = guardduty_client.list_detectors() @@ -263,6 +268,7 @@ def check_guardduty_enabled() -> Dict[str, Any]: reference="https://docs.aws.amazon.com/guardduty/latest/ug/ai-protection.html", severity="High", status="Failed", + region=region, ) ) else: @@ -275,6 +281,7 @@ def check_guardduty_enabled() -> Dict[str, Any]: reference="https://docs.aws.amazon.com/guardduty/latest/ug/ai-protection.html", severity="Medium", status="Passed", + region=region, ) ) except ClientError as e: @@ -289,6 +296,7 @@ def check_guardduty_enabled() -> Dict[str, Any]: reference="https://docs.aws.amazon.com/guardduty/latest/ug/security-iam.html", severity="High", status="Failed", + region=region, ) ) except Exception as e: @@ -301,13 +309,14 @@ def check_guardduty_enabled() -> Dict[str, Any]: reference="https://docs.aws.amazon.com/guardduty/latest/ug/what-is-guardduty.html", severity="High", status="Failed", + region=region, ) ) return findings -def check_sagemaker_iam_permissions(permission_cache) -> Dict[str, Any]: +def check_sagemaker_iam_permissions(permission_cache, region: str = "") -> Dict[str, Any]: """ Check SageMaker IAM permissions, SSO configuration, and stale access """ @@ -325,7 +334,7 @@ def check_sagemaker_iam_permissions(permission_cache) -> Dict[str, Any]: # Check for stale access stale_users = [] - iam_client = boto3.client("iam") + iam_client = boto3.client("iam", config=boto3_config, region_name=region) two_months_ago = datetime.now(timezone.utc) - timedelta(days=60) # Check users' last access to SageMaker @@ -373,7 +382,7 @@ def check_sagemaker_iam_permissions(permission_cache) -> Dict[str, Any]: # Check SSO configuration domains_without_sso = [] try: - sagemaker_client = boto3.client("sagemaker") + sagemaker_client = boto3.client("sagemaker", config=boto3_config, region_name=region) paginator = sagemaker_client.get_paginator("list_domains") for page in paginator.paginate(): @@ -442,6 +451,7 @@ def check_sagemaker_iam_permissions(permission_cache) -> Dict[str, Any]: reference="https://docs.aws.amazon.com/sagemaker-unified-studio/latest/adminguide/security-iam.html", severity="High", status="Failed", + region=region, ) ) @@ -457,6 +467,7 @@ def check_sagemaker_iam_permissions(permission_cache) -> Dict[str, Any]: reference="https://docs.aws.amazon.com/sagemaker-unified-studio/latest/adminguide/security-iam.html", severity="Medium", status="Failed", + region=region, ) ) @@ -478,6 +489,7 @@ def check_sagemaker_iam_permissions(permission_cache) -> Dict[str, Any]: reference="https://aws.amazon.com/blogs/machine-learning/team-and-user-management-with-amazon-sagemaker-and-aws-sso/", severity="Medium", status="Failed", + region=region, ) ) else: @@ -490,6 +502,7 @@ def check_sagemaker_iam_permissions(permission_cache) -> Dict[str, Any]: reference="https://docs.aws.amazon.com/sagemaker-unified-studio/latest/adminguide/security-iam.html", severity="High", status="Passed", + region=region, ) ) @@ -546,7 +559,7 @@ def get_account_id() -> str: raise -def check_sagemaker_data_protection() -> Dict[str, Any]: +def check_sagemaker_data_protection(region: str = "") -> Dict[str, Any]: """ Check SageMaker data protection configurations including encryption at rest and in transit """ @@ -554,7 +567,7 @@ def check_sagemaker_data_protection() -> Dict[str, Any]: try: findings = {"csv_data": []} - sagemaker_client = boto3.client("sagemaker") + sagemaker_client = boto3.client("sagemaker", config=boto3_config, region_name=region) # Track resources with encryption issues resources_with_aws_managed_keys = [] @@ -705,6 +718,7 @@ def check_sagemaker_data_protection() -> Dict[str, Any]: reference="https://docs.aws.amazon.com/sagemaker/latest/dg/key-management.html", severity="High", status="Failed", + region=region, ) ) @@ -719,6 +733,7 @@ def check_sagemaker_data_protection() -> Dict[str, Any]: reference="https://docs.aws.amazon.com/sagemaker/latest/dg/key-management.html", severity="Low", status="Failed", + region=region, ) ) @@ -733,6 +748,7 @@ def check_sagemaker_data_protection() -> Dict[str, Any]: reference="https://docs.aws.amazon.com/sagemaker/latest/dg/encryption-in-transit.html", severity="Medium", status="Failed", + region=region, ) ) @@ -747,6 +763,7 @@ def check_sagemaker_data_protection() -> Dict[str, Any]: reference="https://docs.aws.amazon.com/sagemaker/latest/dg/security.html", severity="High", status="Passed", + region=region, ) ) else: @@ -759,6 +776,7 @@ def check_sagemaker_data_protection() -> Dict[str, Any]: reference="https://docs.aws.amazon.com/sagemaker/latest/dg/security.html", severity="Informational", status="N/A", + region=region, ) ) @@ -778,12 +796,13 @@ def check_sagemaker_data_protection() -> Dict[str, Any]: reference="https://docs.aws.amazon.com/sagemaker/latest/dg/security.html", severity="High", status="Failed", + region=region, ) ] } -def check_sagemaker_mlops_utilization(permission_cache) -> Dict[str, Any]: +def check_sagemaker_mlops_utilization(permission_cache, region: str = "") -> Dict[str, Any]: """ Check if SageMaker MLOps features (Model Registry, Feature Store, and Pipelines) are being utilized properly @@ -792,7 +811,7 @@ def check_sagemaker_mlops_utilization(permission_cache) -> Dict[str, Any]: try: findings = {"csv_data": []} - sagemaker_client = boto3.client("sagemaker", config=boto3_config) + sagemaker_client = boto3.client("sagemaker", config=boto3_config, region_name=region) issues_found = [] # Check Model Registry Usage @@ -948,6 +967,7 @@ def check_sagemaker_mlops_utilization(permission_cache) -> Dict[str, Any]: reference="https://docs.aws.amazon.com/sagemaker/latest/dg/mlops.html", severity=issue["severity"], status=issue["status"], + region=region, ) ) else: @@ -961,6 +981,7 @@ def check_sagemaker_mlops_utilization(permission_cache) -> Dict[str, Any]: reference="https://docs.aws.amazon.com/sagemaker/latest/dg/mlops.html", severity="Low", status="Passed", + region=region, ) ) @@ -1001,7 +1022,7 @@ def get_resolution_for_component(component: str) -> str: ) -def check_sagemaker_clarify_usage(permission_cache) -> Dict[str, Any]: +def check_sagemaker_clarify_usage(permission_cache, region: str = "") -> Dict[str, Any]: """ Check if SageMaker Clarify is being used for bias detection and model explainability """ @@ -1009,7 +1030,7 @@ def check_sagemaker_clarify_usage(permission_cache) -> Dict[str, Any]: try: findings = {"csv_data": []} - sagemaker_client = boto3.client("sagemaker", config=boto3_config) + sagemaker_client = boto3.client("sagemaker", config=boto3_config, region_name=region) issues_found = [] try: @@ -1075,6 +1096,7 @@ def check_sagemaker_clarify_usage(permission_cache) -> Dict[str, Any]: reference="https://docs.aws.amazon.com/sagemaker/latest/dg/clarify-configure-processing-jobs.html", severity=issue["severity"], status=issue["status"], + region=region, ) ) else: @@ -1088,6 +1110,7 @@ def check_sagemaker_clarify_usage(permission_cache) -> Dict[str, Any]: reference="https://docs.aws.amazon.com/sagemaker/latest/dg/clarify-configure-processing-jobs.html", severity="Low", status="Passed", + region=region, ) ) @@ -1103,7 +1126,7 @@ def check_sagemaker_clarify_usage(permission_cache) -> Dict[str, Any]: } -def check_sagemaker_model_monitor_usage(permission_cache) -> Dict[str, Any]: +def check_sagemaker_model_monitor_usage(permission_cache, region: str = "") -> Dict[str, Any]: """ Check if SageMaker Model Monitor is configured and actively monitoring models """ @@ -1111,7 +1134,7 @@ def check_sagemaker_model_monitor_usage(permission_cache) -> Dict[str, Any]: try: findings = {"csv_data": []} - sagemaker_client = boto3.client("sagemaker", config=boto3_config) + sagemaker_client = boto3.client("sagemaker", config=boto3_config, region_name=region) issues_found = [] try: @@ -1170,6 +1193,7 @@ def check_sagemaker_model_monitor_usage(permission_cache) -> Dict[str, Any]: reference="https://docs.aws.amazon.com/sagemaker/latest/dg/model-monitor.html", severity=issue["severity"], status=issue["status"], + region=region, ) ) else: @@ -1182,6 +1206,7 @@ def check_sagemaker_model_monitor_usage(permission_cache) -> Dict[str, Any]: reference="https://docs.aws.amazon.com/sagemaker/latest/dg/model-monitor.html", severity="Medium", status="Passed", + region=region, ) ) @@ -1199,7 +1224,7 @@ def check_sagemaker_model_monitor_usage(permission_cache) -> Dict[str, Any]: } -def check_sagemaker_notebook_root_access() -> Dict[str, Any]: +def check_sagemaker_notebook_root_access(region: str = "") -> Dict[str, Any]: """ Check if SageMaker notebook instances have root access disabled. Root access enables privilege escalation and should be disabled for security. @@ -1209,7 +1234,7 @@ def check_sagemaker_notebook_root_access() -> Dict[str, Any]: try: findings = {"csv_data": []} - sagemaker_client = boto3.client("sagemaker", config=boto3_config) + sagemaker_client = boto3.client("sagemaker", config=boto3_config, region_name=region) notebooks_with_root = [] notebooks_without_root = [] @@ -1252,6 +1277,7 @@ def check_sagemaker_notebook_root_access() -> Dict[str, Any]: reference="https://docs.aws.amazon.com/sagemaker/latest/dg/nbi-root-access.html", severity="High", status="Failed", + region=region, ) ) else: @@ -1266,6 +1292,7 @@ def check_sagemaker_notebook_root_access() -> Dict[str, Any]: reference="https://docs.aws.amazon.com/sagemaker/latest/dg/nbi-root-access.html", severity="High", status="Passed", + region=region, ) ) else: @@ -1279,6 +1306,7 @@ def check_sagemaker_notebook_root_access() -> Dict[str, Any]: reference="https://docs.aws.amazon.com/sagemaker/latest/dg/nbi-root-access.html", severity="Informational", status="N/A", + region=region, ) ) @@ -1298,12 +1326,13 @@ def check_sagemaker_notebook_root_access() -> Dict[str, Any]: reference="https://docs.aws.amazon.com/sagemaker/latest/dg/security.html", severity="High", status="Failed", + region=region, ) ] } -def check_sagemaker_notebook_vpc_deployment() -> Dict[str, Any]: +def check_sagemaker_notebook_vpc_deployment(region: str = "") -> Dict[str, Any]: """ Check if SageMaker notebook instances are deployed within a custom VPC. Notebooks outside VPC use shared infrastructure with less isolation. @@ -1313,7 +1342,7 @@ def check_sagemaker_notebook_vpc_deployment() -> Dict[str, Any]: try: findings = {"csv_data": []} - sagemaker_client = boto3.client("sagemaker", config=boto3_config) + sagemaker_client = boto3.client("sagemaker", config=boto3_config, region_name=region) notebooks_without_vpc = [] notebooks_with_vpc = [] @@ -1362,6 +1391,7 @@ def check_sagemaker_notebook_vpc_deployment() -> Dict[str, Any]: reference="https://docs.aws.amazon.com/sagemaker/latest/dg/appendix-notebook-and-internet-access.html", severity="High", status="Failed", + region=region, ) ) else: @@ -1376,6 +1406,7 @@ def check_sagemaker_notebook_vpc_deployment() -> Dict[str, Any]: reference="https://docs.aws.amazon.com/sagemaker/latest/dg/appendix-notebook-and-internet-access.html", severity="High", status="Passed", + region=region, ) ) else: @@ -1389,6 +1420,7 @@ def check_sagemaker_notebook_vpc_deployment() -> Dict[str, Any]: reference="https://docs.aws.amazon.com/sagemaker/latest/dg/appendix-notebook-and-internet-access.html", severity="Informational", status="N/A", + region=region, ) ) @@ -1408,12 +1440,13 @@ def check_sagemaker_notebook_vpc_deployment() -> Dict[str, Any]: reference="https://docs.aws.amazon.com/sagemaker/latest/dg/security.html", severity="High", status="Failed", + region=region, ) ] } -def check_sagemaker_model_network_isolation() -> Dict[str, Any]: +def check_sagemaker_model_network_isolation(region: str = "") -> Dict[str, Any]: """ Check if SageMaker hosted models have network isolation enabled. Without isolation, model containers can make outbound calls and exfiltrate data. @@ -1423,7 +1456,7 @@ def check_sagemaker_model_network_isolation() -> Dict[str, Any]: try: findings = {"csv_data": []} - sagemaker_client = boto3.client("sagemaker", config=boto3_config) + sagemaker_client = boto3.client("sagemaker", config=boto3_config, region_name=region) models_without_isolation = [] models_with_isolation = [] @@ -1475,6 +1508,7 @@ def check_sagemaker_model_network_isolation() -> Dict[str, Any]: reference="https://docs.aws.amazon.com/sagemaker/latest/dg/mkt-algo-model-internet-free.html", severity="High", status="Failed", + region=region, ) ) @@ -1488,6 +1522,7 @@ def check_sagemaker_model_network_isolation() -> Dict[str, Any]: reference="https://docs.aws.amazon.com/sagemaker/latest/dg/mkt-algo-model-internet-free.html", severity="High", status="Failed", + region=region, ) ) else: @@ -1502,6 +1537,7 @@ def check_sagemaker_model_network_isolation() -> Dict[str, Any]: reference="https://docs.aws.amazon.com/sagemaker/latest/dg/mkt-algo-model-internet-free.html", severity="Medium", status="Passed", + region=region, ) ) else: @@ -1515,6 +1551,7 @@ def check_sagemaker_model_network_isolation() -> Dict[str, Any]: reference="https://docs.aws.amazon.com/sagemaker/latest/dg/mkt-algo-model-internet-free.html", severity="Informational", status="N/A", + region=region, ) ) @@ -1534,12 +1571,13 @@ def check_sagemaker_model_network_isolation() -> Dict[str, Any]: reference="https://docs.aws.amazon.com/sagemaker/latest/dg/security.html", severity="High", status="Failed", + region=region, ) ] } -def check_sagemaker_endpoint_instance_count() -> Dict[str, Any]: +def check_sagemaker_endpoint_instance_count(region: str = "") -> Dict[str, Any]: """ Check if SageMaker endpoints have more than one instance for availability. Single instance creates availability risk and single point of compromise. @@ -1549,7 +1587,7 @@ def check_sagemaker_endpoint_instance_count() -> Dict[str, Any]: try: findings = {"csv_data": []} - sagemaker_client = boto3.client("sagemaker", config=boto3_config) + sagemaker_client = boto3.client("sagemaker", config=boto3_config, region_name=region) endpoints_single_instance = [] endpoints_multi_instance = [] @@ -1613,6 +1651,7 @@ def check_sagemaker_endpoint_instance_count() -> Dict[str, Any]: reference="https://docs.aws.amazon.com/sagemaker/latest/dg/endpoint-auto-scaling.html", severity="Medium", status="Failed", + region=region, ) ) else: @@ -1627,6 +1666,7 @@ def check_sagemaker_endpoint_instance_count() -> Dict[str, Any]: reference="https://docs.aws.amazon.com/sagemaker/latest/dg/endpoint-auto-scaling.html", severity="Medium", status="Passed", + region=region, ) ) else: @@ -1640,6 +1680,7 @@ def check_sagemaker_endpoint_instance_count() -> Dict[str, Any]: reference="https://docs.aws.amazon.com/sagemaker/latest/dg/endpoint-auto-scaling.html", severity="Informational", status="N/A", + region=region, ) ) @@ -1659,12 +1700,13 @@ def check_sagemaker_endpoint_instance_count() -> Dict[str, Any]: reference="https://docs.aws.amazon.com/sagemaker/latest/dg/security.html", severity="High", status="Failed", + region=region, ) ] } -def check_sagemaker_monitoring_network_isolation() -> Dict[str, Any]: +def check_sagemaker_monitoring_network_isolation(region: str = "") -> Dict[str, Any]: """ Check if SageMaker monitoring schedules have network isolation enabled. Aligns with AWS Security Hub control SageMaker.14 @@ -1673,7 +1715,7 @@ def check_sagemaker_monitoring_network_isolation() -> Dict[str, Any]: try: findings = {"csv_data": []} - sagemaker_client = boto3.client("sagemaker", config=boto3_config) + sagemaker_client = boto3.client("sagemaker", config=boto3_config, region_name=region) schedules_without_isolation = [] schedules_with_isolation = [] @@ -1730,6 +1772,7 @@ def check_sagemaker_monitoring_network_isolation() -> Dict[str, Any]: reference="https://docs.aws.amazon.com/sagemaker/latest/dg/model-monitor-network-isolation.html", severity="Medium", status="Failed", + region=region, ) ) else: @@ -1744,6 +1787,7 @@ def check_sagemaker_monitoring_network_isolation() -> Dict[str, Any]: reference="https://docs.aws.amazon.com/sagemaker/latest/dg/model-monitor-network-isolation.html", severity="Medium", status="Passed", + region=region, ) ) else: @@ -1757,6 +1801,7 @@ def check_sagemaker_monitoring_network_isolation() -> Dict[str, Any]: reference="https://docs.aws.amazon.com/sagemaker/latest/dg/model-monitor-network-isolation.html", severity="Informational", status="N/A", + region=region, ) ) @@ -1777,12 +1822,13 @@ def check_sagemaker_monitoring_network_isolation() -> Dict[str, Any]: reference="https://docs.aws.amazon.com/sagemaker/latest/dg/security.html", severity="High", status="Failed", + region=region, ) ] } -def check_sagemaker_model_container_repository() -> Dict[str, Any]: +def check_sagemaker_model_container_repository(region: str = "") -> Dict[str, Any]: """ Check if SageMaker models pull container images from private ECR in VPC. Using Platform mode exposes supply chain risks. @@ -1792,7 +1838,7 @@ def check_sagemaker_model_container_repository() -> Dict[str, Any]: try: findings = {"csv_data": []} - sagemaker_client = boto3.client("sagemaker", config=boto3_config) + sagemaker_client = boto3.client("sagemaker", config=boto3_config, region_name=region) models_platform_mode = [] models_vpc_mode = [] @@ -1873,6 +1919,7 @@ def check_sagemaker_model_container_repository() -> Dict[str, Any]: reference="https://docs.aws.amazon.com/sagemaker/latest/dg/model-container-repositories.html", severity="Medium", status="Failed", + region=region, ) ) @@ -1886,6 +1933,7 @@ def check_sagemaker_model_container_repository() -> Dict[str, Any]: reference="https://docs.aws.amazon.com/sagemaker/latest/dg/model-container-repositories.html", severity="Medium", status="Failed", + region=region, ) ) else: @@ -1900,6 +1948,7 @@ def check_sagemaker_model_container_repository() -> Dict[str, Any]: reference="https://docs.aws.amazon.com/sagemaker/latest/dg/model-container-repositories.html", severity="Medium", status="Passed", + region=region, ) ) else: @@ -1913,6 +1962,7 @@ def check_sagemaker_model_container_repository() -> Dict[str, Any]: reference="https://docs.aws.amazon.com/sagemaker/latest/dg/model-container-repositories.html", severity="Informational", status="N/A", + region=region, ) ) @@ -1933,12 +1983,13 @@ def check_sagemaker_model_container_repository() -> Dict[str, Any]: reference="https://docs.aws.amazon.com/sagemaker/latest/dg/security.html", severity="High", status="Failed", + region=region, ) ] } -def check_sagemaker_feature_store_encryption() -> Dict[str, Any]: +def check_sagemaker_feature_store_encryption(region: str = "") -> Dict[str, Any]: """ Check if SageMaker Feature Store offline stores have KMS encryption. Aligns with AWS Security Hub control SageMaker.17 @@ -1947,7 +1998,7 @@ def check_sagemaker_feature_store_encryption() -> Dict[str, Any]: try: findings = {"csv_data": []} - sagemaker_client = boto3.client("sagemaker", config=boto3_config) + sagemaker_client = boto3.client("sagemaker", config=boto3_config, region_name=region) feature_groups_without_encryption = [] feature_groups_with_encryption = [] @@ -2004,6 +2055,7 @@ def check_sagemaker_feature_store_encryption() -> Dict[str, Any]: reference="https://docs.aws.amazon.com/sagemaker/latest/dg/feature-store-security.html", severity="Medium", status="Failed", + region=region, ) ) else: @@ -2017,6 +2069,7 @@ def check_sagemaker_feature_store_encryption() -> Dict[str, Any]: reference="https://docs.aws.amazon.com/sagemaker/latest/dg/feature-store-security.html", severity="High", status="Passed", + region=region, ) ) else: @@ -2030,6 +2083,7 @@ def check_sagemaker_feature_store_encryption() -> Dict[str, Any]: reference="https://docs.aws.amazon.com/sagemaker/latest/dg/feature-store-security.html", severity="Informational", status="N/A", + region=region, ) ) @@ -2050,12 +2104,13 @@ def check_sagemaker_feature_store_encryption() -> Dict[str, Any]: reference="https://docs.aws.amazon.com/sagemaker/latest/dg/security.html", severity="High", status="Failed", + region=region, ) ] } -def check_sagemaker_data_quality_encryption() -> Dict[str, Any]: +def check_sagemaker_data_quality_encryption(region: str = "") -> Dict[str, Any]: """ Check if SageMaker data quality job definitions have inter-container traffic encryption. Aligns with AWS Security Hub control SageMaker.9 @@ -2064,7 +2119,7 @@ def check_sagemaker_data_quality_encryption() -> Dict[str, Any]: try: findings = {"csv_data": []} - sagemaker_client = boto3.client("sagemaker", config=boto3_config) + sagemaker_client = boto3.client("sagemaker", config=boto3_config, region_name=region) jobs_without_encryption = [] jobs_with_encryption = [] @@ -2114,6 +2169,7 @@ def check_sagemaker_data_quality_encryption() -> Dict[str, Any]: reference="https://docs.aws.amazon.com/sagemaker/latest/dg/model-monitor-data-quality.html", severity="Medium", status="Failed", + region=region, ) ) else: @@ -2128,6 +2184,7 @@ def check_sagemaker_data_quality_encryption() -> Dict[str, Any]: reference="https://docs.aws.amazon.com/sagemaker/latest/dg/model-monitor-data-quality.html", severity="Medium", status="Passed", + region=region, ) ) else: @@ -2141,6 +2198,7 @@ def check_sagemaker_data_quality_encryption() -> Dict[str, Any]: reference="https://docs.aws.amazon.com/sagemaker/latest/dg/model-monitor-data-quality.html", severity="Informational", status="N/A", + region=region, ) ) @@ -2160,12 +2218,13 @@ def check_sagemaker_data_quality_encryption() -> Dict[str, Any]: reference="https://docs.aws.amazon.com/sagemaker/latest/dg/security.html", severity="High", status="Failed", + region=region, ) ] } -def check_sagemaker_processing_job_encryption() -> Dict[str, Any]: +def check_sagemaker_processing_job_encryption(region: str = "") -> Dict[str, Any]: """ Check if SageMaker processing jobs have volume encryption enabled. Aligns with AWS Security Hub control SageMaker.10 @@ -2174,7 +2233,7 @@ def check_sagemaker_processing_job_encryption() -> Dict[str, Any]: try: findings = {"csv_data": []} - sagemaker_client = boto3.client("sagemaker", config=boto3_config) + sagemaker_client = boto3.client("sagemaker", config=boto3_config, region_name=region) jobs_without_encryption = [] jobs_with_encryption = [] @@ -2226,6 +2285,7 @@ def check_sagemaker_processing_job_encryption() -> Dict[str, Any]: reference="https://docs.aws.amazon.com/sagemaker/latest/dg/processing-job.html", severity="Medium", status="Failed", + region=region, ) ) @@ -2239,6 +2299,7 @@ def check_sagemaker_processing_job_encryption() -> Dict[str, Any]: reference="https://docs.aws.amazon.com/sagemaker/latest/dg/processing-job.html", severity="Medium", status="Failed", + region=region, ) ) else: @@ -2253,6 +2314,7 @@ def check_sagemaker_processing_job_encryption() -> Dict[str, Any]: reference="https://docs.aws.amazon.com/sagemaker/latest/dg/processing-job.html", severity="Medium", status="Passed", + region=region, ) ) else: @@ -2266,6 +2328,7 @@ def check_sagemaker_processing_job_encryption() -> Dict[str, Any]: reference="https://docs.aws.amazon.com/sagemaker/latest/dg/processing-job.html", severity="Informational", status="N/A", + region=region, ) ) @@ -2286,12 +2349,13 @@ def check_sagemaker_processing_job_encryption() -> Dict[str, Any]: reference="https://docs.aws.amazon.com/sagemaker/latest/dg/security.html", severity="High", status="Failed", + region=region, ) ] } -def check_sagemaker_transform_job_encryption() -> Dict[str, Any]: +def check_sagemaker_transform_job_encryption(region: str = "") -> Dict[str, Any]: """ Check if SageMaker transform jobs have volume encryption enabled. Aligns with AWS Security Hub control SageMaker.11 @@ -2300,7 +2364,7 @@ def check_sagemaker_transform_job_encryption() -> Dict[str, Any]: try: findings = {"csv_data": []} - sagemaker_client = boto3.client("sagemaker", config=boto3_config) + sagemaker_client = boto3.client("sagemaker", config=boto3_config, region_name=region) jobs_without_encryption = [] jobs_with_encryption = [] @@ -2349,6 +2413,7 @@ def check_sagemaker_transform_job_encryption() -> Dict[str, Any]: reference="https://docs.aws.amazon.com/sagemaker/latest/dg/batch-transform.html", severity="Medium", status="Failed", + region=region, ) ) @@ -2362,6 +2427,7 @@ def check_sagemaker_transform_job_encryption() -> Dict[str, Any]: reference="https://docs.aws.amazon.com/sagemaker/latest/dg/batch-transform.html", severity="Medium", status="Failed", + region=region, ) ) else: @@ -2376,6 +2442,7 @@ def check_sagemaker_transform_job_encryption() -> Dict[str, Any]: reference="https://docs.aws.amazon.com/sagemaker/latest/dg/batch-transform.html", severity="Medium", status="Passed", + region=region, ) ) else: @@ -2389,6 +2456,7 @@ def check_sagemaker_transform_job_encryption() -> Dict[str, Any]: reference="https://docs.aws.amazon.com/sagemaker/latest/dg/batch-transform.html", severity="Informational", status="N/A", + region=region, ) ) @@ -2409,12 +2477,13 @@ def check_sagemaker_transform_job_encryption() -> Dict[str, Any]: reference="https://docs.aws.amazon.com/sagemaker/latest/dg/security.html", severity="High", status="Failed", + region=region, ) ] } -def check_sagemaker_hyperparameter_tuning_encryption() -> Dict[str, Any]: +def check_sagemaker_hyperparameter_tuning_encryption(region: str = "") -> Dict[str, Any]: """ Check if SageMaker hyperparameter tuning jobs have volume encryption enabled. Aligns with AWS Security Hub control SageMaker.12 @@ -2423,7 +2492,7 @@ def check_sagemaker_hyperparameter_tuning_encryption() -> Dict[str, Any]: try: findings = {"csv_data": []} - sagemaker_client = boto3.client("sagemaker", config=boto3_config) + sagemaker_client = boto3.client("sagemaker", config=boto3_config, region_name=region) jobs_without_encryption = [] jobs_with_encryption = [] @@ -2479,6 +2548,7 @@ def check_sagemaker_hyperparameter_tuning_encryption() -> Dict[str, Any]: reference="https://docs.aws.amazon.com/sagemaker/latest/dg/automatic-model-tuning.html", severity="Medium", status="Failed", + region=region, ) ) @@ -2492,6 +2562,7 @@ def check_sagemaker_hyperparameter_tuning_encryption() -> Dict[str, Any]: reference="https://docs.aws.amazon.com/sagemaker/latest/dg/automatic-model-tuning.html", severity="Medium", status="Failed", + region=region, ) ) else: @@ -2506,6 +2577,7 @@ def check_sagemaker_hyperparameter_tuning_encryption() -> Dict[str, Any]: reference="https://docs.aws.amazon.com/sagemaker/latest/dg/automatic-model-tuning.html", severity="Medium", status="Passed", + region=region, ) ) else: @@ -2519,6 +2591,7 @@ def check_sagemaker_hyperparameter_tuning_encryption() -> Dict[str, Any]: reference="https://docs.aws.amazon.com/sagemaker/latest/dg/automatic-model-tuning.html", severity="Informational", status="N/A", + region=region, ) ) @@ -2539,12 +2612,13 @@ def check_sagemaker_hyperparameter_tuning_encryption() -> Dict[str, Any]: reference="https://docs.aws.amazon.com/sagemaker/latest/dg/security.html", severity="High", status="Failed", + region=region, ) ] } -def check_sagemaker_compilation_job_encryption() -> Dict[str, Any]: +def check_sagemaker_compilation_job_encryption(region: str = "") -> Dict[str, Any]: """ Check if SageMaker compilation jobs have volume encryption enabled. Aligns with AWS Security Hub control SageMaker.13 @@ -2553,7 +2627,7 @@ def check_sagemaker_compilation_job_encryption() -> Dict[str, Any]: try: findings = {"csv_data": []} - sagemaker_client = boto3.client("sagemaker", config=boto3_config) + sagemaker_client = boto3.client("sagemaker", config=boto3_config, region_name=region) jobs_without_encryption = [] jobs_with_encryption = [] @@ -2600,6 +2674,7 @@ def check_sagemaker_compilation_job_encryption() -> Dict[str, Any]: reference="https://docs.aws.amazon.com/sagemaker/latest/dg/neo.html", severity="Medium", status="Failed", + region=region, ) ) @@ -2613,6 +2688,7 @@ def check_sagemaker_compilation_job_encryption() -> Dict[str, Any]: reference="https://docs.aws.amazon.com/sagemaker/latest/dg/neo.html", severity="Medium", status="Failed", + region=region, ) ) else: @@ -2627,6 +2703,7 @@ def check_sagemaker_compilation_job_encryption() -> Dict[str, Any]: reference="https://docs.aws.amazon.com/sagemaker/latest/dg/neo.html", severity="Medium", status="Passed", + region=region, ) ) else: @@ -2640,6 +2717,7 @@ def check_sagemaker_compilation_job_encryption() -> Dict[str, Any]: reference="https://docs.aws.amazon.com/sagemaker/latest/dg/neo.html", severity="Informational", status="N/A", + region=region, ) ) @@ -2660,12 +2738,13 @@ def check_sagemaker_compilation_job_encryption() -> Dict[str, Any]: reference="https://docs.aws.amazon.com/sagemaker/latest/dg/security.html", severity="High", status="Failed", + region=region, ) ] } -def check_sagemaker_automl_network_isolation() -> Dict[str, Any]: +def check_sagemaker_automl_network_isolation(region: str = "") -> Dict[str, Any]: """ Check if SageMaker AutoML (Autopilot) jobs have network isolation enabled. Aligns with AWS Security Hub control SageMaker.15 @@ -2674,7 +2753,7 @@ def check_sagemaker_automl_network_isolation() -> Dict[str, Any]: try: findings = {"csv_data": []} - sagemaker_client = boto3.client("sagemaker", config=boto3_config) + sagemaker_client = boto3.client("sagemaker", config=boto3_config, region_name=region) jobs_without_isolation = [] jobs_with_isolation = [] @@ -2725,6 +2804,7 @@ def check_sagemaker_automl_network_isolation() -> Dict[str, Any]: reference="https://docs.aws.amazon.com/sagemaker/latest/dg/autopilot-security.html", severity="Medium", status="Failed", + region=region, ) ) @@ -2738,6 +2818,7 @@ def check_sagemaker_automl_network_isolation() -> Dict[str, Any]: reference="https://docs.aws.amazon.com/sagemaker/latest/dg/autopilot-security.html", severity="Medium", status="Failed", + region=region, ) ) else: @@ -2752,6 +2833,7 @@ def check_sagemaker_automl_network_isolation() -> Dict[str, Any]: reference="https://docs.aws.amazon.com/sagemaker/latest/dg/autopilot-security.html", severity="Medium", status="Passed", + region=region, ) ) else: @@ -2765,6 +2847,7 @@ def check_sagemaker_automl_network_isolation() -> Dict[str, Any]: reference="https://docs.aws.amazon.com/sagemaker/latest/dg/autopilot-security.html", severity="Informational", status="N/A", + region=region, ) ) @@ -2785,6 +2868,7 @@ def check_sagemaker_automl_network_isolation() -> Dict[str, Any]: reference="https://docs.aws.amazon.com/sagemaker/latest/dg/security.html", severity="High", status="Failed", + region=region, ) ] } @@ -2795,7 +2879,7 @@ def check_sagemaker_automl_network_isolation() -> Dict[str, Any]: # ============================================================================ -def check_model_approval_workflow() -> Dict[str, Any]: +def check_model_approval_workflow(region: str = "") -> Dict[str, Any]: """ Check if Model Registry has proper approval workflows configured. Validates that models go through approval process before production deployment. @@ -2804,7 +2888,7 @@ def check_model_approval_workflow() -> Dict[str, Any]: try: findings = {"csv_data": []} - sagemaker_client = boto3.client("sagemaker", config=boto3_config) + sagemaker_client = boto3.client("sagemaker", config=boto3_config, region_name=region) issues_found = [] groups_checked = 0 @@ -2889,6 +2973,7 @@ def check_model_approval_workflow() -> Dict[str, Any]: reference="https://docs.aws.amazon.com/sagemaker/latest/dg/model-registry-approve.html", severity="Informational", status="N/A", + region=region, ) ) elif issues_found: @@ -2902,6 +2987,7 @@ def check_model_approval_workflow() -> Dict[str, Any]: reference="https://docs.aws.amazon.com/sagemaker/latest/dg/model-registry-approve.html", severity=issue["severity"], status="Failed", + region=region, ) ) else: @@ -2914,6 +3000,7 @@ def check_model_approval_workflow() -> Dict[str, Any]: reference="https://docs.aws.amazon.com/sagemaker/latest/dg/model-registry-approve.html", severity="Medium", status="Passed", + region=region, ) ) @@ -2931,12 +3018,13 @@ def check_model_approval_workflow() -> Dict[str, Any]: reference="https://docs.aws.amazon.com/sagemaker/latest/dg/security.html", severity="High", status="Failed", + region=region, ) ] } -def check_model_drift_detection() -> Dict[str, Any]: +def check_model_drift_detection(region: str = "") -> Dict[str, Any]: """ Check if Model Monitor is configured for drift detection with proper baselines. Validates that models have data quality and model quality monitoring configured. @@ -2945,7 +3033,7 @@ def check_model_drift_detection() -> Dict[str, Any]: try: findings = {"csv_data": []} - sagemaker_client = boto3.client("sagemaker", config=boto3_config) + sagemaker_client = boto3.client("sagemaker", config=boto3_config, region_name=region) endpoints_without_monitoring = [] endpoints_with_monitoring = [] @@ -3038,6 +3126,7 @@ def check_model_drift_detection() -> Dict[str, Any]: reference="https://docs.aws.amazon.com/sagemaker/latest/dg/model-monitor.html", severity="Medium", status="Failed", + region=region, ) ) @@ -3051,6 +3140,7 @@ def check_model_drift_detection() -> Dict[str, Any]: reference="https://docs.aws.amazon.com/sagemaker/latest/dg/model-monitor.html", severity="Medium", status="Failed", + region=region, ) ) @@ -3065,6 +3155,7 @@ def check_model_drift_detection() -> Dict[str, Any]: reference="https://docs.aws.amazon.com/sagemaker/latest/dg/model-monitor.html", severity="Low", status="Failed", + region=region, ) ) @@ -3079,6 +3170,7 @@ def check_model_drift_detection() -> Dict[str, Any]: reference="https://docs.aws.amazon.com/sagemaker/latest/dg/model-monitor.html", severity="Medium", status="Passed", + region=region, ) ) else: @@ -3091,6 +3183,7 @@ def check_model_drift_detection() -> Dict[str, Any]: reference="https://docs.aws.amazon.com/sagemaker/latest/dg/model-monitor.html", severity="Medium", status="Passed", + region=region, ) ) @@ -3108,12 +3201,13 @@ def check_model_drift_detection() -> Dict[str, Any]: reference="https://docs.aws.amazon.com/sagemaker/latest/dg/security.html", severity="High", status="Failed", + region=region, ) ] } -def check_ab_testing_shadow_deployment() -> Dict[str, Any]: +def check_ab_testing_shadow_deployment(region: str = "") -> Dict[str, Any]: """ Check if endpoints are configured with proper A/B testing or shadow deployment patterns. Validates production variant configurations for safe model deployment. @@ -3122,7 +3216,7 @@ def check_ab_testing_shadow_deployment() -> Dict[str, Any]: try: findings = {"csv_data": []} - sagemaker_client = boto3.client("sagemaker", config=boto3_config) + sagemaker_client = boto3.client("sagemaker", config=boto3_config, region_name=region) single_variant_endpoints = [] multi_variant_endpoints = [] @@ -3200,6 +3294,7 @@ def check_ab_testing_shadow_deployment() -> Dict[str, Any]: reference="https://docs.aws.amazon.com/sagemaker/latest/dg/model-ab-testing.html", severity="Low", status="Passed", + region=region, ) ) else: @@ -3214,6 +3309,7 @@ def check_ab_testing_shadow_deployment() -> Dict[str, Any]: reference="https://docs.aws.amazon.com/sagemaker/latest/dg/model-shadow-deployment.html", severity="Low", status="Passed", + region=region, ) ) @@ -3228,6 +3324,7 @@ def check_ab_testing_shadow_deployment() -> Dict[str, Any]: reference="https://docs.aws.amazon.com/sagemaker/latest/dg/model-ab-testing.html", severity="Low", status="Passed", + region=region, ) ) @@ -3242,6 +3339,7 @@ def check_ab_testing_shadow_deployment() -> Dict[str, Any]: reference="https://docs.aws.amazon.com/sagemaker/latest/dg/model-ab-testing.html", severity="Informational", status="N/A", + region=region, ) ) elif not shadow_endpoints and not multi_variant_endpoints: @@ -3254,6 +3352,7 @@ def check_ab_testing_shadow_deployment() -> Dict[str, Any]: reference="https://docs.aws.amazon.com/sagemaker/latest/dg/model-ab-testing.html", severity="Informational", status="N/A", + region=region, ) ) else: @@ -3266,6 +3365,7 @@ def check_ab_testing_shadow_deployment() -> Dict[str, Any]: reference="https://docs.aws.amazon.com/sagemaker/latest/dg/model-ab-testing.html", severity="Low", status="Passed", + region=region, ) ) @@ -3285,12 +3385,13 @@ def check_ab_testing_shadow_deployment() -> Dict[str, Any]: reference="https://docs.aws.amazon.com/sagemaker/latest/dg/security.html", severity="High", status="Failed", + region=region, ) ] } -def check_ml_lineage_tracking() -> Dict[str, Any]: +def check_ml_lineage_tracking(region: str = "") -> Dict[str, Any]: """ Check if ML Lineage Tracking is being used to track model artifacts and experiments. Validates that experiments, trials, and artifact associations are configured. @@ -3299,7 +3400,7 @@ def check_ml_lineage_tracking() -> Dict[str, Any]: try: findings = {"csv_data": []} - sagemaker_client = boto3.client("sagemaker", config=boto3_config) + sagemaker_client = boto3.client("sagemaker", config=boto3_config, region_name=region) experiments_found = False trials_found = False @@ -3388,6 +3489,7 @@ def check_ml_lineage_tracking() -> Dict[str, Any]: reference="https://docs.aws.amazon.com/sagemaker/latest/dg/experiments.html", severity="Informational", status="N/A", + region=region, ) ) elif not trials_found: @@ -3400,6 +3502,7 @@ def check_ml_lineage_tracking() -> Dict[str, Any]: reference="https://docs.aws.amazon.com/sagemaker/latest/dg/experiments.html", severity="Low", status="Failed", + region=region, ) ) else: @@ -3412,6 +3515,7 @@ def check_ml_lineage_tracking() -> Dict[str, Any]: reference="https://docs.aws.amazon.com/sagemaker/latest/dg/experiments.html", severity="Low", status="Passed", + region=region, ) ) @@ -3426,6 +3530,7 @@ def check_ml_lineage_tracking() -> Dict[str, Any]: reference="https://docs.aws.amazon.com/sagemaker/latest/dg/lineage-tracking.html", severity="Informational", status="N/A", + region=region, ) ) @@ -3443,12 +3548,13 @@ def check_ml_lineage_tracking() -> Dict[str, Any]: reference="https://docs.aws.amazon.com/sagemaker/latest/dg/security.html", severity="High", status="Failed", + region=region, ) ] } -def check_model_registry_usage(permission_cache) -> Dict[str, Any]: +def check_model_registry_usage(permission_cache, region: str = "") -> Dict[str, Any]: """ Check if Amazon Model Registry is being used effectively for model management """ @@ -3456,7 +3562,7 @@ def check_model_registry_usage(permission_cache) -> Dict[str, Any]: try: findings = {"csv_data": []} - sagemaker_client = boto3.client("sagemaker", config=boto3_config) + sagemaker_client = boto3.client("sagemaker", config=boto3_config, region_name=region) issues_found = [] try: @@ -3546,6 +3652,7 @@ def check_model_registry_usage(permission_cache) -> Dict[str, Any]: reference="https://docs.aws.amazon.com/sagemaker/latest/dg/model-registry.html", severity=issue["severity"], status=issue["status"], + region=region, ) ) else: @@ -3558,6 +3665,7 @@ def check_model_registry_usage(permission_cache) -> Dict[str, Any]: reference="https://docs.aws.amazon.com/sagemaker/latest/dg/model-registry.html", severity="Medium", status="Passed", + region=region, ) ) @@ -3646,6 +3754,7 @@ def generate_csv_report(findings: List[Dict[str, Any]]) -> str: "Reference", "Severity", "Status", + "Region", ] writer = csv.DictWriter(csv_buffer, fieldnames=fieldnames) @@ -3662,7 +3771,7 @@ def get_current_utc_date(): return datetime.now(timezone.utc).strftime("%Y/%m/%d") -def write_to_s3(execution_id, csv_content: str, bucket_name: str) -> Dict[str, str]: +def write_to_s3(execution_id, csv_content: str, bucket_name: str, region: str = "") -> Dict[str, str]: """ Write CSV reports to S3 bucket """ @@ -3670,8 +3779,10 @@ def write_to_s3(execution_id, csv_content: str, bucket_name: str) -> Dict[str, s try: s3_client = boto3.client("s3", config=boto3_config) - # Upload CSV file - csv_file_name = f"sagemaker_security_report_{execution_id}.csv" + if region: + csv_file_name = f"sagemaker_security_report_{execution_id}_{region}.csv" + else: + csv_file_name = f"sagemaker_security_report_{execution_id}.csv" s3_client.put_object( Bucket=bucket_name, Key=csv_file_name, @@ -3695,6 +3806,41 @@ def lambda_handler(event, context): all_findings = [] try: + # Extract target region from Step Functions Map state + region = event.get("Region", os.environ.get("AWS_REGION", "us-east-1")) + logger.info(f"Scanning region: {region}") + + # Verify SageMaker is available in this region + try: + test_client = boto3.client("sagemaker", config=boto3_config, region_name=region) + test_client.list_notebook_instances(MaxResults=1) + except (EndpointConnectionError, Exception) as e: + error_msg = str(e) + if "Could not connect to the endpoint URL" in error_msg or "EndpointConnectionError" in type(e).__name__: + logger.info(f"SageMaker service not available in region {region}, skipping") + all_findings.append({ + "check_name": "SageMaker Service Availability", + "status": "N/A", + "details": f"SageMaker is not available in region {region}", + "csv_data": [ + create_finding( + check_id="SM-00", + finding_name="SageMaker Service Availability", + finding_details=f"Amazon SageMaker is not available in region {region}. No checks performed.", + resolution="No action required. SageMaker is not deployed in this region.", + reference="https://docs.aws.amazon.com/general/latest/gr/sagemaker.html", + severity="Informational", + status="N/A", + region=region, + ) + ], + }) + execution_id = event["Execution"]["Name"] + csv_content = generate_csv_report(all_findings) + bucket_name = os.environ.get("AIML_ASSESSMENT_BUCKET_NAME") + s3_url = write_to_s3(execution_id, csv_content, bucket_name, region=region) + return {"statusCode": 200, "body": {"message": f"SageMaker not available in {region}", "report_url": s3_url}} + # Initialize permission cache logger.info("Initializing IAM permission cache") execution_id = event["Execution"]["Name"] @@ -3707,113 +3853,113 @@ def lambda_handler(event, context): permission_cache = {"role_permissions": {}, "user_permissions": {}} logger.info("Running SageMaker internet access check") - sagemaker_internet_access_findings = check_sagemaker_internet_access() + sagemaker_internet_access_findings = check_sagemaker_internet_access(region=region) all_findings.append(sagemaker_internet_access_findings) logger.info("Running SageMaker IAM permissions check") - sagemaker_iam_findings = check_sagemaker_iam_permissions(permission_cache) + sagemaker_iam_findings = check_sagemaker_iam_permissions(permission_cache, region=region) all_findings.append(sagemaker_iam_findings) logger.info("Running SageMaker data protection check") - sagemaker_data_protection_findings = check_sagemaker_data_protection() + sagemaker_data_protection_findings = check_sagemaker_data_protection(region=region) all_findings.append(sagemaker_data_protection_findings) logger.info("Running GuardDuty SageMaker monitoring check") - guardduty_findings = check_guardduty_enabled() + guardduty_findings = check_guardduty_enabled(region=region) all_findings.append(guardduty_findings) logger.info("Running SageMaker MLOps features utilization check") - mlops_findings = check_sagemaker_mlops_utilization(permission_cache) + mlops_findings = check_sagemaker_mlops_utilization(permission_cache, region=region) all_findings.append(mlops_findings) logger.info("Running SageMaker Clarify usage check") - clarify_findings = check_sagemaker_clarify_usage(permission_cache) + clarify_findings = check_sagemaker_clarify_usage(permission_cache, region=region) all_findings.append(clarify_findings) logger.info("Running SageMaker Model Monitor usage check") - monitor_findings = check_sagemaker_model_monitor_usage(permission_cache) + monitor_findings = check_sagemaker_model_monitor_usage(permission_cache, region=region) all_findings.append(monitor_findings) logger.info("Running Model Registry usage check") - registry_findings = check_model_registry_usage(permission_cache) + registry_findings = check_model_registry_usage(permission_cache, region=region) all_findings.append(registry_findings) logger.info("Running SageMaker notebook root access check") - notebook_root_findings = check_sagemaker_notebook_root_access() + notebook_root_findings = check_sagemaker_notebook_root_access(region=region) all_findings.append(notebook_root_findings) logger.info("Running SageMaker notebook VPC deployment check") - notebook_vpc_findings = check_sagemaker_notebook_vpc_deployment() + notebook_vpc_findings = check_sagemaker_notebook_vpc_deployment(region=region) all_findings.append(notebook_vpc_findings) logger.info("Running SageMaker model network isolation check") - model_isolation_findings = check_sagemaker_model_network_isolation() + model_isolation_findings = check_sagemaker_model_network_isolation(region=region) all_findings.append(model_isolation_findings) logger.info("Running SageMaker endpoint instance count check") - endpoint_instance_findings = check_sagemaker_endpoint_instance_count() + endpoint_instance_findings = check_sagemaker_endpoint_instance_count(region=region) all_findings.append(endpoint_instance_findings) logger.info("Running SageMaker monitoring network isolation check") - monitoring_isolation_findings = check_sagemaker_monitoring_network_isolation() + monitoring_isolation_findings = check_sagemaker_monitoring_network_isolation(region=region) all_findings.append(monitoring_isolation_findings) logger.info("Running SageMaker model container repository check") - model_repository_findings = check_sagemaker_model_container_repository() + model_repository_findings = check_sagemaker_model_container_repository(region=region) all_findings.append(model_repository_findings) logger.info("Running SageMaker Feature Store encryption check") - feature_store_encryption_findings = check_sagemaker_feature_store_encryption() + feature_store_encryption_findings = check_sagemaker_feature_store_encryption(region=region) all_findings.append(feature_store_encryption_findings) logger.info("Running SageMaker data quality job encryption check") - data_quality_encryption_findings = check_sagemaker_data_quality_encryption() + data_quality_encryption_findings = check_sagemaker_data_quality_encryption(region=region) all_findings.append(data_quality_encryption_findings) # Additional AWS Security Hub Controls logger.info("Running SageMaker processing job encryption check (SageMaker.10)") - processing_job_encryption_findings = check_sagemaker_processing_job_encryption() + processing_job_encryption_findings = check_sagemaker_processing_job_encryption(region=region) all_findings.append(processing_job_encryption_findings) logger.info("Running SageMaker transform job encryption check (SageMaker.11)") - transform_job_encryption_findings = check_sagemaker_transform_job_encryption() + transform_job_encryption_findings = check_sagemaker_transform_job_encryption(region=region) all_findings.append(transform_job_encryption_findings) logger.info( "Running SageMaker hyperparameter tuning job encryption check (SageMaker.12)" ) hyperparameter_tuning_encryption_findings = ( - check_sagemaker_hyperparameter_tuning_encryption() + check_sagemaker_hyperparameter_tuning_encryption(region=region) ) all_findings.append(hyperparameter_tuning_encryption_findings) logger.info("Running SageMaker compilation job encryption check (SageMaker.13)") compilation_job_encryption_findings = ( - check_sagemaker_compilation_job_encryption() + check_sagemaker_compilation_job_encryption(region=region) ) all_findings.append(compilation_job_encryption_findings) logger.info( "Running SageMaker AutoML job network isolation check (SageMaker.15)" ) - automl_network_isolation_findings = check_sagemaker_automl_network_isolation() + automl_network_isolation_findings = check_sagemaker_automl_network_isolation(region=region) all_findings.append(automl_network_isolation_findings) # Model Governance Checks logger.info("Running model approval workflow check") - model_approval_workflow_findings = check_model_approval_workflow() + model_approval_workflow_findings = check_model_approval_workflow(region=region) all_findings.append(model_approval_workflow_findings) logger.info("Running model drift detection check") - model_drift_detection_findings = check_model_drift_detection() + model_drift_detection_findings = check_model_drift_detection(region=region) all_findings.append(model_drift_detection_findings) logger.info("Running A/B testing and shadow deployment check") - ab_testing_findings = check_ab_testing_shadow_deployment() + ab_testing_findings = check_ab_testing_shadow_deployment(region=region) all_findings.append(ab_testing_findings) logger.info("Running ML lineage tracking check") - ml_lineage_tracking_findings = check_ml_lineage_tracking() + ml_lineage_tracking_findings = check_ml_lineage_tracking(region=region) all_findings.append(ml_lineage_tracking_findings) # Generate and upload report @@ -3827,7 +3973,7 @@ def lambda_handler(event, context): ) logger.info("Writing reports to S3") - s3_url = write_to_s3(execution_id, csv_content, bucket_name) + s3_url = write_to_s3(execution_id, csv_content, bucket_name, region=region) return { "statusCode": 200, diff --git a/aiml-security-assessment/functions/security/sagemaker_assessments/schema.py b/aiml-security-assessment/functions/security/sagemaker_assessments/schema.py index bb7a07f..c767db7 100644 --- a/aiml-security-assessment/functions/security/sagemaker_assessments/schema.py +++ b/aiml-security-assessment/functions/security/sagemaker_assessments/schema.py @@ -27,6 +27,7 @@ class Finding(BaseModel): Reference: str = Field(..., description="Documentation reference URL") Severity: SeverityEnum = Field(..., description="Severity level of the finding") Status: StatusEnum = Field(..., description="Current status of the finding") + Region: str = Field(default="", description="AWS region where the finding was identified") @validator('Check_ID') def validate_check_id(cls, v): @@ -64,7 +65,8 @@ def create_finding( resolution: str, reference: str, severity: SeverityEnum, - status: StatusEnum + status: StatusEnum, + region: str = "" ) -> Dict[str, Any]: """ Create a validated finding object @@ -77,6 +79,7 @@ def create_finding( reference: Documentation URL severity: Severity level status: Current status + region: AWS region where the finding was identified Returns: Dict[str, Any]: Validated finding as dictionary @@ -91,6 +94,7 @@ def create_finding( Resolution=resolution, Reference=reference, Severity=severity, - Status=status + Status=status, + Region=region ) return dict(finding.model_dump()) # Convert to regular dictionary \ No newline at end of file diff --git a/aiml-security-assessment/statemachine/assessments.asl.json b/aiml-security-assessment/statemachine/assessments.asl.json index 81a48d8..f85a7c0 100644 --- a/aiml-security-assessment/statemachine/assessments.asl.json +++ b/aiml-security-assessment/statemachine/assessments.asl.json @@ -1,5 +1,5 @@ { - "Comment": "A state machine that performs AI/ML security assessments.", + "Comment": "A state machine that performs AI/ML security assessments across multiple regions.", "StartAt": "Cleanup S3 Bucket", "States": { "Cleanup S3 Bucket": { @@ -54,105 +54,158 @@ } ], "ResultPath": null, - "Next": "Run Security Assessments" + "Next": "Resolve Target Regions" }, - "Run Security Assessments": { - "Type": "Parallel", - "Branches": [ + "Resolve Target Regions": { + "Type": "Task", + "Resource": "arn:aws:states:::lambda:invoke", + "Parameters": { + "FunctionName": "${ResolveRegionsFunction}", + "Payload": { + "Execution.$": "$$.Execution", + "StateMachine.$": "$$.StateMachine" + } + }, + "Retry": [ { - "StartAt": "Bedrock Security Assessment", - "States": { - "Bedrock Security Assessment": { - "Type": "Task", - "Resource": "arn:aws:states:::lambda:invoke", - "Parameters": { - "FunctionName": "${BedrockSecurityAssessmentFunction}", - "Payload": { - "Execution.$": "$$.Execution", - "StateMachine.$": "$$.StateMachine" - } - }, - "Retry": [ - { - "ErrorEquals": [ - "Lambda.ServiceException", - "Lambda.AWSLambdaException", - "Lambda.SdkClientException", - "Lambda.TooManyRequestsException" - ], - "IntervalSeconds": 1, - "MaxAttempts": 3, - "BackoffRate": 2, - "JitterStrategy": "FULL" - } - ], - "End": true - } - } + "ErrorEquals": [ + "Lambda.ServiceException", + "Lambda.AWSLambdaException", + "Lambda.SdkClientException", + "Lambda.TooManyRequestsException" + ], + "IntervalSeconds": 1, + "MaxAttempts": 3, + "BackoffRate": 2, + "JitterStrategy": "FULL" + } + ], + "ResultPath": "$.ResolvedRegions", + "ResultSelector": { + "regions.$": "$.Payload.regions" + }, + "Next": "Scan Regions" + }, + "Scan Regions": { + "Type": "Map", + "ItemsPath": "$.ResolvedRegions.regions", + "ItemSelector": { + "Region.$": "$$.Map.Item.Value", + "Execution.$": "$$.Execution", + "StateMachine.$": "$$.StateMachine" + }, + "MaxConcurrency": 0, + "ItemProcessor": { + "ProcessorConfig": { + "Mode": "INLINE" }, - { - "StartAt": "Sagemaker Security Assessment", - "States": { - "Sagemaker Security Assessment": { - "Type": "Task", - "Resource": "arn:aws:states:::lambda:invoke", - "Parameters": { - "FunctionName": "${SagemakerSecurityAssessmentFunction}", - "Payload": { - "Execution.$": "$$.Execution", - "StateMachine.$": "$$.StateMachine" + "StartAt": "Run Security Assessments", + "States": { + "Run Security Assessments": { + "Type": "Parallel", + "Branches": [ + { + "StartAt": "Bedrock Security Assessment", + "States": { + "Bedrock Security Assessment": { + "Type": "Task", + "Resource": "arn:aws:states:::lambda:invoke", + "Parameters": { + "FunctionName": "${BedrockSecurityAssessmentFunction}", + "Payload": { + "Execution.$": "$.Execution", + "StateMachine.$": "$.StateMachine", + "Region.$": "$.Region" + } + }, + "Retry": [ + { + "ErrorEquals": [ + "Lambda.ServiceException", + "Lambda.AWSLambdaException", + "Lambda.SdkClientException", + "Lambda.TooManyRequestsException" + ], + "IntervalSeconds": 1, + "MaxAttempts": 3, + "BackoffRate": 2, + "JitterStrategy": "FULL" + } + ], + "End": true + } } }, - "Retry": [ - { - "ErrorEquals": [ - "Lambda.ServiceException", - "Lambda.AWSLambdaException", - "Lambda.SdkClientException", - "Lambda.TooManyRequestsException" - ], - "IntervalSeconds": 1, - "MaxAttempts": 3, - "BackoffRate": 2, - "JitterStrategy": "FULL" - } - ], - "End": true - } - } - }, - { - "StartAt": "AgentCore Security Assessment", - "States": { - "AgentCore Security Assessment": { - "Type": "Task", - "Resource": "arn:aws:states:::lambda:invoke", - "Parameters": { - "FunctionName": "${AgentCoreSecurityAssessmentFunction}", - "Payload": { - "Execution.$": "$$.Execution", - "StateMachine.$": "$$.StateMachine" + { + "StartAt": "Sagemaker Security Assessment", + "States": { + "Sagemaker Security Assessment": { + "Type": "Task", + "Resource": "arn:aws:states:::lambda:invoke", + "Parameters": { + "FunctionName": "${SagemakerSecurityAssessmentFunction}", + "Payload": { + "Execution.$": "$.Execution", + "StateMachine.$": "$.StateMachine", + "Region.$": "$.Region" + } + }, + "Retry": [ + { + "ErrorEquals": [ + "Lambda.ServiceException", + "Lambda.AWSLambdaException", + "Lambda.SdkClientException", + "Lambda.TooManyRequestsException" + ], + "IntervalSeconds": 1, + "MaxAttempts": 3, + "BackoffRate": 2, + "JitterStrategy": "FULL" + } + ], + "End": true + } } }, - "Retry": [ - { - "ErrorEquals": [ - "Lambda.ServiceException", - "Lambda.AWSLambdaException", - "Lambda.SdkClientException", - "Lambda.TooManyRequestsException" - ], - "IntervalSeconds": 1, - "MaxAttempts": 3, - "BackoffRate": 2, - "JitterStrategy": "FULL" + { + "StartAt": "AgentCore Security Assessment", + "States": { + "AgentCore Security Assessment": { + "Type": "Task", + "Resource": "arn:aws:states:::lambda:invoke", + "Parameters": { + "FunctionName": "${AgentCoreSecurityAssessmentFunction}", + "Payload": { + "Execution.$": "$.Execution", + "StateMachine.$": "$.StateMachine", + "Region.$": "$.Region" + } + }, + "Retry": [ + { + "ErrorEquals": [ + "Lambda.ServiceException", + "Lambda.AWSLambdaException", + "Lambda.SdkClientException", + "Lambda.TooManyRequestsException" + ], + "IntervalSeconds": 1, + "MaxAttempts": 3, + "BackoffRate": 2, + "JitterStrategy": "FULL" + } + ], + "End": true + } } - ], - "End": true - } + } + ], + "End": true } } - ], + }, + "ResultPath": null, "Next": "Generate Consolidated Report" }, "Generate Consolidated Report": { diff --git a/aiml-security-assessment/template-multi-account.yaml b/aiml-security-assessment/template-multi-account.yaml index a16af9d..2188736 100644 --- a/aiml-security-assessment/template-multi-account.yaml +++ b/aiml-security-assessment/template-multi-account.yaml @@ -5,6 +5,15 @@ Description: > Multi-account SAM Template for aiml-security-assessment +Parameters: + TargetRegions: + Type: String + Default: "" + Description: > + Comma-separated list of AWS regions to scan (e.g., us-east-1,us-west-2,eu-west-1). + Use 'all' to scan all regions where the service is available. + Leave empty to scan only the deployment region. + Resources: AIMLAssessmentStateMachine: Type: AWS::Serverless::StateMachine @@ -13,6 +22,7 @@ Resources: DefinitionSubstitutions: CleanupBucketFunction: !GetAtt CleanupBucketFunction.Arn IAMPermissionCachingFunction: !GetAtt IAMPermissionCachingFunction.Arn + ResolveRegionsFunction: !GetAtt ResolveRegionsFunction.Arn BedrockSecurityAssessmentFunction: !GetAtt BedrockSecurityAssessmentFunction.Arn SagemakerSecurityAssessmentFunction: !GetAtt SagemakerSecurityAssessmentFunction.Arn AgentCoreSecurityAssessmentFunction: !GetAtt AgentCoreSecurityAssessmentFunction.Arn @@ -21,10 +31,12 @@ Resources: Policies: - LambdaInvokePolicy: FunctionName: !Ref CleanupBucketFunction - - LambdaInvokePolicy: - FunctionName: !Ref BedrockSecurityAssessmentFunction - LambdaInvokePolicy: FunctionName: !Ref IAMPermissionCachingFunction + - LambdaInvokePolicy: + FunctionName: !Ref ResolveRegionsFunction + - LambdaInvokePolicy: + FunctionName: !Ref BedrockSecurityAssessmentFunction - LambdaInvokePolicy: FunctionName: !Ref SagemakerSecurityAssessmentFunction - LambdaInvokePolicy: @@ -34,6 +46,21 @@ Resources: - S3CrudPolicy: BucketName: !Ref AIMLAssessmentBucket + ResolveRegionsFunction: + Type: AWS::Serverless::Function + Properties: + FunctionName: !Sub 'aiml-security-ResolveRegions' + CodeUri: functions/security/resolve_regions/ + Handler: app.lambda_handler + Runtime: python3.12 + Architectures: + - x86_64 + Timeout: 60 + MemorySize: 256 + Environment: + Variables: + TARGET_REGIONS: !Ref TargetRegions + CleanupBucketFunction: Type: AWS::Serverless::Function Properties: @@ -120,6 +147,7 @@ Resources: Environment: Variables: AIML_ASSESSMENT_BUCKET_NAME: !Ref AIMLAssessmentBucket + TARGET_REGIONS: !Ref TargetRegions Policies: - S3CrudPolicy: BucketName: !Ref AIMLAssessmentBucket @@ -212,6 +240,7 @@ Resources: Environment: Variables: AIML_ASSESSMENT_BUCKET_NAME: !Ref AIMLAssessmentBucket + TARGET_REGIONS: !Ref TargetRegions Policies: - S3CrudPolicy: BucketName: !Ref AIMLAssessmentBucket @@ -305,6 +334,7 @@ Resources: Environment: Variables: AIML_ASSESSMENT_BUCKET_NAME: !Ref AIMLAssessmentBucket + TARGET_REGIONS: !Ref TargetRegions Policies: - S3CrudPolicy: BucketName: !Ref AIMLAssessmentBucket diff --git a/aiml-security-assessment/template.yaml b/aiml-security-assessment/template.yaml index 0cb74e5..c9d7e7e 100644 --- a/aiml-security-assessment/template.yaml +++ b/aiml-security-assessment/template.yaml @@ -5,6 +5,15 @@ Description: > SAM Template for aiml-security-assessment +Parameters: + TargetRegions: + Type: String + Default: "" + Description: > + Comma-separated list of AWS regions to scan (e.g., us-east-1,us-west-2,eu-west-1). + Use 'all' to scan all regions where the service is available. + Leave empty to scan only the deployment region. + Resources: AIMLAssessmentStateMachine: Type: AWS::Serverless::StateMachine # More info about State Machine Resource: https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/sam-resource-statemachine.html @@ -13,29 +22,45 @@ Resources: DefinitionSubstitutions: CleanupBucketFunction: !GetAtt CleanupBucketFunction.Arn IAMPermissionCachingFunction: !GetAtt IAMPermissionCachingFunction.Arn + ResolveRegionsFunction: !GetAtt ResolveRegionsFunction.Arn BedrockSecurityAssessmentFunction: !GetAtt BedrockSecurityAssessmentFunction.Arn SagemakerSecurityAssessmentFunction: !GetAtt SagemakerSecurityAssessmentFunction.Arn AgentCoreSecurityAssessmentFunction: !GetAtt AgentCoreSecurityAssessmentFunction.Arn GenerateConsolidatedReportFunction: !GetAtt GenerateConsolidatedReportFunction.Arn AIMLAssessmentBucketName: !Ref AIMLAssessmentBucket Policies: - # Find out more about SAM policy templates: https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-policy-templates.html - LambdaInvokePolicy: FunctionName: !Ref CleanupBucketFunction - - LambdaInvokePolicy: - FunctionName: !Ref BedrockSecurityAssessmentFunction - LambdaInvokePolicy: FunctionName: !Ref IAMPermissionCachingFunction + - LambdaInvokePolicy: + FunctionName: !Ref ResolveRegionsFunction + - LambdaInvokePolicy: + FunctionName: !Ref BedrockSecurityAssessmentFunction - LambdaInvokePolicy: FunctionName: !Ref SagemakerSecurityAssessmentFunction - LambdaInvokePolicy: FunctionName: !Ref AgentCoreSecurityAssessmentFunction - LambdaInvokePolicy: FunctionName: !Ref GenerateConsolidatedReportFunction - #Add S3 Read Write policy - S3CrudPolicy: BucketName: !Ref AIMLAssessmentBucket + ResolveRegionsFunction: + Type: AWS::Serverless::Function + Properties: + FunctionName: !Sub 'aiml-security-${AWS::StackName}-ResolveRegions' + CodeUri: functions/security/resolve_regions/ + Handler: app.lambda_handler + Runtime: python3.12 + Architectures: + - x86_64 + Timeout: 60 + MemorySize: 256 + Environment: + Variables: + TARGET_REGIONS: !Ref TargetRegions + CleanupBucketFunction: Type: AWS::Serverless::Function Properties: @@ -115,12 +140,12 @@ Resources: Runtime: python3.12 Architectures: - x86_64 - # Add S3 Read Write policy Timeout: 600 # 10 minutes MemorySize: 1024 Environment: Variables: AIML_ASSESSMENT_BUCKET_NAME: !Ref AIMLAssessmentBucket + TARGET_REGIONS: !Ref TargetRegions Policies: - S3CrudPolicy: BucketName: !Ref AIMLAssessmentBucket @@ -206,12 +231,12 @@ Resources: Runtime: python3.12 Architectures: - x86_64 - # Add S3 Read Write policy Timeout: 600 # 10 minutes MemorySize: 1024 Environment: Variables: AIML_ASSESSMENT_BUCKET_NAME: !Ref AIMLAssessmentBucket + TARGET_REGIONS: !Ref TargetRegions Policies: - S3CrudPolicy: BucketName: !Ref AIMLAssessmentBucket @@ -305,6 +330,7 @@ Resources: Environment: Variables: AIML_ASSESSMENT_BUCKET_NAME: !Ref AIMLAssessmentBucket + TARGET_REGIONS: !Ref TargetRegions Policies: - S3CrudPolicy: BucketName: !Ref AIMLAssessmentBucket diff --git a/buildspec.yml b/buildspec.yml index d4a5a4d..9c9bd7a 100644 --- a/buildspec.yml +++ b/buildspec.yml @@ -115,7 +115,7 @@ phases: fi echo "Deploying to account $accountId" - if ! sam deploy --template-file .aws-sam/build/template.yaml --stack-name aiml-security-$accountId --capabilities CAPABILITY_IAM --no-confirm-changeset --no-fail-on-empty-changeset --resolve-s3 --region $AWS_DEFAULT_REGION; then + if ! sam deploy --template-file .aws-sam/build/template.yaml --stack-name aiml-security-$accountId --capabilities CAPABILITY_IAM --no-confirm-changeset --no-fail-on-empty-changeset --resolve-s3 --region $AWS_DEFAULT_REGION --parameter-overrides TargetRegions="${TARGET_REGIONS:-}"; then echo "ERROR: Deploy failed for account $accountId" failed_accounts+=($accountId) unset AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY AWS_SESSION_TOKEN @@ -146,7 +146,7 @@ phases: aws cloudformation delete-stack --stack-name aws-sam-cli-managed-default aws cloudformation wait stack-delete-complete --stack-name aws-sam-cli-managed-default 2>/dev/null || true fi - if ! sam deploy --template-file .aws-sam/build/template.yaml --stack-name aiml-security-mgmt --capabilities CAPABILITY_IAM --no-confirm-changeset --no-fail-on-empty-changeset --resolve-s3 --region $AWS_DEFAULT_REGION; then + if ! sam deploy --template-file .aws-sam/build/template.yaml --stack-name aiml-security-mgmt --capabilities CAPABILITY_IAM --no-confirm-changeset --no-fail-on-empty-changeset --resolve-s3 --region $AWS_DEFAULT_REGION --parameter-overrides TargetRegions="${TARGET_REGIONS:-}"; then echo "ERROR: Deploy failed for management account" failed_accounts+=($AWS_ACCOUNT_ID) else @@ -171,7 +171,7 @@ phases: else echo "Single account deployment" STACK_NAME="aiml-sec-${AWS_ACCOUNT_ID}" - if ! sam deploy --template-file .aws-sam/build/template.yaml --stack-name $STACK_NAME --capabilities CAPABILITY_IAM --no-confirm-changeset --no-fail-on-empty-changeset --resolve-s3; then + if ! sam deploy --template-file .aws-sam/build/template.yaml --stack-name $STACK_NAME --capabilities CAPABILITY_IAM --no-confirm-changeset --no-fail-on-empty-changeset --resolve-s3 --parameter-overrides TargetRegions="${TARGET_REGIONS:-}"; then echo "ERROR: SAM deploy failed for single account" exit 1 fi @@ -215,6 +215,7 @@ phases: # Track failures upload_failed=false + sf_failed=false failed_accounts=() # Clean up any existing account-files directory to ensure fresh start @@ -264,7 +265,8 @@ phases: if [[ $STATUS == "SUCCEEDED" || $STATUS == "FAILED" || $STATUS == "TIMED_OUT" || $STATUS == "ABORTED" ]]; then echo "Step Function for account $accountId completed with status: $STATUS" if [[ $STATUS == "FAILED" || $STATUS == "TIMED_OUT" || $STATUS == "ABORTED" ]]; then - echo "WARNING: Step Function did not succeed for account $accountId" + echo "ERROR: Step Function did not succeed for account $accountId (status: $STATUS)" + sf_failed=true fi break fi @@ -349,6 +351,11 @@ phases: exit 1 fi + if [[ $sf_failed == true ]]; then + echo "ERROR: One or more Step Function executions failed. Check Step Functions console for details." + exit 1 + fi + echo "===========================================" echo "Multi-account assessment completed successfully!" echo "===========================================" @@ -361,6 +368,7 @@ phases: # Single account post-build: wait for Step Function and report status echo "Single account post-build processing" STACK_NAME="aiml-sec-${AWS_ACCOUNT_ID}" + sf_failed=false # Read the execution ARN saved from build phase if [[ -f /tmp/single_account_execution_arn.txt ]]; then @@ -374,7 +382,8 @@ phases: if [[ $STATUS == "SUCCEEDED" || $STATUS == "FAILED" || $STATUS == "TIMED_OUT" || $STATUS == "ABORTED" ]]; then echo "Step Function completed with status: $STATUS" if [[ $STATUS != "SUCCEEDED" ]]; then - echo "WARNING: Step Function did not succeed" + echo "ERROR: Step Function execution failed with status: $STATUS" + sf_failed=true fi break fi @@ -383,11 +392,13 @@ phases: elapsed=$((elapsed + 30)) done if [[ $elapsed -ge $timeout ]]; then - echo "WARNING: Timeout waiting for Step Function completion" + echo "ERROR: Timeout waiting for Step Function completion" + sf_failed=true fi fi else - echo "WARNING: No execution ARN file found from build phase" + echo "ERROR: No execution ARN file found from build phase" + sf_failed=true fi # Get assessment bucket and sync results to BUCKET_REPORT for consistency @@ -411,6 +422,11 @@ phases: else echo "WARNING: Could not determine assessment bucket from stack outputs" fi + + if [[ $sf_failed == true ]]; then + echo "ERROR: Assessment failed. Check Step Functions execution logs for details." + exit 1 + fi fi - | echo "===========================================" diff --git a/consolidate_html_reports.py b/consolidate_html_reports.py index 3b214d9..23d63a0 100644 --- a/consolidate_html_reports.py +++ b/consolidate_html_reports.py @@ -56,6 +56,7 @@ def consolidate_html_reports(): all_findings = [] account_ids = set() + regions = set() service_stats = { "bedrock": {"passed": 0, "failed": 0, "na": 0}, "sagemaker": {"passed": 0, "failed": 0, "na": 0}, @@ -83,6 +84,9 @@ def consolidate_html_reports(): reader = csv.DictReader(f) for row in reader: # Map CSV columns to finding structure + region = row.get("Region", "") + if region: + regions.add(region) finding = { "account_id": account_id, "check_id": row.get("Check_ID", ""), @@ -92,6 +96,7 @@ def consolidate_html_reports(): "reference": row.get("Reference", ""), "severity": row.get("Severity", "N/A"), "status": row.get("Status", ""), + "region": region, } # Determine service from Check_ID prefix @@ -151,6 +156,7 @@ def consolidate_html_reports(): mode="multi", account_ids=list(account_ids), timestamp=timestamp_display, + regions=sorted(regions) if regions else None, ) timestamp_file = datetime.now().strftime("%Y%m%d_%H%M%S") diff --git a/deployment/2-aiml-security-codebuild.yaml b/deployment/2-aiml-security-codebuild.yaml index e9793a1..eb05737 100644 --- a/deployment/2-aiml-security-codebuild.yaml +++ b/deployment/2-aiml-security-codebuild.yaml @@ -9,6 +9,7 @@ Metadata: Parameters: - MultiAccountScan - MultiAccountListOverride + - TargetRegions - EmailAddress - Label: default: Advanced Options @@ -70,6 +71,14 @@ Parameters: - "Twelve" Default: "Three" + TargetRegions: + Type: String + Description: > + Comma-separated list of AWS regions to scan (e.g., us-east-1,us-west-2,eu-west-1). + Use 'all' to scan all regions where the service is available. + Leave empty to scan only the deployment region. + Default: "" + CodeBuildTimeout: Description: "Set the timeout for the CodeBuild job. The default is 300 minutes (5 hours)." @@ -515,6 +524,9 @@ Resources: "ParallelAccounts", ] Type: PLAINTEXT + - Name: TARGET_REGIONS + Value: !Ref TargetRegions + Type: PLAINTEXT Description: Run AIML Security multi-account assessment ServiceRole: !GetAtt MultiAccountCodeBuildRole.Arn TimeoutInMinutes: !Ref CodeBuildTimeout diff --git a/deployment/aiml-security-single-account.yaml b/deployment/aiml-security-single-account.yaml index ad42582..95280cc 100644 --- a/deployment/aiml-security-single-account.yaml +++ b/deployment/aiml-security-single-account.yaml @@ -8,6 +8,7 @@ Metadata: default: Assessment Options Parameters: - EmailAddress + - TargetRegions - Label: default: Advanced Options Parameters: @@ -35,6 +36,14 @@ Parameters: AllowedPattern: ^$|^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$ ConstraintDescription: Must be a valid email address or leave empty + TargetRegions: + Type: String + Description: > + Comma-separated list of AWS regions to scan (e.g., us-east-1,us-west-2,eu-west-1). + Use 'all' to scan all regions where the service is available. + Leave empty to scan only the deployment region. + Default: "" + CodeBuildTimeout: Description: "Timeout for the CodeBuild job in minutes" Type: Number @@ -425,6 +434,9 @@ Resources: - Name: AWS_ACCOUNT_ID Value: !Sub ${AWS::AccountId} Type: PLAINTEXT + - Name: TARGET_REGIONS + Value: !Ref TargetRegions + Type: PLAINTEXT Description: Run AIML Security single-account assessment ServiceRole: !GetAtt CodeBuildRole.Arn TimeoutInMinutes: !Ref CodeBuildTimeout diff --git a/docs/CLEANUP.md b/docs/CLEANUP.md new file mode 100644 index 0000000..0f929dc --- /dev/null +++ b/docs/CLEANUP.md @@ -0,0 +1,105 @@ +# Cleanup Guide + +This guide provides step-by-step instructions for removing all resources deployed by the AI/ML Security Assessment framework. + +## Cleanup Order + +For a clean removal, delete resources in this order: + +1. **Assessment stacks** (auto-created by SAM) +2. **Infrastructure stack** (the stack you deployed manually) +3. AWS CloudFormation StackSet member roles (multi-account only) +4. Any remaining Amazon S3 buckets manually + +--- + +## Single-Account Cleanup + +To remove all resources deployed for single-account assessment: + +1. **Delete the AWS SAM-deployed assessment stack**: + - Navigate to **AWS CloudFormation** > **Stacks** + - Select the `aiml-sec-{account_id}` stack (for example, `aiml-sec-123456789012`) + - Click **Delete** + - Wait for stack deletion to complete + +2. **Delete the AWS CodeBuild infrastructure stack**: + - Select the `aiml-security-single-account` stack (or your custom stack name) + - Click **Delete** + - Wait for stack deletion to complete + +3. **Clean up Amazon S3 buckets** (if stack deletion fails due to non-empty buckets): + ```bash + # Empty the assessment bucket + aws s3 rm s3:// --recursive + + # If versioning is enabled, delete version markers + aws s3api delete-objects --bucket --delete \ + "$(aws s3api list-object-versions --bucket \ + --query '{Objects: Versions[].{Key:Key,VersionId:VersionId}}')" + + # Delete the bucket + aws s3 rb s3:// + ``` + +--- + +## Multi-Account Cleanup + +To remove all resources deployed for multi-account assessment: + +1. **Delete AWS SAM-deployed stacks in each member account**: + - For each account that was scanned, navigate to **AWS CloudFormation** > **Stacks** + - Select the `aiml-security-{account_id}` stack (for example, `aiml-security-123456789012`) + - For the management account, select `aiml-security-mgmt` + - Click **Delete** + - Alternatively, use the AWS CLI to delete across accounts: + ```bash + # Assume role in member account and delete stack + aws cloudformation delete-stack --stack-name aiml-security- \ + --region + ``` + +2. **Delete the central AWS CodeBuild infrastructure stack**: + - In the management account, navigate to **AWS CloudFormation** > **Stacks** + - Select the `aiml-security-multi-account` stack + - Click **Delete** + - Wait for stack deletion to complete + +3. **Delete the AWS CloudFormation StackSet member roles**: + - Navigate to **AWS CloudFormation** > **StackSets** + - Select the `aiml-security-member-roles` AWS CloudFormation StackSet + - Click **Actions** > **Delete stacks from StackSet** + - Select all deployment targets (OUs or accounts) + - Wait for stack instances to be deleted + - Once all stack instances are removed, delete the AWS CloudFormation StackSet itself + +4. **Clean up Amazon S3 buckets** (if stack deletion fails due to non-empty buckets): + ```bash + # List and identify assessment buckets + aws s3 ls | grep aiml-security + + # Empty each bucket + aws s3 rm s3:// --recursive + + # Delete version markers if versioning was enabled + aws s3api delete-objects --bucket --delete \ + "$(aws s3api list-object-versions --bucket \ + --query '{Objects: Versions[].{Key:Key,VersionId:VersionId}}')" + + # Delete the bucket + aws s3 rb s3:// + ``` + +--- + +## Identifying Stack Types + +The deployment creates multiple AWS CloudFormation stacks. Here's how to identify them: + +| Stack Type | How to Identify | Action | +|------------|-----------------|--------| +| **Infrastructure Stack** (yours) | The name you chose (for example, `aiml-security-single-account`) | Delete second | +| **Assessment Stack** (auto-generated) | `aiml-sec-{account_id}` (single) or `aiml-security-{account_id}` (multi) | Delete first | + +**Quick Check**: If you see a stack name starting with `aiml-sec-` or `aiml-security-` followed by numbers (or `aiml-security-mgmt`), that's an auto-generated assessment stack. diff --git a/docs/DEVELOPER_GUIDE.md b/docs/DEVELOPER_GUIDE.md index 1dab9ef..24c3d42 100644 --- a/docs/DEVELOPER_GUIDE.md +++ b/docs/DEVELOPER_GUIDE.md @@ -44,7 +44,7 @@ ## Architecture Overview -The AI/ML Security Assessment Framework is a serverless, multi-account security assessment solution for AWS AI/ML workloads. It performs 52 security checks across Amazon Bedrock, Amazon SageMaker AI, and Amazon Bedrock AgentCore, generating interactive HTML reports with findings and remediation guidance. +The AI/ML Security Assessment Framework is a serverless, multi-account security assessment solution for AWS AI/ML workloads. It performs 51 security checks across Amazon Bedrock, Amazon SageMaker AI, and Amazon Bedrock AgentCore, generating interactive HTML reports with findings and remediation guidance. ### Security Design Principles @@ -101,6 +101,7 @@ sample-aiml-security-assessment/ │ │ ├── agentcore_assessments/ # AgentCore security checks (13) │ │ ├── iam_permission_caching/ # AWS IAM permissions cache │ │ ├── cleanup_bucket/ # Amazon S3 cleanup +│ │ ├── resolve_regions/ # Multi-region resolution Lambda │ │ └── generate_consolidated_report/ # HTML/CSV report generation │ ├── statemachine/ # AWS Step Functions definition │ ├── images/ # SAM application images @@ -120,6 +121,7 @@ sample-aiml-security-assessment/ │ ├── scripts/ # Screenshot capture scripts │ ├── *.html # Sample HTML reports │ └── *.png # Report screenshots +├── tests/ # Unit tests for assessment functions ├── buildspec.yml # AWS CodeBuild orchestration ├── buildspec-modular-example.yml # Modular buildspec example └── consolidate_html_reports.py # Multi-account report consolidation @@ -148,30 +150,44 @@ sample-aiml-security-assessment/ ```json { "Comment": "AI/ML Assessment Module", - "StartAt": "Cleanup Amazon S3 Bucket", + "StartAt": "Cleanup S3 Bucket", "States": { - "Cleanup Amazon S3 Bucket": { + "Cleanup S3 Bucket": { "Type": "Task", - "Resource": "arn:aws:states:::lambda:invoke", - "Next": "AWS IAM Permission Caching" + "Next": "IAM Permission Caching" }, - "AWS IAM Permission Caching": { + "IAM Permission Caching": { "Type": "Task", - "Resource": "arn:aws:states:::lambda:invoke", - "Next": "Parallel Service Assessments" + "Next": "Resolve Target Regions" }, - "Parallel Service Assessments": { - "Type": "Parallel", - "Branches": [ - {"StartAt": "Amazon Bedrock Assessment", "States": {...}}, - {"StartAt": "Amazon SageMaker AI Assessment", "States": {...}}, - {"StartAt": "Amazon Bedrock AgentCore Assessment", "States": {...}} - ], + "Resolve Target Regions": { + "Type": "Task", + "Comment": "Resolves target regions from TARGET_REGIONS env var", + "Next": "Scan Regions" + }, + "Scan Regions": { + "Type": "Map", + "ItemsPath": "$.ResolvedRegions.regions", + "MaxConcurrency": 0, + "ItemProcessor": { + "ProcessorConfig": {"Mode": "INLINE"}, + "StartAt": "Run Security Assessments", + "States": { + "Run Security Assessments": { + "Type": "Parallel", + "Branches": [ + {"StartAt": "Bedrock Security Assessment", "States": {...}}, + {"StartAt": "Sagemaker Security Assessment", "States": {...}}, + {"StartAt": "AgentCore Security Assessment", "States": {...}} + ], + "End": true + } + } + }, "Next": "Generate Consolidated Report" }, "Generate Consolidated Report": { "Type": "Task", - "Resource": "arn:aws:states:::lambda:invoke", "End": true } } @@ -180,22 +196,25 @@ sample-aiml-security-assessment/ ## Assessment Structure -The framework includes **52 security checks** across three AI/ML services. For the complete list of checks with descriptions, see the [Security Checks Reference](SECURITY_CHECKS.md). +The framework includes **51 security checks** across three AI/ML services. For the complete list of checks with descriptions, see the [Security Checks Reference](SECURITY_CHECKS.md). ### AWS Lambda Functions Each assessment AWS Lambda function: -1. Receives execution context from AWS Step Functions -2. Reads cached AWS IAM permissions from Amazon S3 -3. Performs security checks against AWS APIs -4. Generates CSV report with findings -5. Uploads results to Amazon S3 -6. Returns findings summary to AWS Step Functions +1. Receives execution context and target region from AWS Step Functions (via the Map state) +2. Verifies the service is available in the target region (returns N/A finding if not) +3. Reads cached AWS IAM permissions from Amazon S3 +4. Creates regional boto3 clients with explicit `region_name` parameter +5. Performs security checks against AWS APIs in the target region +6. Generates CSV report with findings (includes `Region` column) +7. Uploads results to Amazon S3 with region-suffixed filename +8. Returns findings summary to AWS Step Functions **Additional Functions:** -- **AWS IAM Permission Caching**: Pre-fetches AWS IAM policies to optimize assessment +- **AWS IAM Permission Caching**: Pre-fetches AWS IAM policies to optimize assessment (global, runs once) - **Cleanup Bucket**: Removes old assessment data -- **Generate Consolidated Report**: Creates HTML report from CSV findings +- **Resolve Regions**: Resolves target regions from `TargetRegions` parameter for the Map state +- **Generate Consolidated Report**: Creates HTML report from CSV findings with region filtering ## Adding New AI/ML Service Assessments @@ -214,26 +233,44 @@ cd aiml-security-assessment/functions/security/comprehend_assessments ```python # app.py import boto3 +import os import json +from botocore.config import Config +from botocore.exceptions import ClientError, EndpointConnectionError from schema import create_finding +boto3_config = Config(retries=dict(max_attempts=10, mode="adaptive")) + def lambda_handler(event, context): """Main assessment handler for new service""" all_findings = [] + # Extract target region from Step Functions Map state + region = event.get("Region", os.environ.get("AWS_REGION", "us-east-1")) + + # Verify service availability in this region + try: + test_client = boto3.client("comprehend", config=boto3_config, region_name=region) + test_client.list_endpoints(MaxResults=1) + except (EndpointConnectionError, Exception) as e: + if "Could not connect to the endpoint URL" in str(e): + # Service not available — return N/A finding + ... + return {"statusCode": 200, "body": {"message": f"Service not available in {region}"}} + # Get cached permissions execution_id = event["Execution"]["Name"] permission_cache = get_permissions_cache(execution_id) - # Run assessment checks - findings = check_new_service_security(permission_cache) + # Run assessment checks (pass region to each) + findings = check_new_service_security(permission_cache, region=region) all_findings.append(findings) - # Generate and upload report + # Generate and upload report (include region in S3 key) csv_content = generate_csv_report(all_findings) bucket_name = os.environ.get("AIML_ASSESSMENT_BUCKET_NAME") - s3_url = write_to_s3(execution_id, csv_content, bucket_name) + s3_url = write_to_s3(execution_id, csv_content, bucket_name, region=region) return { "statusCode": 200, @@ -245,7 +282,7 @@ def lambda_handler(event, context): } -def check_new_service_security(permission_cache): +def check_new_service_security(permission_cache, region: str = ""): """Implement your security checks here""" findings = { "check_name": "New Service Security Check", @@ -254,9 +291,11 @@ def check_new_service_security(permission_cache): "csv_data": [], } + # Create regional client + client = boto3.client("comprehend", config=boto3_config, region_name=region) + # Your assessment logic here - # Use permission_cache to check IAM permissions - # Use AWS SDK to check service configurations + # Pass region= to all create_finding() calls return findings ``` @@ -279,7 +318,6 @@ class SeverityEnum(str, Enum): MEDIUM = "Medium" LOW = "Low" INFORMATIONAL = "Informational" - NA = "N/A" class StatusEnum(str, Enum): @@ -289,7 +327,7 @@ class StatusEnum(str, Enum): def create_finding( - check_id, finding_name, finding_details, resolution, reference, severity, status + check_id, finding_name, finding_details, resolution, reference, severity, status, region="" ): """Create standardized finding format @@ -301,6 +339,7 @@ def create_finding( reference: Documentation URL severity: SeverityEnum value status: StatusEnum value (Failed, Passed, or N/A) + region: AWS region where the finding was identified """ return { "Check_ID": check_id, @@ -310,6 +349,7 @@ def create_finding( "Reference": reference, "Severity": severity, "Status": status, + "Region": region, } ``` @@ -330,6 +370,7 @@ Add your new function to `aiml-security-assessment/template.yaml`: Environment: Variables: AIML_ASSESSMENT_BUCKET_NAME: !Ref AIMLAssessmentBucket + TARGET_REGIONS: !Ref TargetRegions Policies: - S3CrudPolicy: BucketName: !Ref AIMLAssessmentBucket @@ -490,7 +531,7 @@ For detailed troubleshooting guidance, common issues, and debugging tips, see th ## Development Roadmap ### Current Status -- **AI/ML Assessment**: 52 security checks across three services (see [Security Checks Reference](SECURITY_CHECKS.md)) +- **AI/ML Assessment**: 51 security checks across three services (see [Security Checks Reference](SECURITY_CHECKS.md)) ### Potential Additions - **Amazon Comprehend**: Data privacy, access controls, entity recognition security diff --git a/docs/TROUBLESHOOTING.md b/docs/TROUBLESHOOTING.md index 02251d5..486bd58 100644 --- a/docs/TROUBLESHOOTING.md +++ b/docs/TROUBLESHOOTING.md @@ -110,6 +110,38 @@ aws s3 rb s3:// 3. **Wrong prefix**: Look under `{account_id}/` for single-account, `consolidated-reports/` for multi-account 4. **Permissions**: Check CloudWatch Logs for Lambda execution errors +### 8. Confused by Multiple CloudFormation Stacks + +**Symptoms:** You see multiple stacks and aren't sure which one has your results. + +**Explanation:** The deployment creates TWO stacks. Only the infrastructure stack has the `AssessmentBucket` output. + +| Stack Type | How to Identify | What to Do | +|------------|-----------------|------------| +| **Infrastructure Stack** (yours) | The name you chose (for example, `aiml-security-single-account`) | Use this — go to Outputs tab, copy `AssessmentBucket` | +| **Assessment Stack** (auto-generated) | `aiml-sec-{account_id}` (single) or `aiml-security-{account_id}` (multi) | Ignore — internal operations only | + +**Quick Check**: If a stack name starts with `aiml-sec-` or `aiml-security-` followed by numbers (or `aiml-security-mgmt`), it's auto-generated. Look for the name you chose during deployment. + +### 9. Upgrading an Existing Deployment to Multi-Region + +**Symptoms:** You have an existing single-region deployment and want to enable multi-region scanning. + +**Solution:** Update your existing CloudFormation stack — no teardown required. + +1. Navigate to **AWS CloudFormation** > **Stacks** +2. Select your infrastructure stack (for example, `aiml-security-single-account` or `aiml-security-multi-account`) +3. Click **Update** > **Use current template** +4. Set the `TargetRegions` parameter (for example, `us-east-1,us-west-2,eu-west-1` or `all`) +5. Click through to **Submit** +6. The next assessment run will scan the specified regions in parallel + +**What happens during the upgrade:** +- CloudFormation updates the `TARGET_REGIONS` environment variable on the existing Lambdas (the `ResolveRegionsFunction`, state machine, and assessment Lambdas already exist in all deployments — when `TargetRegions` is empty they simply scan a single region) +- No new resources are created — the value of the environment variable changes from empty to your specified regions +- No data is lost — the S3 bucket retains all previous reports +- Fully backward compatible — leaving `TargetRegions` empty preserves single-region behavior + --- ## Debugging diff --git a/sample-reports/security_assessment_multi_region_sample.html b/sample-reports/security_assessment_multi_region_sample.html new file mode 100644 index 0000000..e20cccd --- /dev/null +++ b/sample-reports/security_assessment_multi_region_sample.html @@ -0,0 +1,873 @@ + + + + + + AI/ML Security Assessment Report + + + + +
+ +
+
+ +
+
Security Checks
9
Evaluated across 3 regions
+
Total Findings
16
Across 3 regions
+
Actionable Findings
11
High, Medium, and Low severity
+
High Severity
4/9
44.4% passed · Immediate action required
+
Medium Severity
0/2
0.0% passed · Should be addressed
+
Low Severity
0/0
0% passed · Best practices
+
+

Priority Recommendations

+
2
+
+
Bedrock Full Access Roles
+
Bedrock
+
+
+
1
+
+
Bedrock Logging Configuration
+
Bedrock
+
+
+
1
+
+
SageMaker Direct Internet Access
+
SageMaker
+
+
+
1
+
+
Bedrock Guardrails Check
+
Bedrock
+
+
+
+

Severity Legend

View full methodology
+
+ + + + + + + + +
SeverityMeaningRecommended Action
HighDirect security risk - IAM/access control gaps, missing audit trails, guardrail bypasses that could lead to unauthorized access or data exposureRemediate within 7 days
MediumDefense-in-depth gaps - encryption, logging, or configuration issues that reduce security postureRemediate within 30 days
LowBest practice deviations - optimization opportunities that improve security hygieneRemediate within 90 days
InformationalNo resources found or advisory recommendations - check does not apply or suggests optional improvementsNo action required
+
+
+
+
+
All Security Findings
+
+
+ +
+
+
+
+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Account IDRegionCheck IDFindingDetailsResolutionReferenceSeverityStatus
123456789012us-east-1BR-01Bedrock Full Access RolesFound 2 roles with AmazonBedrockFullAccess policy attached: BedrockAdmin, MLEngineerReview and restrict to least privilege access.HighFailed
123456789012us-east-1BR-05Bedrock Guardrails CheckAmazon Bedrock Guardrails are properly configured with 3 guardrailsNo action required.HighPassed
123456789012us-east-1BR-04Bedrock Logging ConfigurationModel invocation logging is enabledNo action required.HighPassed
123456789012us-west-2BR-01Bedrock Full Access RolesFound 2 roles with AmazonBedrockFullAccess policy attachedReview and restrict to least privilege access.HighFailed
123456789012us-west-2BR-05Bedrock Guardrails CheckNo Bedrock Guardrails configured in this region.Configure Bedrock Guardrails.MediumFailed
123456789012us-west-2BR-04Bedrock Logging ConfigurationModel invocation logging is not enabledEnable model invocation logging.HighFailed
123456789012eu-central-1BR-00Bedrock Service AvailabilityAmazon Bedrock is not available in region eu-central-1.No action required.InformationalN/A
123456789012us-east-1SM-01SageMaker Direct Internet AccessFound 1 notebook with direct internet access: ml-notebook-devDisable direct internet access.HighFailed
123456789012us-east-1SM-03SageMaker Data EncryptionAll training jobs use KMS encryptionNo action required.HighPassed
123456789012us-west-2SM-01SageMaker Direct Internet AccessNo notebook instances foundNo action required.InformationalPassed
123456789012us-west-2SM-03SageMaker Data EncryptionFound 2 training jobs without KMS encryptionConfigure KMS keys.HighFailed
123456789012eu-central-1SM-01SageMaker Direct Internet AccessNo notebook instances foundNo action required.InformationalPassed
123456789012us-east-1AC-01AgentCore VPC ConfigurationAll runtimes deployed in private subnetsNo action required.HighPassed
123456789012us-east-1AC-03AgentCore EncryptionECR repositories lack KMS encryptionEnable KMS encryption for ECR.MediumFailed
123456789012us-west-2AC-00AgentCore Service AvailabilityAgentCore not available in us-west-2.No action required.InformationalN/A
123456789012eu-central-1AC-00AgentCore Service AvailabilityAgentCore not available in eu-central-1.No action required.InformationalN/A
+
+
+
Risk Distribution
+

Pass Rate by Severity

+
+
HIGH
44.4%
4 of 9 checks passed
+
MEDIUM
0.0%
0 of 2 checks passed
+
LOW
0%
0 of 0 checks passed
+
Overall
36.4%
4 of 11 actionable checks
+
+ +

Risk by Region

+
eu-central-1
0
0 High · 0 Med · 0 Low
us-east-1
3
2 High · 1 Med · 0 Low
us-west-2
4
3 High · 1 Med · 0 Low
+

Findings by Service

+
+
Bedrock
7
4 Failed · 2 Passed
+
SageMaker
5
2 Failed · 3 Passed
+
AgentCore
4
1 Failed · 1 Passed
+
+
+
+
Amazon Bedrock Findings
+
+
+ +
+
+
+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Account IDRegionCheck IDFindingDetailsResolutionReferenceSeverityStatus
123456789012us-east-1BR-01Bedrock Full Access RolesFound 2 roles with AmazonBedrockFullAccess policy attached: BedrockAdmin, MLEngineerReview and restrict to least privilege access.HighFailed
123456789012us-east-1BR-05Bedrock Guardrails CheckAmazon Bedrock Guardrails are properly configured with 3 guardrailsNo action required.HighPassed
123456789012us-east-1BR-04Bedrock Logging ConfigurationModel invocation logging is enabledNo action required.HighPassed
123456789012us-west-2BR-01Bedrock Full Access RolesFound 2 roles with AmazonBedrockFullAccess policy attachedReview and restrict to least privilege access.HighFailed
123456789012us-west-2BR-05Bedrock Guardrails CheckNo Bedrock Guardrails configured in this region.Configure Bedrock Guardrails.MediumFailed
123456789012us-west-2BR-04Bedrock Logging ConfigurationModel invocation logging is not enabledEnable model invocation logging.HighFailed
123456789012eu-central-1BR-00Bedrock Service AvailabilityAmazon Bedrock is not available in region eu-central-1.No action required.InformationalN/A
+
+
+
Amazon SageMaker Findings
+
+
+ +
+
+
+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Account IDRegionCheck IDFindingDetailsResolutionReferenceSeverityStatus
123456789012us-east-1SM-01SageMaker Direct Internet AccessFound 1 notebook with direct internet access: ml-notebook-devDisable direct internet access.HighFailed
123456789012us-east-1SM-03SageMaker Data EncryptionAll training jobs use KMS encryptionNo action required.HighPassed
123456789012us-west-2SM-01SageMaker Direct Internet AccessNo notebook instances foundNo action required.InformationalPassed
123456789012us-west-2SM-03SageMaker Data EncryptionFound 2 training jobs without KMS encryptionConfigure KMS keys.HighFailed
123456789012eu-central-1SM-01SageMaker Direct Internet AccessNo notebook instances foundNo action required.InformationalPassed
+
+
+
Amazon Bedrock AgentCore Findings
+
+
+ +
+
+
+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Account IDRegionCheck IDFindingDetailsResolutionReferenceSeverityStatus
123456789012us-east-1AC-01AgentCore VPC ConfigurationAll runtimes deployed in private subnetsNo action required.HighPassed
123456789012us-east-1AC-03AgentCore EncryptionECR repositories lack KMS encryptionEnable KMS encryption for ECR.MediumFailed
123456789012us-west-2AC-00AgentCore Service AvailabilityAgentCore not available in us-west-2.No action required.InformationalN/A
123456789012eu-central-1AC-00AgentCore Service AvailabilityAgentCore not available in eu-central-1.No action required.InformationalN/A
+
+
+
Assessment Methodology
+

Severity Levels & Status Values

HighDirect security riskFailedRemediation needed
MediumDefense-in-depth gapPassedMeets requirements
LowBest practiceN/ANot applicable
InformationalNo action required
+

Remediation Guidance

High7 daysAddress immediately; block deployment if unresolved
Medium30 daysSchedule in next sprint; may require change window
Low90 daysInclude in backlog; address during regular maintenance
+

Assessment Notes

Point-in-time: Security posture changes as resources are modified. Scope limited: Passed checks verify tested controls only. Context matters: Adjust severity for compliance requirements and environment type.
+

Assessment Scope

Amazon Bedrock
Amazon SageMaker
Amazon Bedrock AgentCore

Based on AWS Well-Architected Framework (Generative AI Lens) and service-specific security documentation.

+
+
+
+
Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. Licensed under MIT-0. This report is provided as-is for informational purposes only and does not constitute professional security advice, compliance certification, or audit evidence. You are responsible for validating findings and determining applicability to your environment. See the AWS Customer Agreement for terms of use.
+ + + \ No newline at end of file From 64eb8fcfdd481644cdf67aa1a951a55162155dec Mon Sep 17 00:00:00 2001 From: Agasthi Kothurkar Date: Fri, 12 Jun 2026 18:27:05 -0400 Subject: [PATCH 02/30] Add .ash/ to .gitignore --- .gitignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 219bbc1..42e7880 100644 --- a/.gitignore +++ b/.gitignore @@ -39,4 +39,5 @@ samples/ # Test output **/test_reports/ -.ruff_cache/ \ No newline at end of file +.ruff_cache/ +.ash/ \ No newline at end of file From c7bd20303ec62f77fabad25d63239cbb7ce65246 Mon Sep 17 00:00:00 2001 From: Agasthi Kothurkar Date: Sun, 14 Jun 2026 11:26:19 -0400 Subject: [PATCH 03/30] Deduplicate global findings and gate regional availability in multi-region scans Refine multi-region scanning so global/IAM-only checks (e.g. AC-02/AC-03, BR-01/BR-03) run once on the primary region and are tagged with a "Global" region label, avoiding duplicate findings across scanned regions. Regional assessments now probe availability and skip opt-in/disabled regions and APIs unsupported in a region, tagging remaining findings with their region. Add test coverage for the consolidated report, report filenames, SageMaker template permissions, and the per-service multi-region check behavior. --- README.md | 29 +- .../security/agentcore_assessments/app.py | 157 ++++++--- .../security/agentcore_assessments/schema.py | 17 +- .../security/bedrock_assessments/app.py | 186 +++++++---- .../security/bedrock_assessments/schema.py | 19 +- .../generate_consolidated_report/app.py | 73 +++-- .../functions/security/resolve_regions/app.py | 2 +- .../security/sagemaker_assessments/app.py | 303 +++++++++++------- .../security/sagemaker_assessments/schema.py | 22 +- .../statemachine/assessments.asl.json | 12 +- .../template-multi-account.yaml | 38 ++- aiml-security-assessment/template.yaml | 24 ++ consolidate_html_reports.py | 15 +- docs/DEVELOPER_GUIDE.md | 8 +- docs/SECURITY_CHECKS.md | 15 +- docs/TROUBLESHOOTING.md | 12 +- tests/conftest.py | 6 + tests/test_agentcore_checks.py | 162 +++++++++- tests/test_bedrock_checks.py | 247 ++++++++++++++ tests/test_consolidated_report.py | 207 ++++++++++++ tests/test_report_filenames.py | 48 +++ tests/test_sagemaker_checks.py | 203 ++++++++++++ tests/test_sagemaker_template_permissions.py | 38 +++ tests/test_schema.py | 1 + 24 files changed, 1554 insertions(+), 290 deletions(-) create mode 100644 tests/test_consolidated_report.py create mode 100644 tests/test_report_filenames.py create mode 100644 tests/test_sagemaker_template_permissions.py diff --git a/README.md b/README.md index e4da99d..60ea7f3 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ **Open-source automated security scanner for Amazon Bedrock, Amazon SageMaker AI, and Amazon Bedrock AgentCore** — Built on [AWS Well-Architected Framework (Generative AI Lens)](https://docs.aws.amazon.com/wellarchitected/latest/generative-ai-lens/generative-ai-lens.html) -Cloud security automation with **[51 security checks](docs/SECURITY_CHECKS.md)** for your generative AI and machine learning workloads. Identify IAM misconfigurations, encryption gaps, network isolation issues, and compliance violations with interactive HTML reports and actionable remediation guidance. +Cloud security automation with **[52 security checks](docs/SECURITY_CHECKS.md)** for your generative AI and machine learning workloads. Identify IAM misconfigurations, encryption gaps, network isolation issues, and compliance violations with interactive HTML reports and actionable remediation guidance. --- @@ -35,7 +35,7 @@ The framework generates professional, interactive security assessment reports wi ### Key Features -- **[51 Security Checks](docs/SECURITY_CHECKS.md)** across Amazon Bedrock, Amazon SageMaker AI, and Amazon Bedrock AgentCore +- **[52 Security Checks](docs/SECURITY_CHECKS.md)** across Amazon Bedrock, Amazon SageMaker AI, and Amazon Bedrock AgentCore - **Multi-Region Support** — parallel scanning across AWS regions with per-region risk breakdown - **Multi-Account Support** — consolidated reporting across AWS Organizations - **Interactive Filtering** by account, region, service, severity, and status @@ -43,7 +43,7 @@ The framework generates professional, interactive security assessment reports wi - **Fully Automated** — one-click CloudFormation deployment and execution **Services Covered:** -- **[Amazon Bedrock](docs/SECURITY_CHECKS.md#amazon-bedrock-security-checks-13)** (13 checks) — Guardrails, encryption, VPC endpoints, IAM, model invocation logging +- **[Amazon Bedrock](docs/SECURITY_CHECKS.md#amazon-bedrock-security-checks-14)** (14 checks) — Guardrails, encryption, VPC endpoints, IAM, model invocation logging - **[Amazon SageMaker AI](docs/SECURITY_CHECKS.md#amazon-sagemaker-ai-security-checks-25)** (25 checks) — Security Hub controls, encryption, network isolation, IAM, MLOps - **[Amazon Bedrock AgentCore](docs/SECURITY_CHECKS.md#amazon-bedrock-agentcore-security-checks-13)** (13 checks) — VPC configuration, encryption, observability, resource policies @@ -78,7 +78,7 @@ The framework generates professional, interactive security assessment reports wi - `all` to scan all regions where the services are available 6. Acknowledge IAM capabilities and click **Submit**. 7. Once complete, CodeBuild automatically runs the assessment. -8. View results: go to the stack **Outputs** tab → copy `AssessmentBucket` → open the HTML report in that S3 bucket. +8. View results: go to the stack **Outputs** tab → copy `AssessmentBucket` → open the report under the `/{account_id}/` prefix in that S3 bucket. > **Tip**: The deployment creates two stacks. Your results are in the stack *you named*, not the auto-generated `aiml-sec-*` stack. See [Troubleshooting](docs/TROUBLESHOOTING.md#8-confused-by-multiple-cloudformation-stacks) for details. @@ -140,7 +140,8 @@ For detailed architecture, execution flow, and extension guidance, see the [Deve 1. Open your **infrastructure stack** in CloudFormation → **Outputs** tab → copy `AssessmentBucket` 2. Navigate to that S3 bucket -3. Open the `security_assessment_*.html` file (single-account) or check `consolidated-reports/` (multi-account) +3. For single-account, open `{account_id}/security_assessment_single_account_*.html` +4. For multi-account, open `consolidated-reports/security_assessment_multi_account_*.html` ### Understanding Results @@ -170,6 +171,24 @@ For detailed architecture, execution flow, and extension guidance, see the [Deve --- +## Permissions Required + +The deployment uses multiple IAM roles with different trust and permission boundaries. They are not all read-only. + +- **`CodeBuildRole` / `MultiAccountCodeBuildRole`**: orchestration roles used by the infrastructure stack to clone the repo, build SAM, deploy/update the assessment stack, and start Step Functions executions. These roles require infrastructure-management permissions such as CloudFormation, Lambda, IAM, Step Functions, and S3 actions. +- **`AIMLSecurityMemberRole`**: role assumed in the target account during single-account and multi-account runs. In the multi-account flow this role is also **not read-only**. It needs both service-read permissions for the checks and deployment permissions so CodeBuild can create or update the per-account SAM assessment stack. +- **SAM-created Lambda execution roles**: runtime roles for the assessment functions. These are the closest thing to read-only assessment roles. They primarily use `List*`, `Describe*`, and `Get*` access against Bedrock, SageMaker, AgentCore, IAM analysis APIs, and supporting read APIs, plus S3 access to write reports and read the cached IAM permissions file. + +If you need to reduce scope, review the role policies in: + +- [deployment/aiml-security-single-account.yaml](/Users/akothurk/Documents/Code/Github/aws-samples/sample-aiml-security-assessment/deployment/aiml-security-single-account.yaml) +- [deployment/1-aiml-security-member-roles.yaml](/Users/akothurk/Documents/Code/Github/aws-samples/sample-aiml-security-assessment/deployment/1-aiml-security-member-roles.yaml) +- [deployment/2-aiml-security-codebuild.yaml](/Users/akothurk/Documents/Code/Github/aws-samples/sample-aiml-security-assessment/deployment/2-aiml-security-codebuild.yaml) +- [aiml-security-assessment/template.yaml](/Users/akothurk/Documents/Code/Github/aws-samples/sample-aiml-security-assessment/aiml-security-assessment/template.yaml) +- [aiml-security-assessment/template-multi-account.yaml](/Users/akothurk/Documents/Code/Github/aws-samples/sample-aiml-security-assessment/aiml-security-assessment/template-multi-account.yaml) + +--- + ## Documentation | Document | Description | diff --git a/aiml-security-assessment/functions/security/agentcore_assessments/app.py b/aiml-security-assessment/functions/security/agentcore_assessments/app.py index 23f427f..14ed84c 100644 --- a/aiml-security-assessment/functions/security/agentcore_assessments/app.py +++ b/aiml-security-assessment/functions/security/agentcore_assessments/app.py @@ -41,6 +41,22 @@ # Environment variables BUCKET_NAME = os.environ.get("AIML_ASSESSMENT_BUCKET_NAME") +# IAM is a global service. Findings derived purely from the IAM permission cache +# (e.g. AC-02, AC-03) are identical across regions, so they are produced only on +# the primary region (Map index 0) and tagged with this region label to avoid +# duplicate findings when scanning multiple regions. +GLOBAL_REGION_LABEL = "Global" + +# Error codes returned when a region exists but is not enabled/usable for the +# account (opt-in regions, disabled regions). The availability probe treats +# these the same as an endpoint connection failure. +REGION_UNAVAILABLE_ERROR_CODES = { + "UnrecognizedClientException", + "InvalidClientTokenId", + "AuthFailure", + "OptInRequired", +} + # Execution tracking start_time = None @@ -2257,9 +2273,13 @@ def lambda_handler(event, context): try: # Extract target region from Step Functions Map state region = event.get("Region", os.environ.get("AWS_REGION", "us-east-1")) - logger.info(f"Scanning region: {region}") + # IAM is global: only the primary region (Map index 0) runs IAM-only checks. + is_primary_region = int(event.get("RegionIndex", 0)) == 0 + logger.info(f"Scanning region: {region} (primary={is_primary_region})") + + execution_id = event.get("Execution", {}).get("Name", "unknown") - # Initialize regional clients + # Initialize regional clients (iam is global, the rest are region-scoped) iam_client = boto3.client("iam", config=boto3_config) ec2_client = boto3.client("ec2", config=boto3_config, region_name=region) ecr_client = boto3.client("ecr", config=boto3_config, region_name=region) @@ -2267,33 +2287,95 @@ def lambda_handler(event, context): xray_client = boto3.client("xray", config=boto3_config, region_name=region) cloudwatch_client = boto3.client("cloudwatch", config=boto3_config, region_name=region) + # Collect all findings + all_findings = [] + + # Retrieve permission cache (shared/global IAM data) + try: + permission_cache = get_permissions_cache(execution_id) + except Exception as e: + logger.warning(f"Failed to retrieve permission cache: {e}") + permission_cache = {"role_permissions": [], "user_permissions": []} + + # Run global IAM-only checks once (on the primary region) so the same role + # violations are not reported once per scanned region. These run before the + # regional availability gate so they are still emitted even if AgentCore is + # not available in the primary region. + if is_primary_region: + global_checks = [ + ("IAM Full Access", lambda: check_agentcore_full_access_roles(permission_cache)), + ("Stale Access", lambda: check_stale_agentcore_access(permission_cache)), + # AC-09 inspects a global IAM service-linked role, so it is also + # run once on the primary region rather than per scanned region. + ("Service-Linked Role", check_agentcore_service_linked_role), + ] + for check_name, check_func in global_checks: + try: + logger.info(f"Running global check: {check_name}") + global_findings = check_func() + for finding in global_findings: + finding["Region"] = GLOBAL_REGION_LABEL + all_findings.extend(global_findings) + except Exception as e: + logger.error(f"Error in global check '{check_name}': {e}") + all_findings.append( + create_finding( + check_id="AC-00", + finding_name=f"AgentCore {check_name} Check Error", + finding_details=f"Error during {check_name} check: {str(e)}", + resolution="Investigate error and retry assessment", + reference="https://aws.github.io/bedrock-agentcore-starter-toolkit/", + severity=SeverityEnum.HIGH, + status=StatusEnum.FAILED, + region=GLOBAL_REGION_LABEL, + ) + ) + + # Reset per-invocation so a warm container cannot leak a previous + # region's client if creation below fails. + agentcore_client = None try: agentcore_client = boto3.client("bedrock-agentcore-control", config=boto3_config, region_name=region) - # Test service availability with a lightweight call - agentcore_client.list_agent_runtimes(maxResults=1) - logger.info("Successfully initialized bedrock-agentcore-control client") - except EndpointConnectionError: - logger.info(f"AgentCore service not available in region {region}, skipping") - agentcore_client = None - except ClientError as e: - if "Could not connect" in str(e): - logger.info(f"AgentCore service not available in region {region}, skipping") - agentcore_client = None - else: - # Service is available but returned an API error (e.g., access denied) — proceed - logger.info("AgentCore client initialized (API accessible)") except Exception as e: - if "Could not connect to the endpoint URL" in str(e): + # The client could not even be constructed (e.g. the SDK in this + # runtime does not know the service). This is the one case where the + # region genuinely cannot be assessed. + logger.warning(f"Failed to initialize bedrock-agentcore-control client: {e}") + agentcore_client = None + + if agentcore_client is not None: + # Test service availability with a lightweight call + try: + agentcore_client.list_agent_runtimes(maxResults=1) + logger.info("Successfully initialized bedrock-agentcore-control client") + except EndpointConnectionError: logger.info(f"AgentCore service not available in region {region}, skipping") agentcore_client = None - else: - logger.warning(f"Failed to initialize bedrock-agentcore-control client: {e}") - agentcore_client = None + except ClientError as e: + error_code = e.response.get("Error", {}).get("Code", "") + if error_code in REGION_UNAVAILABLE_ERROR_CODES: + logger.info(f"AgentCore not accessible in region {region} ({error_code}), skipping") + agentcore_client = None + else: + # Service is reachable but returned another API error (e.g. access + # denied) — proceed; individual checks handle their own errors. + logger.info(f"AgentCore client initialized (probe returned {error_code})") + except Exception as e: + # An unexpected probe failure (e.g. a boto3/botocore SDK param or + # operation mismatch such as ParamValidationError/AttributeError) + # says nothing about regional availability. Treating it as "not + # available" would silently skip every AgentCore check and emit a + # false N/A report, so keep the client and let the individual + # checks surface their own errors instead. + logger.warning( + f"AgentCore availability probe raised an unexpected error, " + f"proceeding with checks: {e}" + ) - # If AgentCore not available, produce a single N/A report and exit early + # If AgentCore not available, produce an N/A report (plus any global IAM + # findings already collected on the primary region) and exit early if agentcore_client is None: - execution_id = event.get("Execution", {}).get("Name", "unknown") - na_findings = [ + all_findings.append( create_finding( check_id="AC-00", finding_name="AgentCore Service Availability", @@ -2304,47 +2386,32 @@ def lambda_handler(event, context): status=StatusEnum.NA, region=region, ) - ] - for finding in na_findings: - finding["Region"] = region - csv_content = generate_csv_report(na_findings) + ) + for finding in all_findings: + if not finding.get("Region"): + finding["Region"] = region + csv_content = generate_csv_report(all_findings) s3_url = write_to_s3(execution_id, csv_content, BUCKET_NAME, region=region) return { "statusCode": 200, "body": json.dumps({"message": f"AgentCore not available in {region}", "s3_url": s3_url}), } - # Extract execution ID - execution_id = event.get("Execution", {}).get("Name", "unknown") logger.info( f"Starting AgentCore security assessment for execution: {execution_id}" ) - # Retrieve permission cache - try: - permission_cache = get_permissions_cache(execution_id) - except Exception as e: - logger.warning(f"Failed to retrieve permission cache: {e}") - permission_cache = {"role_permissions": [], "user_permissions": []} - - # Collect all findings - all_findings = [] - - # Execute all assessment checks + # Execute regional assessment checks (IAM-only checks AC-02/AC-03 and the + # global service-linked role check AC-09 are run separately, once, on the + # primary region above) checks = [ ("VPC Configuration", check_agentcore_vpc_configuration), - ( - "IAM Full Access", - lambda: check_agentcore_full_access_roles(permission_cache), - ), - ("Stale Access", lambda: check_stale_agentcore_access(permission_cache)), ("Observability", check_agentcore_observability), ("Encryption", check_agentcore_encryption), ("Browser Tool Recording", check_browser_tool_recording), ("Memory Configuration", check_agentcore_memory_configuration), ("Gateway Configuration", check_agentcore_gateway_configuration), ("VPC Endpoints", check_agentcore_vpc_endpoints), - ("Service-Linked Role", check_agentcore_service_linked_role), ("Resource-Based Policies", check_agentcore_resource_based_policies), ("Policy Engine Encryption", check_agentcore_policy_engine_encryption), ("Gateway Encryption", check_agentcore_gateway_encryption), diff --git a/aiml-security-assessment/functions/security/agentcore_assessments/schema.py b/aiml-security-assessment/functions/security/agentcore_assessments/schema.py index 56f63b5..c2c4938 100644 --- a/aiml-security-assessment/functions/security/agentcore_assessments/schema.py +++ b/aiml-security-assessment/functions/security/agentcore_assessments/schema.py @@ -1,7 +1,6 @@ from enum import Enum -from typing import Dict, List, Any -from pydantic import BaseModel, Field, HttpUrl, validator -from datetime import datetime +from typing import Any, Dict +from pydantic import BaseModel, Field, field_validator import re class SeverityEnum(str, Enum): @@ -26,7 +25,8 @@ class Finding(BaseModel): Status: StatusEnum = Field(..., description="Current status of the finding") Region: str = Field(default="", description="AWS region where the finding was identified") - @validator('Check_ID') + @field_validator("Check_ID") + @classmethod def validate_check_id(cls, v): """Validate that Check_ID follows the pattern XX-NN (e.g., SM-01, BR-14, AC-05)""" pattern = r'^[A-Z]{2,3}-\d{2}$' @@ -34,21 +34,24 @@ def validate_check_id(cls, v): raise ValueError('Check_ID must follow pattern XX-NN (e.g., SM-01, BR-14, AC-05)') return v - @validator('Reference') + @field_validator("Reference") + @classmethod def validate_reference_url(cls, v): """Validate that reference URL starts with https://""" if not str(v).startswith('https://'): raise ValueError('Reference URL must start with https://') return v - @validator('Severity') + @field_validator("Severity") + @classmethod def validate_severity(cls, v): """Validate that severity is one of the allowed values""" if v not in SeverityEnum.__members__.values(): raise ValueError('Severity must be one of the allowed values') return v - @validator('Status') + @field_validator("Status") + @classmethod def validate_status(cls, v): """Validate that status is one of the allowed values""" if v not in StatusEnum.__members__.values(): diff --git a/aiml-security-assessment/functions/security/bedrock_assessments/app.py b/aiml-security-assessment/functions/security/bedrock_assessments/app.py index c59fda7..98db843 100644 --- a/aiml-security-assessment/functions/security/bedrock_assessments/app.py +++ b/aiml-security-assessment/functions/security/bedrock_assessments/app.py @@ -25,6 +25,41 @@ logger = logging.getLogger() logger.setLevel(logging.ERROR) +# IAM is a global service. Findings derived purely from the IAM permission cache +# (e.g. BR-01, BR-03) are identical across regions, so they are produced only on +# the primary region (Map index 0) and tagged with this region label to avoid +# duplicate findings when scanning multiple regions. +GLOBAL_REGION_LABEL = "Global" + +# Error codes returned when a region exists but is not enabled/usable for the +# account (opt-in regions, disabled regions). The availability probe treats +# these the same as an endpoint connection failure. +REGION_UNAVAILABLE_ERROR_CODES = { + "UnrecognizedClientException", + "InvalidClientTokenId", + "AuthFailure", + "OptInRequired", +} + + +def describe_api_error(error: Exception, api_label: str, region: str = "") -> str: + """ + Build a report-friendly description for an API error raised by a regional + check. + + Some regions don't support a given Bedrock API. boto3 surfaces this as + "Unknown operation ..." (ValidationException) or UnknownOperationException. + For those, return a clean " not available in " message + instead of leaking the raw boto3 exception text into the report. Any other + error keeps its raw text so genuine problems (e.g. permissions) stay + diagnosable. + """ + error_text = str(error) + if "UnknownOperation" in error_text or "Unknown operation" in error_text: + location = region if region else "this region" + return f"{api_label} not available in {location}" + return f"Unable to check {api_label}: {error_text}" + def get_permissions_cache(execution_id: str) -> Optional[Dict[str, Any]]: """ @@ -268,7 +303,14 @@ def has_bedrock_access(iam_client, principal_name: str, principal_type: str) -> return False -def check_stale_bedrock_access(permission_cache) -> Dict[str, Any]: +def check_stale_bedrock_access(permission_cache, region: str = "") -> Dict[str, Any]: + """ + Check for stale Bedrock access using IAM service-last-accessed data. + + This check is derived purely from IAM (a global service) and the cached + permissions, so it produces identical results in every region. The handler + runs it once, on the primary region, tagged with GLOBAL_REGION_LABEL. + """ logger.debug("Starting check for stale Bedrock access") try: findings = { @@ -308,6 +350,7 @@ def check_stale_bedrock_access(permission_cache) -> Dict[str, Any]: reference="https://docs.aws.amazon.com/IAM/latest/UserGuide/access_policies_last-accessed.html", severity="Informational", status="N/A", + region=region, ) ) return findings @@ -394,6 +437,7 @@ def check_stale_bedrock_access(permission_cache) -> Dict[str, Any]: reference="https://docs.aws.amazon.com/IAM/latest/UserGuide/access_policies_last-accessed.html", severity="Medium", status="Failed", + region=region, ) ) @@ -421,6 +465,7 @@ def check_stale_bedrock_access(permission_cache) -> Dict[str, Any]: reference="https://docs.aws.amazon.com/IAM/latest/UserGuide/access_policies_last-accessed.html", severity="Medium", status="Passed", + region=region, ) ) @@ -441,6 +486,7 @@ def check_stale_bedrock_access(permission_cache) -> Dict[str, Any]: reference="https://docs.aws.amazon.com/bedrock/latest/userguide/security.html", severity="High", status="Failed", + region=region, ) ], } @@ -1202,18 +1248,22 @@ def check_bedrock_prompt_management(region: str = "") -> Dict[str, Any]: ) ) - except bedrock_client.exceptions.ValidationException as e: - findings["status"] = "ERROR" - findings["details"] = f"Error checking Prompt Management: {str(e)}" + except Exception as e: + # An API error (e.g. InternalServerErrorException after retries, + # throttling, or a permissions issue) is not a security failure. + # Surface it as N/A rather than Failed, matching the BR-11 pattern. + logger.warning(f"Error listing prompts: {str(e)}") findings["csv_data"].append( create_finding( check_id="BR-07", finding_name="Bedrock Prompt Management Check", - finding_details=f"Error checking Bedrock Prompt Management configuration: {str(e)}", - resolution="Verify your AWS credentials and permissions to access Bedrock Prompt Management.", + finding_details=describe_api_error( + e, "Bedrock Prompt Management API", region + ), + resolution="Verify your AWS credentials and permissions to access Bedrock Prompt Management, then retry the assessment.", reference="https://docs.aws.amazon.com/bedrock/latest/userguide/prompt-management.html", - severity="High", - status="Failed", + severity="Low", + status="N/A", region=region, ) ) @@ -1697,7 +1747,7 @@ def check_bedrock_custom_model_encryption(region: str = "") -> Dict[str, Any]: create_finding( check_id="BR-11", finding_name="Bedrock Custom Model Encryption Check", - finding_details=f"Unable to list custom models: {str(e)}", + finding_details=describe_api_error(e, "Custom model API", region), resolution="Verify permissions to access Bedrock custom models", reference="https://docs.aws.amazon.com/bedrock/latest/userguide/model-customization-iam-role.html", severity="Low", @@ -2056,7 +2106,7 @@ def check_bedrock_flows_guardrails(region: str = "") -> Dict[str, Any]: create_finding( check_id="BR-13", finding_name="Bedrock Flows Guardrails Check", - finding_details=f"Unable to check flows: {str(e)}", + finding_details=describe_api_error(e, "Bedrock Flows API", region), resolution="Verify permissions to access Bedrock Flows", reference="https://docs.aws.amazon.com/bedrock/latest/userguide/flows-guardrails.html", severity="Low", @@ -2346,42 +2396,14 @@ def lambda_handler(event, context): try: # Extract target region from Step Functions Map state region = event.get("Region", os.environ.get("AWS_REGION", "us-east-1")) - logger.info(f"Scanning region: {region}") + # IAM is global: only the primary region (Map index 0) runs IAM-only checks. + is_primary_region = int(event.get("RegionIndex", 0)) == 0 + logger.info(f"Scanning region: {region} (primary={is_primary_region})") - # Verify Bedrock is available in this region - try: - test_client = boto3.client("bedrock", config=boto3_config, region_name=region) - test_client.get_model_invocation_logging_configuration() - except (EndpointConnectionError, Exception) as e: - error_msg = str(e) - if "Could not connect to the endpoint URL" in error_msg or "EndpointConnectionError" in type(e).__name__: - logger.info(f"Bedrock service not available in region {region}, skipping") - all_findings.append({ - "check_name": "Bedrock Service Availability", - "status": "N/A", - "details": f"Bedrock is not available in region {region}", - "csv_data": [ - create_finding( - check_id="BR-00", - finding_name="Bedrock Service Availability", - finding_details=f"Amazon Bedrock is not available in region {region}. No checks performed.", - resolution="No action required. Bedrock is not deployed in this region.", - reference="https://docs.aws.amazon.com/bedrock/latest/userguide/bedrock-regions.html", - severity="Informational", - status="N/A", - region=region, - ) - ], - }) - execution_id = event["Execution"]["Name"] - csv_content = generate_csv_report(all_findings) - bucket_name = os.environ.get("AIML_ASSESSMENT_BUCKET_NAME") - s3_url = write_to_s3(execution_id, csv_content, bucket_name, region=region) - return {"statusCode": 200, "body": {"message": f"Bedrock not available in {region}", "report_url": s3_url}} - - # Initialize permission cache - logger.info("Initializing IAM permission cache") execution_id = event["Execution"]["Name"] + + # Initialize permission cache (shared/global IAM data) + logger.info("Initializing IAM permission cache") permission_cache = get_permissions_cache(execution_id) if not permission_cache: @@ -2390,23 +2412,79 @@ def lambda_handler(event, context): ) permission_cache = {"role_permissions": {}, "user_permissions": {}} - # Run all checks using the cached permissions - logger.info("Running AmazonBedrockFullAccess check") - bedrock_full_access_findings = check_bedrock_full_access_roles(permission_cache, region=region) - all_findings.append(bedrock_full_access_findings) + # Run global IAM-only checks once (on the primary region) so the same role + # violations are not reported once per scanned region. These run before the + # regional availability gate so they are still emitted even if Bedrock is + # not available in the primary region. + if is_primary_region: + logger.info("Running global AmazonBedrockFullAccess check (BR-01)") + all_findings.append( + check_bedrock_full_access_roles(permission_cache, region=GLOBAL_REGION_LABEL) + ) + + logger.info("Running global marketplace subscription access check (BR-03)") + all_findings.append( + check_marketplace_subscription_access(permission_cache, region=GLOBAL_REGION_LABEL) + ) + + logger.info("Running global stale Bedrock access check (BR-14)") + all_findings.append( + check_stale_bedrock_access(permission_cache, region=GLOBAL_REGION_LABEL) + ) + # Verify Bedrock is available in this region. A ValidationException here + # (logging simply not configured) still means the service is reachable, + # so only an endpoint connection failure or a region-not-enabled error + # should short-circuit the assessment. + bedrock_unavailable = False + unavailable_detail = "" + try: + test_client = boto3.client("bedrock", config=boto3_config, region_name=region) + test_client.get_model_invocation_logging_configuration() + except EndpointConnectionError: + bedrock_unavailable = True + unavailable_detail = f"Amazon Bedrock is not available in region {region}. No checks performed." + except ClientError as e: + error_code = e.response.get("Error", {}).get("Code", "") + if error_code in REGION_UNAVAILABLE_ERROR_CODES: + bedrock_unavailable = True + unavailable_detail = f"Amazon Bedrock is not available or not enabled in region {region} ({error_code}). No checks performed." + else: + # Service is reachable (e.g. ValidationException, AccessDenied) — + # proceed; individual checks handle their own errors. + logger.info(f"Bedrock availability probe returned {error_code}; proceeding with checks") + + if bedrock_unavailable: + logger.info(f"Bedrock service not available in region {region}, skipping") + all_findings.append({ + "check_name": "Bedrock Service Availability", + "status": "N/A", + "details": f"Bedrock is not available in region {region}", + "csv_data": [ + create_finding( + check_id="BR-00", + finding_name="Bedrock Service Availability", + finding_details=unavailable_detail, + resolution="No action required. Bedrock is not deployed in this region.", + reference="https://docs.aws.amazon.com/bedrock/latest/userguide/bedrock-regions.html", + severity="Informational", + status="N/A", + region=region, + ) + ], + }) + csv_content = generate_csv_report(all_findings) + bucket_name = os.environ.get("AIML_ASSESSMENT_BUCKET_NAME") + s3_url = write_to_s3(execution_id, csv_content, bucket_name, region=region) + return {"statusCode": 200, "body": {"message": f"Bedrock not available in {region}", "report_url": s3_url}} + + # Run regional checks using the cached permissions logger.info("Running Bedrock access and VPC endpoints check") bedrock_access_vpc_findings = check_bedrock_access_and_vpc_endpoints( permission_cache, region=region ) all_findings.append(bedrock_access_vpc_findings) - logger.info("Running marketplace subscription access check") - marketplace_access_findings = check_marketplace_subscription_access( - permission_cache, region=region - ) - all_findings.append(marketplace_access_findings) - logger.info("Running Bedrock logging findings check") bedrock_logging_findings = check_bedrock_logging_configuration(region=region) all_findings.append(bedrock_logging_findings) diff --git a/aiml-security-assessment/functions/security/bedrock_assessments/schema.py b/aiml-security-assessment/functions/security/bedrock_assessments/schema.py index 6b48996..c2c4938 100644 --- a/aiml-security-assessment/functions/security/bedrock_assessments/schema.py +++ b/aiml-security-assessment/functions/security/bedrock_assessments/schema.py @@ -1,7 +1,6 @@ from enum import Enum -from typing import Dict, List, Any -from pydantic import BaseModel, Field, HttpUrl, validator -from datetime import datetime +from typing import Any, Dict +from pydantic import BaseModel, Field, field_validator import re class SeverityEnum(str, Enum): @@ -26,7 +25,8 @@ class Finding(BaseModel): Status: StatusEnum = Field(..., description="Current status of the finding") Region: str = Field(default="", description="AWS region where the finding was identified") - @validator('Check_ID') + @field_validator("Check_ID") + @classmethod def validate_check_id(cls, v): """Validate that Check_ID follows the pattern XX-NN (e.g., SM-01, BR-14, AC-05)""" pattern = r'^[A-Z]{2,3}-\d{2}$' @@ -34,21 +34,24 @@ def validate_check_id(cls, v): raise ValueError('Check_ID must follow pattern XX-NN (e.g., SM-01, BR-14, AC-05)') return v - @validator('Reference') + @field_validator("Reference") + @classmethod def validate_reference_url(cls, v): """Validate that reference URL starts with https://""" if not str(v).startswith('https://'): raise ValueError('Reference URL must start with https://') return v - @validator('Severity') + @field_validator("Severity") + @classmethod def validate_severity(cls, v): """Validate that severity is one of the allowed values""" if v not in SeverityEnum.__members__.values(): raise ValueError('Severity must be one of the allowed values') return v - @validator('Status') + @field_validator("Status") + @classmethod def validate_status(cls, v): """Validate that status is one of the allowed values""" if v not in StatusEnum.__members__.values(): @@ -94,4 +97,4 @@ def create_finding( Status=status, Region=region ) - return dict(finding.model_dump()) # Convert to regular dictionary \ No newline at end of file + return dict(finding.model_dump()) # Convert to regular dictionary diff --git a/aiml-security-assessment/functions/security/generate_consolidated_report/app.py b/aiml-security-assessment/functions/security/generate_consolidated_report/app.py index 555c491..4f74d81 100644 --- a/aiml-security-assessment/functions/security/generate_consolidated_report/app.py +++ b/aiml-security-assessment/functions/security/generate_consolidated_report/app.py @@ -10,6 +10,13 @@ from report_template import generate_html_report as generate_report_from_template +# Sentinel region label used by the per-service assessments to tag findings that +# are derived purely from global (IAM) data and run once per execution rather +# than per region (e.g. BR-01, SM-02, AC-09). It is NOT a real AWS region, so it +# must be excluded when counting scanned regions for the report's multi-region +# UI (region filter, "Risk by Region", region count). +GLOBAL_REGION_LABEL = "Global" + boto3_config = Config( retries=dict( max_attempts=10, # Maximum number of retries @@ -56,30 +63,25 @@ def get_assessment_results(execution_id: str, account_id: str = None) -> Dict[st try: s3_client = boto3.client("s3", config=boto3_config) - # List all CSV files with execution ID in filename (bucket root) + # List all CSV files with execution ID in filename (bucket root). + # Use a paginator: a multi-region scan produces one file per service per + # region, so a single list_objects_v2 call (capped at 1000 keys) could + # silently truncate and drop regions for large scans. s3_bucket = os.environ.get("AIML_ASSESSMENT_BUCKET_NAME") - response = s3_client.list_objects_v2( - Bucket=s3_bucket, Prefix=f"bedrock_security_report_{execution_id}" - ) + paginator = s3_client.get_paginator("list_objects_v2") - # Also check for SageMaker reports - sagemaker_response = s3_client.list_objects_v2( - Bucket=s3_bucket, Prefix=f"sagemaker_security_report_{execution_id}" - ) - - # Also check for AgentCore reports - agentcore_response = s3_client.list_objects_v2( - Bucket=s3_bucket, Prefix=f"agentcore_security_report_{execution_id}" - ) + # One prefix per service; each matches every region's report file. + prefixes = [ + f"bedrock_security_report_{execution_id}", + f"sagemaker_security_report_{execution_id}", + f"agentcore_security_report_{execution_id}", + ] - # Combine all responses all_objects = [] - if "Contents" in response: - all_objects.extend(response["Contents"]) - if "Contents" in sagemaker_response: - all_objects.extend(sagemaker_response["Contents"]) - if "Contents" in agentcore_response: - all_objects.extend(agentcore_response["Contents"]) + for prefix in prefixes: + for page in paginator.paginate(Bucket=s3_bucket, Prefix=prefix): + all_objects.extend(page.get("Contents", [])) + if not all_objects: logger.warning(f"No assessment files found for execution {execution_id}") return {} @@ -200,10 +202,30 @@ def generate_html_report(assessment_results: Dict[str, Any]) -> str: service_findings = {"bedrock": [], "sagemaker": [], "agentcore": []} regions = set() + # Global/IAM findings (Region == "Global", e.g. BR-01, SM-02, AC-09) are + # produced once per run by the primary-region Lambda and should land in a + # single CSV. Dedup defensively here so the totals and per-region tiles stay + # correct even if the same finding ever appears in more than one region's + # file (e.g. RegionIndex missing from the event, or a future per-region + # write of a global check). The key uniquely identifies a finding within an + # account; account is included so a future multi-account merge is unaffected. + seen_findings = set() + for service in ["bedrock", "sagemaker", "agentcore"]: if service in assessment_results: for report_type, findings in assessment_results[service].items(): for finding in findings: + dedup_key = ( + finding.get("Account_ID", ""), + service, + finding.get("Check_ID", ""), + finding.get("Region", ""), + finding.get("Finding_Details", ""), + ) + if dedup_key in seen_findings: + continue + seen_findings.add(dedup_key) + finding["_service"] = service all_findings.append(finding) service_findings[service].append(finding) @@ -215,7 +237,9 @@ def generate_html_report(assessment_results: Dict[str, Any]) -> str: elif status == "n/a": service_stats[service]["na"] += 1 region = finding.get("Region", "") - if region: + # "Global" tags IAM-only findings; it is not a scanned region + # and must not inflate the region count / multi-region UI. + if region and region != GLOBAL_REGION_LABEL: regions.add(region) account_id = assessment_results.get("account_id", "Unknown") @@ -242,6 +266,11 @@ def get_current_utc_date(): return datetime.now(timezone.utc).strftime("%Y/%m/%d") +def build_single_account_report_key(timestamp: str) -> str: + """Build the single-account HTML report object key.""" + return f"security_assessment_single_account_{timestamp}.html" + + def write_html_to_s3( html_content: str, s3_bucket: str, execution_id: str, account_id: str = None ) -> Optional[str]: @@ -261,7 +290,7 @@ def write_html_to_s3( # Generate the S3 key for local bucket (no account folder needed) timestamp = datetime.now(timezone.utc).strftime("%Y%m%d_%H%M%S") - s3_key = f"security_assessment_{timestamp}_{execution_id}.html" + s3_key = build_single_account_report_key(timestamp) # Upload the HTML file s3_client.put_object( diff --git a/aiml-security-assessment/functions/security/resolve_regions/app.py b/aiml-security-assessment/functions/security/resolve_regions/app.py index ba65b78..ccd3eb2 100644 --- a/aiml-security-assessment/functions/security/resolve_regions/app.py +++ b/aiml-security-assessment/functions/security/resolve_regions/app.py @@ -15,7 +15,7 @@ BEDROCK_SERVICE = "bedrock" SAGEMAKER_SERVICE = "sagemaker" -AGENTCORE_SERVICE = "bedrock-agent" +AGENTCORE_SERVICE = "bedrock-agentcore-control" SERVICES = [BEDROCK_SERVICE, SAGEMAKER_SERVICE, AGENTCORE_SERVICE] diff --git a/aiml-security-assessment/functions/security/sagemaker_assessments/app.py b/aiml-security-assessment/functions/security/sagemaker_assessments/app.py index fec78d9..e78fe5a 100644 --- a/aiml-security-assessment/functions/security/sagemaker_assessments/app.py +++ b/aiml-security-assessment/functions/security/sagemaker_assessments/app.py @@ -26,6 +26,23 @@ logger = logging.getLogger() logger.setLevel(logging.ERROR) +# IAM is a global service. Findings derived purely from the IAM permission cache +# (e.g. the SM-02 full-access and stale-access checks) are identical across +# regions, so they are produced only on the primary region (Map index 0) and +# tagged with this region label to avoid duplicate findings when scanning +# multiple regions. +GLOBAL_REGION_LABEL = "Global" + +# Error codes returned when a region exists but is not enabled/usable for the +# account (opt-in regions, disabled regions). The availability probe treats +# these the same as an endpoint connection failure. +REGION_UNAVAILABLE_ERROR_CODES = { + "UnrecognizedClientException", + "InvalidClientTokenId", + "AuthFailure", + "OptInRequired", +} + def get_permissions_cache(execution_id: str) -> Optional[Dict[str, Any]]: """ @@ -318,7 +335,13 @@ def check_guardduty_enabled(region: str = "") -> Dict[str, Any]: def check_sagemaker_iam_permissions(permission_cache, region: str = "") -> Dict[str, Any]: """ - Check SageMaker IAM permissions, SSO configuration, and stale access + Check SageMaker IAM permissions and stale access. + + These checks are derived purely from IAM (a global service) and the cached + permissions, so they produce identical results in every region. The handler + runs this check once, on the primary region, tagged with GLOBAL_REGION_LABEL. + Regional SSO/domain configuration is checked separately by + check_sagemaker_sso_configuration. """ logger.debug("Starting check for SageMaker IAM permissions") try: @@ -332,9 +355,10 @@ def check_sagemaker_iam_permissions(permission_cache, region: str = "") -> Dict[ roles_with_full_access.append(role_name) break - # Check for stale access + # Check for stale access. IAM is a global service, so the client is not + # region-scoped (region is used only for finding tags). stale_users = [] - iam_client = boto3.client("iam", config=boto3_config, region_name=region) + iam_client = boto3.client("iam", config=boto3_config) two_months_ago = datetime.now(timezone.utc) - timedelta(days=60) # Check users' last access to SageMaker @@ -379,66 +403,8 @@ def check_sagemaker_iam_permissions(permission_cache, region: str = "") -> Dict[ f"Error checking last access for user {user_name}: {str(e)}" ) - # Check SSO configuration - domains_without_sso = [] - try: - sagemaker_client = boto3.client("sagemaker", config=boto3_config, region_name=region) - paginator = sagemaker_client.get_paginator("list_domains") - - for page in paginator.paginate(): - for domain in page["Domains"]: - domain_id = domain["DomainId"] - try: - domain_details = sagemaker_client.describe_domain( - DomainId=domain_id - ) - - # Check authentication mode - auth_mode = domain_details.get("AuthMode", "") - if auth_mode != "SSO": - domains_without_sso.append( - { - "domain_id": domain_id, - "domain_name": domain_details.get( - "DomainName", "N/A" - ), - "auth_mode": auth_mode, - } - ) - - # Check if SSO is properly configured with Identity Center - if auth_mode == "SSO": - try: - # Check Identity Center configuration - identity_store_id = domain_details.get( - "IdentityStoreId" - ) - - if not identity_store_id: - domains_without_sso.append( - { - "domain_id": domain_id, - "domain_name": domain_details.get( - "DomainName", "N/A" - ), - "auth_mode": "SSO (Incomplete Configuration)", - } - ) - except Exception as sso_error: - logger.error( - f"Error checking SSO configuration for domain {domain_id}: {str(sso_error)}" - ) - - except Exception as domain_error: - logger.error( - f"Error checking domain {domain_id}: {str(domain_error)}" - ) - - except Exception as e: - logger.error(f"Error checking SSO configuration: {str(e)}") - # Generate findings - if roles_with_full_access or stale_users or domains_without_sso: + if roles_with_full_access or stale_users: # Findings for full access roles if roles_with_full_access: for role_name in roles_with_full_access: @@ -470,34 +436,12 @@ def check_sagemaker_iam_permissions(permission_cache, region: str = "") -> Dict[ region=region, ) ) - - # Findings for SSO - if domains_without_sso: - for domain in domains_without_sso: - findings["csv_data"].append( - create_finding( - check_id="SM-02", - finding_name="SSO Not Properly Configured", - finding_details=( - f"SageMaker domain '{domain['domain_id']}' ({domain['domain_name']}) " - f"is using authentication mode: {domain['auth_mode']}" - ), - resolution=( - "Enable and properly configure AWS IAM Identity Center (successor to AWS SSO) " - "for centralized access management. Ensure Identity Store ID is configured." - ), - reference="https://aws.amazon.com/blogs/machine-learning/team-and-user-management-with-amazon-sagemaker-and-aws-sso/", - severity="Medium", - status="Failed", - region=region, - ) - ) else: findings["csv_data"].append( create_finding( check_id="SM-02", finding_name="SageMaker IAM Permissions Check", - finding_details="No issues found with IAM permissions, SSO is enabled, and no stale access detected", + finding_details="No issues found with IAM permissions and no stale access detected", resolution="No action required", reference="https://docs.aws.amazon.com/sagemaker-unified-studio/latest/adminguide/security-iam.html", severity="High", @@ -520,6 +464,109 @@ def check_sagemaker_iam_permissions(permission_cache, region: str = "") -> Dict[ } +def check_sagemaker_sso_configuration(region: str = "") -> Dict[str, Any]: + """ + Check SageMaker domain SSO / IAM Identity Center configuration. + + SageMaker domains are regional resources, so this check runs once per + scanned region (unlike the IAM-global checks in + check_sagemaker_iam_permissions). + """ + logger.debug("Starting check for SageMaker SSO configuration") + try: + findings = {"csv_data": []} + + domains_without_sso = [] + sagemaker_client = boto3.client("sagemaker", config=boto3_config, region_name=region) + paginator = sagemaker_client.get_paginator("list_domains") + + for page in paginator.paginate(): + for domain in page["Domains"]: + domain_id = domain["DomainId"] + try: + domain_details = sagemaker_client.describe_domain( + DomainId=domain_id + ) + + # Check authentication mode + auth_mode = domain_details.get("AuthMode", "") + if auth_mode != "SSO": + domains_without_sso.append( + { + "domain_id": domain_id, + "domain_name": domain_details.get("DomainName", "N/A"), + "auth_mode": auth_mode, + } + ) + + # Check if SSO is properly configured with Identity Center + if auth_mode == "SSO": + identity_store_id = domain_details.get("IdentityStoreId") + + if not identity_store_id: + domains_without_sso.append( + { + "domain_id": domain_id, + "domain_name": domain_details.get( + "DomainName", "N/A" + ), + "auth_mode": "SSO (Incomplete Configuration)", + } + ) + + except Exception as domain_error: + logger.error( + f"Error checking domain {domain_id}: {str(domain_error)}" + ) + + if domains_without_sso: + for domain in domains_without_sso: + findings["csv_data"].append( + create_finding( + check_id="SM-02", + finding_name="SSO Not Properly Configured", + finding_details=( + f"SageMaker domain '{domain['domain_id']}' ({domain['domain_name']}) " + f"is using authentication mode: {domain['auth_mode']}" + ), + resolution=( + "Enable and properly configure AWS IAM Identity Center (successor to AWS SSO) " + "for centralized access management. Ensure Identity Store ID is configured." + ), + reference="https://aws.amazon.com/blogs/machine-learning/team-and-user-management-with-amazon-sagemaker-and-aws-sso/", + severity="Medium", + status="Failed", + region=region, + ) + ) + else: + findings["csv_data"].append( + create_finding( + check_id="SM-02", + finding_name="SageMaker SSO Configuration Check", + finding_details="No SageMaker domains found, or all domains use SSO with IAM Identity Center configured", + resolution="No action required", + reference="https://aws.amazon.com/blogs/machine-learning/team-and-user-management-with-amazon-sagemaker-and-aws-sso/", + severity="Medium", + status="Passed", + region=region, + ) + ) + + return findings + + except Exception as e: + logger.error( + f"Error in check_sagemaker_sso_configuration: {str(e)}", exc_info=True + ) + return { + "check_name": "SageMaker SSO Configuration Check", + "status": "ERROR", + "details": f"Error during check: {str(e)}", + "csv_data": [], + } + + def has_sagemaker_permissions(policy_doc: Dict) -> bool: """ Check if a policy document contains SageMaker permissions @@ -3808,16 +3855,67 @@ def lambda_handler(event, context): try: # Extract target region from Step Functions Map state region = event.get("Region", os.environ.get("AWS_REGION", "us-east-1")) - logger.info(f"Scanning region: {region}") + # IAM is global: only the primary region (Map index 0) runs IAM-only checks. + is_primary_region = int(event.get("RegionIndex", 0)) == 0 + logger.info(f"Scanning region: {region} (primary={is_primary_region})") + + execution_id = event["Execution"]["Name"] + + # Initialize permission cache (shared/global IAM data) + logger.info("Initializing IAM permission cache") + permission_cache = get_permissions_cache(execution_id) + + if not permission_cache: + logger.error( + "Permission cache not found - IAM permission caching may have failed" + ) + permission_cache = {"role_permissions": {}, "user_permissions": {}} + + # Run global IAM-only checks once (on the primary region) so the same role + # and stale-access violations are not reported once per scanned region. + # These run before the regional availability gate so they are still emitted + # even if SageMaker is not available in the primary region. + if is_primary_region: + logger.info("Running global SageMaker IAM permissions check (SM-02)") + sagemaker_iam_findings = check_sagemaker_iam_permissions( + permission_cache, region=GLOBAL_REGION_LABEL + ) + all_findings.append(sagemaker_iam_findings) # Verify SageMaker is available in this region try: test_client = boto3.client("sagemaker", config=boto3_config, region_name=region) test_client.list_notebook_instances(MaxResults=1) - except (EndpointConnectionError, Exception) as e: - error_msg = str(e) - if "Could not connect to the endpoint URL" in error_msg or "EndpointConnectionError" in type(e).__name__: - logger.info(f"SageMaker service not available in region {region}, skipping") + except EndpointConnectionError: + logger.info(f"SageMaker service not available in region {region}, skipping") + all_findings.append({ + "check_name": "SageMaker Service Availability", + "status": "N/A", + "details": f"SageMaker is not available in region {region}", + "csv_data": [ + create_finding( + check_id="SM-00", + finding_name="SageMaker Service Availability", + finding_details=f"Amazon SageMaker is not available in region {region}. No checks performed.", + resolution="No action required. SageMaker is not deployed in this region.", + reference="https://docs.aws.amazon.com/general/latest/gr/sagemaker.html", + severity="Informational", + status="N/A", + region=region, + ) + ], + }) + csv_content = generate_csv_report(all_findings) + bucket_name = os.environ.get("AIML_ASSESSMENT_BUCKET_NAME") + s3_url = write_to_s3(execution_id, csv_content, bucket_name, region=region) + return {"statusCode": 200, "body": {"message": f"SageMaker not available in {region}", "report_url": s3_url}} + except ClientError as e: + # A region that exists but is not enabled for the account surfaces as + # an auth/opt-in error rather than a connection failure. Treat it the + # same as "not available" instead of running every check against it. + error_code = e.response.get("Error", {}).get("Code", "") + if error_code in REGION_UNAVAILABLE_ERROR_CODES: + logger.info(f"SageMaker not accessible in region {region} ({error_code}), skipping") all_findings.append({ "check_name": "SageMaker Service Availability", "status": "N/A", @@ -3826,8 +3924,8 @@ def lambda_handler(event, context): create_finding( check_id="SM-00", finding_name="SageMaker Service Availability", - finding_details=f"Amazon SageMaker is not available in region {region}. No checks performed.", - resolution="No action required. SageMaker is not deployed in this region.", + finding_details=f"Amazon SageMaker is not available or not enabled in region {region} ({error_code}). No checks performed.", + resolution="No action required if the region is intentionally disabled. Otherwise enable the region for this account.", reference="https://docs.aws.amazon.com/general/latest/gr/sagemaker.html", severity="Informational", status="N/A", @@ -3835,30 +3933,21 @@ def lambda_handler(event, context): ) ], }) - execution_id = event["Execution"]["Name"] csv_content = generate_csv_report(all_findings) bucket_name = os.environ.get("AIML_ASSESSMENT_BUCKET_NAME") s3_url = write_to_s3(execution_id, csv_content, bucket_name, region=region) return {"statusCode": 200, "body": {"message": f"SageMaker not available in {region}", "report_url": s3_url}} - - # Initialize permission cache - logger.info("Initializing IAM permission cache") - execution_id = event["Execution"]["Name"] - permission_cache = get_permissions_cache(execution_id) - - if not permission_cache: - logger.error( - "Permission cache not found - IAM permission caching may have failed" - ) - permission_cache = {"role_permissions": {}, "user_permissions": {}} + # Service is reachable but returned another API error (e.g. AccessDenied) + # — proceed; individual checks handle their own errors. + logger.info(f"SageMaker availability probe returned {error_code}; proceeding with checks") logger.info("Running SageMaker internet access check") sagemaker_internet_access_findings = check_sagemaker_internet_access(region=region) all_findings.append(sagemaker_internet_access_findings) - logger.info("Running SageMaker IAM permissions check") - sagemaker_iam_findings = check_sagemaker_iam_permissions(permission_cache, region=region) - all_findings.append(sagemaker_iam_findings) + logger.info("Running SageMaker SSO configuration check") + sagemaker_sso_findings = check_sagemaker_sso_configuration(region=region) + all_findings.append(sagemaker_sso_findings) logger.info("Running SageMaker data protection check") sagemaker_data_protection_findings = check_sagemaker_data_protection(region=region) diff --git a/aiml-security-assessment/functions/security/sagemaker_assessments/schema.py b/aiml-security-assessment/functions/security/sagemaker_assessments/schema.py index c767db7..c2c4938 100644 --- a/aiml-security-assessment/functions/security/sagemaker_assessments/schema.py +++ b/aiml-security-assessment/functions/security/sagemaker_assessments/schema.py @@ -1,12 +1,8 @@ from enum import Enum -from typing import Dict, List, Any -from pydantic import BaseModel, Field, HttpUrl, validator -from datetime import datetime +from typing import Any, Dict +from pydantic import BaseModel, Field, field_validator import re -class Config: - strict = True # Enables strict type checking - class SeverityEnum(str, Enum): HIGH = "High" MEDIUM = "Medium" @@ -29,7 +25,8 @@ class Finding(BaseModel): Status: StatusEnum = Field(..., description="Current status of the finding") Region: str = Field(default="", description="AWS region where the finding was identified") - @validator('Check_ID') + @field_validator("Check_ID") + @classmethod def validate_check_id(cls, v): """Validate that Check_ID follows the pattern XX-NN (e.g., SM-01, BR-14, AC-05)""" pattern = r'^[A-Z]{2,3}-\d{2}$' @@ -37,21 +34,24 @@ def validate_check_id(cls, v): raise ValueError('Check_ID must follow pattern XX-NN (e.g., SM-01, BR-14, AC-05)') return v - @validator('Reference') + @field_validator("Reference") + @classmethod def validate_reference_url(cls, v): """Validate that reference URL starts with https://""" if not str(v).startswith('https://'): raise ValueError('Reference URL must start with https://') return v - @validator('Severity') + @field_validator("Severity") + @classmethod def validate_severity(cls, v): """Validate that severity is one of the allowed values""" if v not in SeverityEnum.__members__.values(): raise ValueError('Severity must be one of the allowed values') return v - @validator('Status') + @field_validator("Status") + @classmethod def validate_status(cls, v): """Validate that status is one of the allowed values""" if v not in StatusEnum.__members__.values(): @@ -97,4 +97,4 @@ def create_finding( Status=status, Region=region ) - return dict(finding.model_dump()) # Convert to regular dictionary \ No newline at end of file + return dict(finding.model_dump()) # Convert to regular dictionary diff --git a/aiml-security-assessment/statemachine/assessments.asl.json b/aiml-security-assessment/statemachine/assessments.asl.json index f85a7c0..001de8f 100644 --- a/aiml-security-assessment/statemachine/assessments.asl.json +++ b/aiml-security-assessment/statemachine/assessments.asl.json @@ -91,10 +91,11 @@ "ItemsPath": "$.ResolvedRegions.regions", "ItemSelector": { "Region.$": "$$.Map.Item.Value", + "RegionIndex.$": "$$.Map.Item.Index", "Execution.$": "$$.Execution", "StateMachine.$": "$$.StateMachine" }, - "MaxConcurrency": 0, + "MaxConcurrency": ${MaxRegionConcurrency}, "ItemProcessor": { "ProcessorConfig": { "Mode": "INLINE" @@ -115,7 +116,8 @@ "Payload": { "Execution.$": "$.Execution", "StateMachine.$": "$.StateMachine", - "Region.$": "$.Region" + "Region.$": "$.Region", + "RegionIndex.$": "$.RegionIndex" } }, "Retry": [ @@ -147,7 +149,8 @@ "Payload": { "Execution.$": "$.Execution", "StateMachine.$": "$.StateMachine", - "Region.$": "$.Region" + "Region.$": "$.Region", + "RegionIndex.$": "$.RegionIndex" } }, "Retry": [ @@ -179,7 +182,8 @@ "Payload": { "Execution.$": "$.Execution", "StateMachine.$": "$.StateMachine", - "Region.$": "$.Region" + "Region.$": "$.Region", + "RegionIndex.$": "$.RegionIndex" } }, "Retry": [ diff --git a/aiml-security-assessment/template-multi-account.yaml b/aiml-security-assessment/template-multi-account.yaml index 2188736..bd82963 100644 --- a/aiml-security-assessment/template-multi-account.yaml +++ b/aiml-security-assessment/template-multi-account.yaml @@ -13,6 +13,15 @@ Parameters: Comma-separated list of AWS regions to scan (e.g., us-east-1,us-west-2,eu-west-1). Use 'all' to scan all regions where the service is available. Leave empty to scan only the deployment region. + MaxRegionConcurrency: + Type: Number + Default: 5 + MinValue: 1 + MaxValue: 40 + Description: > + Maximum number of regions scanned in parallel by the Map state. Lower + values reduce concurrent Lambda usage; higher values speed up large + multi-region scans. Set to 1 for fully sequential scanning. Resources: AIMLAssessmentStateMachine: @@ -28,6 +37,7 @@ Resources: AgentCoreSecurityAssessmentFunction: !GetAtt AgentCoreSecurityAssessmentFunction.Arn GenerateConsolidatedReportFunction: !GetAtt GenerateConsolidatedReportFunction.Arn AIMLAssessmentBucketName: !Ref AIMLAssessmentBucket + MaxRegionConcurrency: !Ref MaxRegionConcurrency Policies: - LambdaInvokePolicy: FunctionName: !Ref CleanupBucketFunction @@ -49,7 +59,7 @@ Resources: ResolveRegionsFunction: Type: AWS::Serverless::Function Properties: - FunctionName: !Sub 'aiml-security-ResolveRegions' + FunctionName: !Sub 'aiml-security-${AWS::StackName}-ResolveRegions' CodeUri: functions/security/resolve_regions/ Handler: app.lambda_handler Runtime: python3.12 @@ -64,7 +74,7 @@ Resources: CleanupBucketFunction: Type: AWS::Serverless::Function Properties: - FunctionName: !Sub 'aiml-security-CleanupBucket' + FunctionName: !Sub 'aiml-security-${AWS::StackName}-CleanupBucket' CodeUri: functions/security/cleanup_bucket/ Handler: app.lambda_handler Runtime: python3.12 @@ -82,7 +92,7 @@ Resources: IAMPermissionCachingFunction: Type: AWS::Serverless::Function Properties: - FunctionName: !Sub 'aiml-security-IAMPermissionCaching' + FunctionName: !Sub 'aiml-security-${AWS::StackName}-IAMPermissionCaching' CodeUri: functions/security/iam_permission_caching/ Handler: app.lambda_handler Runtime: python3.12 @@ -118,7 +128,7 @@ Resources: GenerateConsolidatedReportFunction: Type: AWS::Serverless::Function Properties: - FunctionName: !Sub 'aiml-security-GenerateReport' + FunctionName: !Sub 'aiml-security-${AWS::StackName}-GenerateReport' CodeUri: functions/security/generate_consolidated_report/ Handler: app.lambda_handler Runtime: python3.12 @@ -136,7 +146,7 @@ Resources: BedrockSecurityAssessmentFunction: Type: AWS::Serverless::Function Properties: - FunctionName: !Sub 'aiml-security-BedrockAssessment' + FunctionName: !Sub 'aiml-security-${AWS::StackName}-BedrockAssessment' CodeUri: functions/security/bedrock_assessments/ Handler: app.lambda_handler Runtime: python3.12 @@ -229,7 +239,7 @@ Resources: SagemakerSecurityAssessmentFunction: Type: AWS::Serverless::Function Properties: - FunctionName: !Sub 'aiml-security-SagemakerAssessment' + FunctionName: !Sub 'aiml-security-${AWS::StackName}-SagemakerAssessment' CodeUri: functions/security/sagemaker_assessments/ Handler: app.lambda_handler Runtime: python3.12 @@ -281,6 +291,7 @@ Resources: - kms:ListAliases - sagemaker:ListModelPackageGroups - sagemaker:ListModelPackages + - sagemaker:DescribeModelPackage - sagemaker:ListFeatureGroups - sagemaker:DescribeFeatureGroup - sagemaker:ListPipelines @@ -295,6 +306,19 @@ Resources: - sagemaker:DescribeEndpoint - sagemaker:ListDataQualityJobDefinitions - sagemaker:DescribeDataQualityJobDefinition + - sagemaker:ListTransformJobs + - sagemaker:DescribeTransformJob + - sagemaker:ListHyperParameterTuningJobs + - sagemaker:DescribeHyperParameterTuningJob + - sagemaker:ListCompilationJobs + - sagemaker:DescribeCompilationJob + - sagemaker:ListAutoMLJobs + - sagemaker:DescribeAutoMLJob + - sagemaker:ListExperiments + - sagemaker:DescribeExperiment + - sagemaker:ListTrials + - sagemaker:DescribeTrial + - sagemaker:ListAssociations Resource: '*' - Statement: - Sid: GuardDutyPermissions @@ -323,7 +347,7 @@ Resources: AgentCoreSecurityAssessmentFunction: Type: AWS::Serverless::Function Properties: - FunctionName: !Sub 'aiml-security-AgentCoreAssessment' + FunctionName: !Sub 'aiml-security-${AWS::StackName}-AgentCoreAssessment' CodeUri: functions/security/agentcore_assessments/ Handler: app.lambda_handler Runtime: python3.12 diff --git a/aiml-security-assessment/template.yaml b/aiml-security-assessment/template.yaml index c9d7e7e..a055de6 100644 --- a/aiml-security-assessment/template.yaml +++ b/aiml-security-assessment/template.yaml @@ -13,6 +13,15 @@ Parameters: Comma-separated list of AWS regions to scan (e.g., us-east-1,us-west-2,eu-west-1). Use 'all' to scan all regions where the service is available. Leave empty to scan only the deployment region. + MaxRegionConcurrency: + Type: Number + Default: 5 + MinValue: 1 + MaxValue: 40 + Description: > + Maximum number of regions scanned in parallel by the Map state. Lower + values reduce concurrent Lambda usage; higher values speed up large + multi-region scans. Set to 1 for fully sequential scanning. Resources: AIMLAssessmentStateMachine: @@ -28,6 +37,7 @@ Resources: AgentCoreSecurityAssessmentFunction: !GetAtt AgentCoreSecurityAssessmentFunction.Arn GenerateConsolidatedReportFunction: !GetAtt GenerateConsolidatedReportFunction.Arn AIMLAssessmentBucketName: !Ref AIMLAssessmentBucket + MaxRegionConcurrency: !Ref MaxRegionConcurrency Policies: - LambdaInvokePolicy: FunctionName: !Ref CleanupBucketFunction @@ -277,6 +287,7 @@ Resources: - kms:ListAliases - sagemaker:ListModelPackageGroups - sagemaker:ListModelPackages + - sagemaker:DescribeModelPackage - sagemaker:ListFeatureGroups - sagemaker:DescribeFeatureGroup - sagemaker:ListPipelines @@ -291,6 +302,19 @@ Resources: - sagemaker:DescribeEndpoint - sagemaker:ListDataQualityJobDefinitions - sagemaker:DescribeDataQualityJobDefinition + - sagemaker:ListTransformJobs + - sagemaker:DescribeTransformJob + - sagemaker:ListHyperParameterTuningJobs + - sagemaker:DescribeHyperParameterTuningJob + - sagemaker:ListCompilationJobs + - sagemaker:DescribeCompilationJob + - sagemaker:ListAutoMLJobs + - sagemaker:DescribeAutoMLJob + - sagemaker:ListExperiments + - sagemaker:DescribeExperiment + - sagemaker:ListTrials + - sagemaker:DescribeTrial + - sagemaker:ListAssociations Resource: '*' - Statement: - Sid: GuardDutyPermissions diff --git a/consolidate_html_reports.py b/consolidate_html_reports.py index 23d63a0..6a2d3b2 100644 --- a/consolidate_html_reports.py +++ b/consolidate_html_reports.py @@ -33,6 +33,16 @@ from report_template import generate_html_report +# Sentinel region label used by the per-service assessments to tag IAM-only +# findings that run once per execution rather than per region. It is not a real +# AWS region and must be excluded when counting scanned regions. +GLOBAL_REGION_LABEL = "Global" + + +def build_multi_account_report_key(timestamp: str) -> str: + """Build the consolidated multi-account HTML report object key.""" + return f"consolidated-reports/security_assessment_multi_account_{timestamp}.html" + def consolidate_html_reports(): """ @@ -85,7 +95,8 @@ def consolidate_html_reports(): for row in reader: # Map CSV columns to finding structure region = row.get("Region", "") - if region: + # "Global" tags IAM-only findings; not a scanned region. + if region and region != GLOBAL_REGION_LABEL: regions.add(region) finding = { "account_id": account_id, @@ -160,7 +171,7 @@ def consolidate_html_reports(): ) timestamp_file = datetime.now().strftime("%Y%m%d_%H%M%S") - s3_key = f"consolidated-reports/multi_account_report_{timestamp_file}.html" + s3_key = build_multi_account_report_key(timestamp_file) try: s3.put_object( diff --git a/docs/DEVELOPER_GUIDE.md b/docs/DEVELOPER_GUIDE.md index 24c3d42..6f9414e 100644 --- a/docs/DEVELOPER_GUIDE.md +++ b/docs/DEVELOPER_GUIDE.md @@ -44,7 +44,7 @@ ## Architecture Overview -The AI/ML Security Assessment Framework is a serverless, multi-account security assessment solution for AWS AI/ML workloads. It performs 51 security checks across Amazon Bedrock, Amazon SageMaker AI, and Amazon Bedrock AgentCore, generating interactive HTML reports with findings and remediation guidance. +The AI/ML Security Assessment Framework is a serverless, multi-account security assessment solution for AWS AI/ML workloads. It performs 52 security checks across Amazon Bedrock, Amazon SageMaker AI, and Amazon Bedrock AgentCore, generating interactive HTML reports with findings and remediation guidance. ### Security Design Principles @@ -168,7 +168,7 @@ sample-aiml-security-assessment/ "Scan Regions": { "Type": "Map", "ItemsPath": "$.ResolvedRegions.regions", - "MaxConcurrency": 0, + "MaxConcurrency": "${MaxRegionConcurrency}", "ItemProcessor": { "ProcessorConfig": {"Mode": "INLINE"}, "StartAt": "Run Security Assessments", @@ -196,7 +196,7 @@ sample-aiml-security-assessment/ ## Assessment Structure -The framework includes **51 security checks** across three AI/ML services. For the complete list of checks with descriptions, see the [Security Checks Reference](SECURITY_CHECKS.md). +The framework includes **52 security checks** across three AI/ML services. For the complete list of checks with descriptions, see the [Security Checks Reference](SECURITY_CHECKS.md). ### AWS Lambda Functions @@ -531,7 +531,7 @@ For detailed troubleshooting guidance, common issues, and debugging tips, see th ## Development Roadmap ### Current Status -- **AI/ML Assessment**: 51 security checks across three services (see [Security Checks Reference](SECURITY_CHECKS.md)) +- **AI/ML Assessment**: 52 security checks across three services (see [Security Checks Reference](SECURITY_CHECKS.md)) ### Potential Additions - **Amazon Comprehend**: Data privacy, access controls, entity recognition security diff --git a/docs/SECURITY_CHECKS.md b/docs/SECURITY_CHECKS.md index ae1a624..323831f 100644 --- a/docs/SECURITY_CHECKS.md +++ b/docs/SECURITY_CHECKS.md @@ -1,6 +1,6 @@ # Security Checks Reference -This document provides a comprehensive reference for all 51 security checks performed by the AI/ML Security Assessment framework. +This document provides a comprehensive reference for all 52 security checks performed by the AI/ML Security Assessment framework. ## Table of Contents @@ -9,7 +9,7 @@ This document provides a comprehensive reference for all 51 security checks perf - [Severity Levels](#severity-levels) - [Status Values](#status-values) - [Amazon SageMaker AI Security Checks (25)](#amazon-sagemaker-ai-security-checks-25) -- [Amazon Bedrock Security Checks (13)](#amazon-bedrock-security-checks-13) +- [Amazon Bedrock Security Checks (14)](#amazon-bedrock-security-checks-14) - [Amazon Bedrock AgentCore Security Checks (13)](#amazon-bedrock-agentcore-security-checks-13) --- @@ -21,7 +21,7 @@ The framework evaluates your AI/ML workloads against AWS security best practices | Service | Number of Checks | Focus Areas | |---------|------------------|-------------| | Amazon SageMaker AI | 25 | Security Hub controls, encryption, network isolation, IAM, MLOps | -| Amazon Bedrock | 13 | Guardrails, encryption, VPC endpoints, IAM permissions, logging | +| Amazon Bedrock | 14 | Guardrails, encryption, VPC endpoints, IAM permissions, logging | | Amazon Bedrock AgentCore | 13 | VPC configuration, encryption, observability, resource policies | --- @@ -33,7 +33,7 @@ Each security check has a unique identifier with a service prefix: | Prefix | Service | Example | |--------|---------|---------| | **SM-XX** | Amazon SageMaker | SM-01, SM-25 | -| **BR-XX** | Amazon Bedrock | BR-01, BR-13 | +| **BR-XX** | Amazon Bedrock | BR-01, BR-14 | | **AC-XX** | Amazon Bedrock AgentCore | AC-01, AC-13 | --- @@ -195,7 +195,7 @@ Each security check has a unique identifier with a service prefix: --- -## Amazon Bedrock Security Checks (13) +## Amazon Bedrock Security Checks (14) ### BR-01: AWS IAM Least Privilege @@ -262,6 +262,11 @@ Each security check has a unique identifier with a service prefix: - **Severity:** Medium - **Description:** Validates Bedrock Flows have guardrails attached. +### BR-14: Stale Bedrock Access + +- **Severity:** Medium +- **Description:** Detects principals with Bedrock permissions that have not used the service recently, using IAM service-last-accessed data. As an IAM-global check, it runs once per execution and is tagged with the `Global` region in multi-region scans. + --- ## Amazon Bedrock AgentCore Security Checks (13) diff --git a/docs/TROUBLESHOOTING.md b/docs/TROUBLESHOOTING.md index 486bd58..21ba9ff 100644 --- a/docs/TROUBLESHOOTING.md +++ b/docs/TROUBLESHOOTING.md @@ -335,14 +335,14 @@ A: All assessment data remains **entirely within your AWS account**: - Logs in **your Amazon CloudWatch Logs** (configurable retention) - No data is sent to external services or third parties -**Q: What IAM permissions does the assessment role need?** +**Q: What IAM permissions does the framework need?** -A: The framework uses **read-only permissions** only: -- AI/ML services: `List*`, `Describe*`, `Get*` actions -- AWS IAM: Read permissions for policy analysis -- Supporting services: AWS CloudTrail, Amazon GuardDuty, Amazon VPC (read-only) +A: The framework uses multiple roles, and only the Lambda runtime roles are close to read-only: +- **CodeBuild orchestration roles** (`CodeBuildRole`, `MultiAccountCodeBuildRole`) need deployment permissions to build SAM, create or update stacks, and start Step Functions executions. +- **`AIMLSecurityMemberRole`** in the target account is also not read-only in the multi-account flow, because it must allow the central CodeBuild project to deploy or update the per-account SAM stack before the assessment runs. +- **Assessment Lambda execution roles** are primarily read-oriented. They use AI/ML service `List*`, `Describe*`, and `Get*` APIs plus supporting read APIs, and S3 access to read the IAM cache and write reports. -See the main [README](../README.md#permissions-required) for the complete permission list. +See [README - Permissions Required](../README.md#permissions-required) for the role breakdown and the template files that define each policy. **Q: Is this assessment sufficient for compliance requirements (SOC 2, HIPAA, and similar)?** diff --git a/tests/conftest.py b/tests/conftest.py index b459b03..a08be64 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -266,6 +266,10 @@ def assert_finding_schema(finding): "Reference", "Severity", "Status", + # Region is part of the multi-region finding schema. Every finding + # produced via create_finding() must carry it (may be an empty string + # when a check is invoked without a region). + "Region", } assert required_keys.issubset(finding.keys()), ( f"Missing keys: {required_keys - finding.keys()}" @@ -273,3 +277,5 @@ def assert_finding_schema(finding): assert finding["Severity"] in ("High", "Medium", "Low", "Informational") assert finding["Status"] in ("Failed", "Passed", "N/A") assert finding["Reference"].startswith("https://") + # Region must be a string (default "" when unset). + assert isinstance(finding["Region"], str) diff --git a/tests/test_agentcore_checks.py b/tests/test_agentcore_checks.py index 547588e..0cce9b8 100644 --- a/tests/test_agentcore_checks.py +++ b/tests/test_agentcore_checks.py @@ -17,8 +17,8 @@ import sys import os import importlib.util -from unittest.mock import patch -from botocore.exceptions import ClientError +from unittest.mock import patch, MagicMock +from botocore.exceptions import ClientError, EndpointConnectionError sys.path.insert(0, "aiml-security-assessment/functions/security/agentcore_assessments") sys.path.insert(0, os.path.join(os.path.dirname(__file__))) @@ -599,3 +599,161 @@ def test_ac13_schema_valid(self): result = agentcore_app.check_agentcore_gateway_configuration() for f in extract_csv_data(result): assert_finding_schema(f) + + +# =================================================================== +# lambda_handler: multi-region gating and availability probe +# =================================================================== +def _agentcore_event(region="us-east-1", region_index=0): + return { + "Region": region, + "RegionIndex": region_index, + "Execution": {"Name": "test-execution-1"}, + "StateMachine": {"Name": "test-sm"}, + } + + +def _valid_slr_role(): + """A valid service-linked-role get_role response so AC-09 passes cleanly.""" + return { + "Role": { + "RoleName": "AWSServiceRoleForBedrockAgentCoreNetwork", + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Effect": "Allow", + "Principal": {"Service": "agentcore.bedrock.amazonaws.com"}, + "Action": "sts:AssumeRole", + } + ] + }, + } + } + + +class TestAgentCoreHandlerMultiRegion: + """lambda_handler primary-region gating (AC-02/AC-03/AC-09) + availability probe (AC-00).""" + + def _run_handler(self, agentcore_side_effect, event): + """Run the handler with a per-service boto3.client dispatch. The + bedrock-agentcore-control probe (list_agent_runtimes) uses + agentcore_side_effect to simulate availability; iam is given a valid SLR + response. Returns (response, findings) where findings is the flat list + passed to generate_csv_report.""" + captured = {} + + def fake_csv(findings): + captured["findings"] = findings + return "csv" + + iam_mock = MagicMock() + iam_mock.get_role.return_value = _valid_slr_role() + iam_mock.exceptions.NoSuchEntityException = type( + "NoSuchEntityException", (Exception,), {} + ) + + sts_mock = MagicMock() + sts_mock.get_caller_identity.return_value = {"Account": "123456789012"} + + agentcore_mock = MagicMock() + agentcore_mock.list_agent_runtimes.side_effect = agentcore_side_effect + + def client_dispatch(service, *args, **kwargs): + if service == "iam": + return iam_mock + if service == "sts": + return sts_mock + if service == "bedrock-agentcore-control": + return agentcore_mock + return MagicMock() + + with patch("agentcore_app.boto3.client", side_effect=client_dispatch), \ + patch.object(agentcore_app, "get_permissions_cache", return_value={ + "role_permissions": {}, "user_permissions": {} + }), \ + patch.object(agentcore_app, "generate_csv_report", side_effect=fake_csv), \ + patch.object(agentcore_app, "write_to_s3", return_value="s3://b/r.csv"): + resp = agentcore_app.lambda_handler(event, None) + + return resp, captured.get("findings", []) + + def test_primary_region_emits_global_iam_checks_tagged_global(self): + # On the primary region, AC-02, AC-03 and AC-09 (all IAM-global) must be + # emitted and tagged "Global", even when AgentCore is unavailable. + resp, findings = self._run_handler( + EndpointConnectionError(endpoint_url="https://agentcore.invalid"), + _agentcore_event(region="ap-south-2", region_index=0), + ) + assert resp["statusCode"] == 200 + + check_ids = {f["Check_ID"] for f in findings} + assert "AC-02" in check_ids + assert "AC-03" in check_ids + assert "AC-09" in check_ids + for f in findings: + if f["Check_ID"] in ("AC-02", "AC-03", "AC-09"): + assert f["Region"] == "Global" + # The availability finding is tagged with the scanned region. + ac00 = [f for f in findings if f["Check_ID"] == "AC-00"] + assert ac00 and ac00[0]["Region"] == "ap-south-2" + + def test_non_primary_region_skips_global_iam_checks(self): + # On a non-primary region the IAM-global checks must NOT run. + resp, findings = self._run_handler( + EndpointConnectionError(endpoint_url="https://agentcore.invalid"), + _agentcore_event(region="eu-west-1", region_index=3), + ) + assert resp["statusCode"] == 200 + + check_ids = {f["Check_ID"] for f in findings} + assert "AC-02" not in check_ids + assert "AC-03" not in check_ids + assert "AC-09" not in check_ids + assert check_ids == {"AC-00"} + + def test_optin_region_error_treated_as_unavailable(self): + # A region-not-enabled ClientError code makes agentcore_client None, so + # the handler emits the AC-00 availability finding and exits early. + resp, findings = self._run_handler( + _make_client_error("UnrecognizedClientException"), + _agentcore_event(region="me-south-1", region_index=1), + ) + assert resp["statusCode"] == 200 + ac00 = [f for f in findings if f["Check_ID"] == "AC-00"] + assert ac00 and ac00[0]["Status"] == "N/A" + + def test_access_denied_probe_proceeds_with_checks(self): + # AccessDenied is NOT a region-unavailable code: the service is reachable, + # so the handler proceeds and runs regional checks (no AC-00 emitted). + resp, findings = self._run_handler( + _make_client_error("AccessDeniedException"), + _agentcore_event(region="us-east-1", region_index=0), + ) + assert resp["statusCode"] == 200 + check_ids = {f["Check_ID"] for f in findings} + assert "AC-00" not in check_ids + # Regional checks ran (e.g. AC-01 VPC, AC-04 observability present). + assert len(check_ids) > 3 + + def test_unexpected_probe_error_proceeds_with_checks(self): + # An unexpected, non-ClientError probe failure (e.g. a boto3/botocore SDK + # param/operation mismatch surfacing as ParamValidationError) says nothing + # about regional availability. The handler must NOT treat it as + # unavailable (which would emit a false AC-00 N/A and skip every check); + # it should proceed and run the regional checks. + try: + from botocore.exceptions import ParamValidationError + + probe_error = ParamValidationError(report="maxResults is not a valid parameter") + except Exception: + probe_error = TypeError("unexpected SDK signature") + + resp, findings = self._run_handler( + probe_error, + _agentcore_event(region="us-east-1", region_index=0), + ) + assert resp["statusCode"] == 200 + check_ids = {f["Check_ID"] for f in findings} + # No false "not available" finding, and the regional checks executed. + assert "AC-00" not in check_ids + assert len(check_ids) > 3 diff --git a/tests/test_bedrock_checks.py b/tests/test_bedrock_checks.py index c201d22..0c5a592 100644 --- a/tests/test_bedrock_checks.py +++ b/tests/test_bedrock_checks.py @@ -13,6 +13,7 @@ import os import importlib.util from unittest.mock import patch, MagicMock +from botocore.exceptions import EndpointConnectionError, ClientError # Add tests dir so we can import helpers sys.path.insert(0, os.path.join(os.path.dirname(__file__))) @@ -431,6 +432,22 @@ def test_br07_exception_returns_error_finding(self, mock_client): assert len(findings) >= 1 assert findings[0]["Status"] == "Failed" + @patch("boto3.client") + def test_br07_list_prompts_api_error_returns_na(self, mock_client): + # An API error (e.g. InternalServerErrorException after retries) is not a + # security failure; it should surface as N/A, not Failed (matches BR-11). + check = bedrock_app.check_bedrock_prompt_management + mock_agent = MagicMock() + mock_client.return_value = mock_agent + mock_agent.list_prompts.side_effect = Exception( + "InternalServerErrorException" + ) + result = check() + findings = extract_csv_data(result) + assert len(findings) >= 1 + assert findings[0]["Status"] == "N/A" + assert findings[0]["Check_ID"] == "BR-07" + @patch("boto3.client") def test_br07_schema_valid(self, mock_client): check = bedrock_app.check_bedrock_prompt_management @@ -699,6 +716,40 @@ def test_br11_schema_valid(self, mock_client): for f in extract_csv_data(result): assert_finding_schema(f) + @patch("boto3.client") + def test_br11_unknown_operation_returns_clean_message(self, mock_client): + # Regions without the custom model API surface "Unknown operation + # ListCustomModels" / UnknownOperationException; report a clean message + # instead of leaking the raw boto3 exception text. + check = bedrock_app.check_bedrock_custom_model_encryption + mock_bedrock = MagicMock() + mock_client.return_value = mock_bedrock + mock_bedrock.get_paginator.side_effect = Exception( + "ValidationException: Unknown operation ListCustomModels" + ) + result = check(region="us-west-1") + findings = extract_csv_data(result) + assert len(findings) >= 1 + assert findings[0]["Status"] == "N/A" + assert ( + findings[0]["Finding_Details"] + == "Custom model API not available in us-west-1" + ) + + @patch("boto3.client") + def test_br11_other_list_error_preserves_raw_message(self, mock_client): + # Genuine errors (e.g. permissions) keep the raw text so they stay + # diagnosable. + check = bedrock_app.check_bedrock_custom_model_encryption + mock_bedrock = MagicMock() + mock_client.return_value = mock_bedrock + mock_bedrock.get_paginator.side_effect = Exception("AccessDeniedException") + result = check(region="us-east-1") + findings = extract_csv_data(result) + assert len(findings) >= 1 + assert findings[0]["Status"] == "N/A" + assert "AccessDeniedException" in findings[0]["Finding_Details"] + # =================================================================== # BR-12: check_bedrock_invocation_log_encryption @@ -908,3 +959,199 @@ def test_br13_schema_valid(self, mock_client): result = check() for f in extract_csv_data(result): assert_finding_schema(f) + + @patch("boto3.client") + def test_br13_unknown_operation_returns_clean_message(self, mock_client): + # Regions without the Flows API surface an "Unknown operation" error; + # report a clean message instead of leaking the raw boto3 text. + check = bedrock_app.check_bedrock_flows_guardrails + mock_agent = MagicMock() + mock_client.return_value = mock_agent + mock_agent.get_paginator.side_effect = Exception( + "UnknownOperationException" + ) + result = check(region="us-west-1") + findings = extract_csv_data(result) + assert len(findings) >= 1 + assert findings[0]["Status"] == "N/A" + assert ( + findings[0]["Finding_Details"] + == "Bedrock Flows API not available in us-west-1" + ) + + +# =================================================================== +# describe_api_error helper +# =================================================================== +class TestDescribeApiError: + """Shared helper that maps region-unavailability errors to clean text.""" + + def test_unknown_operation_phrase_returns_clean_message(self): + msg = bedrock_app.describe_api_error( + Exception("ValidationException: Unknown operation ListPrompts"), + "Bedrock Prompt Management API", + "us-east-2", + ) + assert msg == "Bedrock Prompt Management API not available in us-east-2" + + def test_unknown_operation_exception_returns_clean_message(self): + msg = bedrock_app.describe_api_error( + Exception("UnknownOperationException"), "Custom model API", "us-west-1" + ) + assert msg == "Custom model API not available in us-west-1" + + def test_missing_region_falls_back_to_generic_location(self): + msg = bedrock_app.describe_api_error( + Exception("Unknown operation Foo"), "Custom model API" + ) + assert msg == "Custom model API not available in this region" + + def test_other_error_preserves_raw_text(self): + msg = bedrock_app.describe_api_error( + Exception("AccessDeniedException"), "Custom model API", "us-east-1" + ) + assert msg == "Unable to check Custom model API: AccessDeniedException" + + +# =================================================================== +# lambda_handler: multi-region gating and availability probe +# =================================================================== +def _make_client_error(code, message="error"): + return ClientError({"Error": {"Code": code, "Message": message}}, "operation") + + +def _bedrock_event(region="us-east-1", region_index=0): + return { + "Region": region, + "RegionIndex": region_index, + "Execution": {"Name": "test-execution-1"}, + "StateMachine": {"Name": "test-sm"}, + } + + +class TestBedrockHandlerMultiRegion: + """lambda_handler primary-region gating + availability probe (BR-00/BR-01/BR-03).""" + + def _run_handler_unavailable(self, mock_client, event): + """Drive the handler down the 'Bedrock unavailable' early-return path and + return the findings captured via generate_csv_report. The availability + probe raises EndpointConnectionError so no regional checks run.""" + captured = {} + + def fake_csv(findings): + captured["findings"] = findings + return "csv" + + test_client = MagicMock() + test_client.get_model_invocation_logging_configuration.side_effect = ( + EndpointConnectionError(endpoint_url="https://bedrock.invalid") + ) + mock_client.return_value = test_client + + with patch.object(bedrock_app, "get_permissions_cache", return_value={ + "role_permissions": {}, "user_permissions": {} + }), patch.object(bedrock_app, "generate_csv_report", side_effect=fake_csv), \ + patch.object(bedrock_app, "write_to_s3", return_value="s3://bucket/report.csv"): + resp = bedrock_app.lambda_handler(event, None) + + return resp, captured.get("findings", []) + + @patch("bedrock_app.boto3.client") + def test_primary_region_emits_global_iam_checks_tagged_global(self, mock_client): + # On the primary region, BR-01 and BR-03 (IAM-global) must be emitted and + # tagged "Global", even when Bedrock itself is unavailable in the region. + resp, findings = self._run_handler_unavailable( + mock_client, _bedrock_event(region="ap-south-2", region_index=0) + ) + assert resp["statusCode"] == 200 + + rows = [r for f in findings for r in f.get("csv_data", [])] + check_ids = {r["Check_ID"] for r in rows} + assert "BR-01" in check_ids + assert "BR-03" in check_ids + # Every global IAM finding is tagged Global, not the scanned region. + for r in rows: + if r["Check_ID"] in ("BR-01", "BR-03"): + assert r["Region"] == "Global" + # The availability finding itself is tagged with the scanned region. + br00 = [r for r in rows if r["Check_ID"] == "BR-00"] + assert br00 and br00[0]["Region"] == "ap-south-2" + + @patch("bedrock_app.boto3.client") + def test_non_primary_region_skips_global_iam_checks(self, mock_client): + # On a non-primary region (index > 0), the IAM-global checks must NOT run, + # so they are not duplicated once per scanned region. + resp, findings = self._run_handler_unavailable( + mock_client, _bedrock_event(region="eu-west-1", region_index=1) + ) + assert resp["statusCode"] == 200 + + rows = [r for f in findings for r in f.get("csv_data", [])] + check_ids = {r["Check_ID"] for r in rows} + assert "BR-01" not in check_ids + assert "BR-03" not in check_ids + # Only the BR-00 availability finding should be present. + assert check_ids == {"BR-00"} + + @patch("bedrock_app.boto3.client") + def test_optin_region_error_treated_as_unavailable(self, mock_client): + # A region-not-enabled error code (e.g. UnrecognizedClientException) is + # treated like an endpoint failure: emit a single BR-00 N/A finding. + captured = {} + + def fake_csv(findings): + captured["findings"] = findings + return "csv" + + test_client = MagicMock() + test_client.get_model_invocation_logging_configuration.side_effect = ( + _make_client_error("UnrecognizedClientException") + ) + mock_client.return_value = test_client + + with patch.object(bedrock_app, "get_permissions_cache", return_value={ + "role_permissions": {}, "user_permissions": {} + }), patch.object(bedrock_app, "generate_csv_report", side_effect=fake_csv), \ + patch.object(bedrock_app, "write_to_s3", return_value="s3://b/r.csv"): + resp = bedrock_app.lambda_handler( + _bedrock_event(region="me-south-1", region_index=1), None + ) + + assert resp["statusCode"] == 200 + rows = [r for f in captured["findings"] for r in f.get("csv_data", [])] + br00 = [r for r in rows if r["Check_ID"] == "BR-00"] + assert br00 and br00[0]["Status"] == "N/A" + assert "me-south-1" in br00[0]["Finding_Details"] + + @patch("bedrock_app.boto3.client") + def test_validation_exception_proceeds_with_checks(self, mock_client): + # A ValidationException from the probe means logging simply isn't + # configured — the service IS reachable, so the handler must NOT short + # circuit; it should run the regional checks (no BR-00 finding emitted). + captured = {} + + def fake_csv(findings): + captured["findings"] = findings + return "csv" + + test_client = MagicMock() + test_client.get_model_invocation_logging_configuration.side_effect = ( + _make_client_error("ValidationException") + ) + mock_client.return_value = test_client + + with patch.object(bedrock_app, "get_permissions_cache", return_value={ + "role_permissions": {}, "user_permissions": {} + }), patch.object(bedrock_app, "generate_csv_report", side_effect=fake_csv), \ + patch.object(bedrock_app, "write_to_s3", return_value="s3://b/r.csv"): + resp = bedrock_app.lambda_handler( + _bedrock_event(region="us-east-1", region_index=0), None + ) + + assert resp["statusCode"] == 200 + rows = [r for f in captured["findings"] for r in f.get("csv_data", [])] + check_ids = {r["Check_ID"] for r in rows} + # Service reachable => no availability (BR-00) finding, and regional + # checks ran (e.g. BR-04 logging, BR-05 guardrails are present). + assert "BR-00" not in check_ids + assert len(check_ids) > 3 diff --git a/tests/test_consolidated_report.py b/tests/test_consolidated_report.py new file mode 100644 index 0000000..16c8ac3 --- /dev/null +++ b/tests/test_consolidated_report.py @@ -0,0 +1,207 @@ +""" +Tests for the consolidated report generator's finding aggregation. + +Focus: multi-region dedup. Global/IAM findings (Region == "Global") are +produced once per run by the primary-region Lambda, but the consolidator must +not double-count a finding even if it ever lands in more than one region's CSV +(e.g. RegionIndex missing from the event). Regional findings that differ only +by Region must NOT be collapsed. +""" + +import sys +import os +import importlib.util + +# The consolidator imports `report_template` as a top-level module, so its +# directory must be on sys.path before app.py is loaded. +_report_dir = os.path.abspath( + os.path.join( + os.path.dirname(__file__), + "..", + "aiml-security-assessment/functions/security/generate_consolidated_report", + ) +) +if _report_dir not in sys.path: + sys.path.insert(0, _report_dir) + +_spec = importlib.util.spec_from_file_location( + "consolidated_report_app", os.path.join(_report_dir, "app.py") +) +consolidated_app = importlib.util.module_from_spec(_spec) +sys.modules["consolidated_report_app"] = consolidated_app +_spec.loader.exec_module(consolidated_app) + + +def _finding(check_id, region, status="Failed", details=None, name="Test Finding"): + """Build a CSV-style finding row as parsed from a per-region report.""" + return { + "Check_ID": check_id, + "Finding": name, + "Finding_Details": details if details is not None else f"{check_id} details", + "Resolution": "Do the thing", + "Reference": "https://docs.aws.amazon.com/", + "Severity": "High", + "Status": status, + "Region": region, + } + + +def _build_results(bedrock_reports): + """Wrap per-file finding lists into the assessment_results structure.""" + return { + "execution_id": "exec-123", + "account_id": "111122223333", + "bedrock": bedrock_reports, + "sagemaker": {}, + "agentcore": {}, + } + + +class TestGlobalFindingDedup: + """A duplicated global finding across region files is counted once.""" + + def test_global_finding_in_multiple_region_files_counted_once(self, monkeypatch): + captured = {} + + def fake_template(**kwargs): + captured.update(kwargs) + return "" + + monkeypatch.setattr(consolidated_app, "generate_report_from_template", fake_template) + + # Same global BR-01 finding written into two different region files. + results = _build_results( + { + "bedrock_security_report_exec-123_us-east-1": [ + _finding("BR-01", "Global"), + ], + "bedrock_security_report_exec-123_us-west-2": [ + _finding("BR-01", "Global"), + ], + } + ) + + consolidated_app.generate_html_report(results) + + all_findings = captured["all_findings"] + br01 = [f for f in all_findings if f["Check_ID"] == "BR-01"] + assert len(br01) == 1, "Duplicated global finding should be deduped to one" + assert captured["service_stats"]["bedrock"]["failed"] == 1 + + def test_regional_findings_differing_by_region_are_kept(self, monkeypatch): + captured = {} + monkeypatch.setattr( + consolidated_app, + "generate_report_from_template", + lambda **kwargs: captured.update(kwargs) or "", + ) + + # Same check id but genuinely per-region findings -> must both survive. + results = _build_results( + { + "bedrock_security_report_exec-123_us-east-1": [ + _finding("BR-05", "us-east-1"), + ], + "bedrock_security_report_exec-123_us-west-2": [ + _finding("BR-05", "us-west-2"), + ], + } + ) + + consolidated_app.generate_html_report(results) + + br05 = [f for f in captured["all_findings"] if f["Check_ID"] == "BR-05"] + assert len(br05) == 2, "Distinct regional findings must not be collapsed" + assert captured["service_stats"]["bedrock"]["failed"] == 2 + assert set(captured["regions"]) == {"us-east-1", "us-west-2"} + + def test_distinct_findings_same_region_are_kept(self, monkeypatch): + captured = {} + monkeypatch.setattr( + consolidated_app, + "generate_report_from_template", + lambda **kwargs: captured.update(kwargs) or "", + ) + + # Same check id and region but different details (e.g. two flagged roles). + results = _build_results( + { + "bedrock_security_report_exec-123_us-east-1": [ + _finding("BR-01", "Global", details="Role A is over-permissive"), + _finding("BR-01", "Global", details="Role B is over-permissive"), + ], + } + ) + + consolidated_app.generate_html_report(results) + + br01 = [f for f in captured["all_findings"] if f["Check_ID"] == "BR-01"] + assert len(br01) == 2, "Findings differing by detail must be kept" + + +class TestRegionCounting: + """The "Global" sentinel (IAM-only findings) must not be counted as a + scanned region — otherwise a default single-region scan renders as + multi-region (region filter, "Risk by Region", "N Regions" header).""" + + def _capture(self, monkeypatch): + captured = {} + monkeypatch.setattr( + consolidated_app, + "generate_report_from_template", + lambda **kwargs: captured.update(kwargs) or "", + ) + return captured + + def test_global_only_does_not_count_as_region(self, monkeypatch): + # Default single-region scan: one real region plus the IAM-global finding. + captured = self._capture(monkeypatch) + results = _build_results( + { + "bedrock_security_report_exec-123_us-east-1": [ + _finding("BR-01", "Global"), + _finding("BR-05", "us-east-1", status="Passed"), + ], + } + ) + + consolidated_app.generate_html_report(results) + + # Only the real region is counted; "Global" is excluded. + assert captured["regions"] == ["us-east-1"] + assert "Global" not in captured["regions"] + + def test_genuine_multi_region_still_counted(self, monkeypatch): + # Two real regions plus a global finding -> still multi-region. + captured = self._capture(monkeypatch) + results = _build_results( + { + "bedrock_security_report_exec-123_us-east-1": [ + _finding("BR-01", "Global"), + _finding("BR-05", "us-east-1", status="Passed"), + ], + "bedrock_security_report_exec-123_us-west-2": [ + _finding("BR-05", "us-west-2", status="Passed"), + ], + } + ) + + consolidated_app.generate_html_report(results) + + assert set(captured["regions"]) == {"us-east-1", "us-west-2"} + + def test_global_only_yields_no_regions(self, monkeypatch): + # Every region unavailable: only global findings exist. regions -> None + # so the report shows no multi-region UI rather than a "Global" region. + captured = self._capture(monkeypatch) + results = _build_results( + { + "bedrock_security_report_exec-123_us-east-1": [ + _finding("BR-01", "Global"), + ], + } + ) + + consolidated_app.generate_html_report(results) + + assert captured["regions"] is None diff --git a/tests/test_report_filenames.py b/tests/test_report_filenames.py new file mode 100644 index 0000000..e98f770 --- /dev/null +++ b/tests/test_report_filenames.py @@ -0,0 +1,48 @@ +import importlib.util +import os +import sys + + +REPO_ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), "..")) +REPORT_APP_DIR = os.path.join( + REPO_ROOT, + "aiml-security-assessment", + "functions", + "security", + "generate_consolidated_report", +) + +if REPORT_APP_DIR not in sys.path: + sys.path.insert(0, REPORT_APP_DIR) + + +def _load_module(name: str, path: str): + spec = importlib.util.spec_from_file_location(name, path) + module = importlib.util.module_from_spec(spec) + sys.modules[name] = module + spec.loader.exec_module(module) + return module + + +single_report_app = _load_module( + "single_report_app", + os.path.join(REPORT_APP_DIR, "app.py"), +) +multi_report_app = _load_module( + "multi_report_app", + os.path.join(REPO_ROOT, "consolidate_html_reports.py"), +) + + +def test_single_account_report_filename_does_not_include_execution_id(): + key = single_report_app.build_single_account_report_key("20260614_120000") + assert key == "security_assessment_single_account_20260614_120000.html" + assert "exec" not in key + + +def test_multi_account_report_filename_uses_consistent_prefix(): + key = multi_report_app.build_multi_account_report_key("20260614_120000") + assert ( + key + == "consolidated-reports/security_assessment_multi_account_20260614_120000.html" + ) diff --git a/tests/test_sagemaker_checks.py b/tests/test_sagemaker_checks.py index f226273..923428a 100644 --- a/tests/test_sagemaker_checks.py +++ b/tests/test_sagemaker_checks.py @@ -13,6 +13,7 @@ import os import importlib.util from unittest.mock import patch, MagicMock +from botocore.exceptions import EndpointConnectionError, ClientError sys.path.insert(0, os.path.join(os.path.dirname(__file__))) from conftest import extract_csv_data, assert_finding_schema @@ -163,6 +164,71 @@ def test_sm02_schema_valid(self, empty_permission_cache): for f in extract_csv_data(result): assert_finding_schema(f) + def test_sm02_iam_check_does_not_query_domains( + self, permission_cache_sagemaker_full_access + ): + # The IAM-global SM-02 check must NOT call regional SageMaker domain APIs; + # domain/SSO inspection lives in check_sagemaker_sso_configuration so it is + # not duplicated per region. Only IAM findings should be produced here. + check = sagemaker_app.check_sagemaker_iam_permissions + result = check(permission_cache_sagemaker_full_access, region="Global") + findings = extract_csv_data(result) + # No SSO finding should be emitted from the IAM-global check + assert all("SSO" not in f["Finding"] for f in findings) + + +# =================================================================== +# SM-02b: check_sagemaker_sso_configuration (regional) +# =================================================================== +class TestSM02SSOConfiguration: + """SM-02: Regional SageMaker domain SSO configuration check.""" + + @patch("sagemaker_app.boto3.client") + def test_sso_no_domains_returns_passed(self, mock_client): + check = sagemaker_app.check_sagemaker_sso_configuration + mock_sm = MagicMock() + mock_client.return_value = mock_sm + mock_paginator = MagicMock() + mock_paginator.paginate.return_value = [{"Domains": []}] + mock_sm.get_paginator.return_value = mock_paginator + result = check(region="us-east-1") + findings = extract_csv_data(result) + assert len(findings) >= 1 + assert findings[0]["Check_ID"] == "SM-02" + assert findings[0]["Status"] == "Passed" + + @patch("sagemaker_app.boto3.client") + def test_sso_non_sso_domain_returns_failed(self, mock_client): + check = sagemaker_app.check_sagemaker_sso_configuration + mock_sm = MagicMock() + mock_client.return_value = mock_sm + mock_paginator = MagicMock() + mock_paginator.paginate.return_value = [ + {"Domains": [{"DomainId": "d-123"}]} + ] + mock_sm.get_paginator.return_value = mock_paginator + mock_sm.describe_domain.return_value = { + "DomainName": "test-domain", + "AuthMode": "IAM", + } + result = check(region="us-east-1") + findings = extract_csv_data(result) + assert len(findings) >= 1 + assert findings[0]["Status"] == "Failed" + assert "SSO" in findings[0]["Finding"] + + @patch("sagemaker_app.boto3.client") + def test_sso_schema_valid(self, mock_client): + check = sagemaker_app.check_sagemaker_sso_configuration + mock_sm = MagicMock() + mock_client.return_value = mock_sm + mock_paginator = MagicMock() + mock_paginator.paginate.return_value = [{"Domains": []}] + mock_sm.get_paginator.return_value = mock_paginator + result = check(region="us-east-1") + for f in extract_csv_data(result): + assert_finding_schema(f) + # =================================================================== # SM-03: check_sagemaker_data_protection @@ -1325,3 +1391,140 @@ def test_sm25_schema_valid(self, mock_client): result = check() for f in extract_csv_data(result): assert_finding_schema(f) + + +# =================================================================== +# lambda_handler: multi-region gating and availability probe +# =================================================================== +def _make_client_error(code, message="error"): + return ClientError({"Error": {"Code": code, "Message": message}}, "operation") + + +def _sagemaker_event(region="us-east-1", region_index=0): + return { + "Region": region, + "RegionIndex": region_index, + "Execution": {"Name": "test-execution-1"}, + "StateMachine": {"Name": "test-sm"}, + } + + +class TestSageMakerHandlerMultiRegion: + """lambda_handler primary-region gating (SM-02) + availability probe (SM-00).""" + + def _run_handler_unavailable(self, mock_client, event): + """Drive the handler down the 'SageMaker unavailable' early-return path. + The availability probe raises EndpointConnectionError so no regional + checks run; only global IAM checks (if primary) plus SM-00 are emitted.""" + captured = {} + + def fake_csv(findings): + captured["findings"] = findings + return "csv" + + test_client = MagicMock() + test_client.list_notebook_instances.side_effect = EndpointConnectionError( + endpoint_url="https://sagemaker.invalid" + ) + mock_client.return_value = test_client + + with patch.object(sagemaker_app, "get_permissions_cache", return_value={ + "role_permissions": {}, "user_permissions": {} + }), patch.object(sagemaker_app, "generate_csv_report", side_effect=fake_csv), \ + patch.object(sagemaker_app, "write_to_s3", return_value="s3://b/r.csv"): + resp = sagemaker_app.lambda_handler(event, None) + + return resp, captured.get("findings", []) + + @patch("sagemaker_app.boto3.client") + def test_primary_region_emits_global_iam_check_tagged_global(self, mock_client): + # On the primary region, the IAM-global SM-02 check must be emitted and + # tagged "Global", even when SageMaker is unavailable in the region. + resp, findings = self._run_handler_unavailable( + mock_client, _sagemaker_event(region="ap-south-2", region_index=0) + ) + assert resp["statusCode"] == 200 + + rows = [r for f in findings for r in f.get("csv_data", [])] + sm02 = [r for r in rows if r["Check_ID"] == "SM-02"] + assert sm02, "SM-02 IAM-global finding should be present on primary region" + for r in sm02: + assert r["Region"] == "Global" + # The availability finding is tagged with the scanned region. + sm00 = [r for r in rows if r["Check_ID"] == "SM-00"] + assert sm00 and sm00[0]["Region"] == "ap-south-2" + + @patch("sagemaker_app.boto3.client") + def test_non_primary_region_skips_global_iam_check(self, mock_client): + # On a non-primary region the IAM-global SM-02 check must NOT run. + resp, findings = self._run_handler_unavailable( + mock_client, _sagemaker_event(region="eu-west-1", region_index=2) + ) + assert resp["statusCode"] == 200 + + rows = [r for f in findings for r in f.get("csv_data", [])] + check_ids = {r["Check_ID"] for r in rows} + assert "SM-02" not in check_ids + assert check_ids == {"SM-00"} + + @patch("sagemaker_app.boto3.client") + def test_optin_region_error_treated_as_unavailable(self, mock_client): + # A region-not-enabled error code is treated like an endpoint failure: + # emit a single SM-00 N/A finding (no regional checks). + captured = {} + + def fake_csv(findings): + captured["findings"] = findings + return "csv" + + test_client = MagicMock() + test_client.list_notebook_instances.side_effect = _make_client_error( + "OptInRequired" + ) + mock_client.return_value = test_client + + with patch.object(sagemaker_app, "get_permissions_cache", return_value={ + "role_permissions": {}, "user_permissions": {} + }), patch.object(sagemaker_app, "generate_csv_report", side_effect=fake_csv), \ + patch.object(sagemaker_app, "write_to_s3", return_value="s3://b/r.csv"): + resp = sagemaker_app.lambda_handler( + _sagemaker_event(region="ap-east-1", region_index=1), None + ) + + assert resp["statusCode"] == 200 + rows = [r for f in captured["findings"] for r in f.get("csv_data", [])] + sm00 = [r for r in rows if r["Check_ID"] == "SM-00"] + assert sm00 and sm00[0]["Status"] == "N/A" + assert "ap-east-1" in sm00[0]["Finding_Details"] + + @patch("sagemaker_app.boto3.client") + def test_access_denied_probe_proceeds_with_checks(self, mock_client): + # AccessDenied is NOT in REGION_UNAVAILABLE_ERROR_CODES: the service is + # reachable, so the handler must proceed and run regional checks rather + # than short-circuiting with SM-00. + captured = {} + + def fake_csv(findings): + captured["findings"] = findings + return "csv" + + test_client = MagicMock() + test_client.list_notebook_instances.side_effect = _make_client_error( + "AccessDeniedException" + ) + mock_client.return_value = test_client + + with patch.object(sagemaker_app, "get_permissions_cache", return_value={ + "role_permissions": {}, "user_permissions": {} + }), patch.object(sagemaker_app, "generate_csv_report", side_effect=fake_csv), \ + patch.object(sagemaker_app, "write_to_s3", return_value="s3://b/r.csv"): + resp = sagemaker_app.lambda_handler( + _sagemaker_event(region="us-east-1", region_index=0), None + ) + + assert resp["statusCode"] == 200 + rows = [r for f in captured["findings"] for r in f.get("csv_data", [])] + check_ids = {r["Check_ID"] for r in rows} + # Reachable => no SM-00, and many regional checks ran. + assert "SM-00" not in check_ids + assert len(check_ids) > 3 diff --git a/tests/test_sagemaker_template_permissions.py b/tests/test_sagemaker_template_permissions.py new file mode 100644 index 0000000..381b6cf --- /dev/null +++ b/tests/test_sagemaker_template_permissions.py @@ -0,0 +1,38 @@ +from pathlib import Path + + +REPO_ROOT = Path(__file__).resolve().parent.parent +TEMPLATE_PATHS = [ + REPO_ROOT / "aiml-security-assessment" / "template.yaml", + REPO_ROOT / "aiml-security-assessment" / "template-multi-account.yaml", +] + +# These actions back the SageMaker checks that the assessment Lambda actually runs +# for transform jobs, tuning jobs, compilation jobs, AutoML, and lineage tracking. +REQUIRED_SAGEMAKER_ACTIONS = [ + "sagemaker:ListTransformJobs", + "sagemaker:DescribeTransformJob", + "sagemaker:ListHyperParameterTuningJobs", + "sagemaker:DescribeHyperParameterTuningJob", + "sagemaker:ListCompilationJobs", + "sagemaker:DescribeCompilationJob", + "sagemaker:ListAutoMLJobs", + "sagemaker:DescribeAutoMLJob", + "sagemaker:ListExperiments", + "sagemaker:DescribeExperiment", + "sagemaker:ListTrials", + "sagemaker:DescribeTrial", + "sagemaker:ListAssociations", +] + + +def test_sagemaker_lambda_templates_include_required_actions(): + for template_path in TEMPLATE_PATHS: + template_text = template_path.read_text(encoding="utf-8") + missing_actions = [ + action for action in REQUIRED_SAGEMAKER_ACTIONS if action not in template_text + ] + assert not missing_actions, ( + f"{template_path.name} is missing SageMaker Lambda permissions: " + f"{', '.join(missing_actions)}" + ) diff --git a/tests/test_schema.py b/tests/test_schema.py index 55cb3a0..585d036 100644 --- a/tests/test_schema.py +++ b/tests/test_schema.py @@ -55,6 +55,7 @@ def test_valid_finding_has_all_keys(self): "Reference", "Severity", "Status", + "Region", } assert set(result.keys()) == expected_keys From 9fa7d18df2a082a63a313b163c60c8d23eb4695e Mon Sep 17 00:00:00 2001 From: Agasthi Kothurkar Date: Sun, 14 Jun 2026 11:42:56 -0400 Subject: [PATCH 04/30] Upgrade CodeBuild image to Amazon Linux 2023 standard 6.0 Update .gitignore to exclude .codex/ and .pytest_cache/. --- .gitignore | 4 +++- deployment/2-aiml-security-codebuild.yaml | 2 +- deployment/aiml-security-single-account.yaml | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index 42e7880..d6df932 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,7 @@ .kiro/ .vscode/ .claude/ +.codex/ .mcp.json # AWS SAM build artifacts @@ -12,6 +13,7 @@ __pycache__/ *.py[cod] *$py.class *.pyc +.pytest_cache/ # OS files .DS_Store @@ -40,4 +42,4 @@ samples/ **/test_reports/ .ruff_cache/ -.ash/ \ No newline at end of file +.ash/ diff --git a/deployment/2-aiml-security-codebuild.yaml b/deployment/2-aiml-security-codebuild.yaml index eb05737..862e6fe 100644 --- a/deployment/2-aiml-security-codebuild.yaml +++ b/deployment/2-aiml-security-codebuild.yaml @@ -489,7 +489,7 @@ Resources: !Ref ConcurrentAccountScans, "CodeBuildComputeType", ] - Image: "aws/codebuild/amazonlinux2-x86_64-standard:4.0" + Image: "aws/codebuild/amazonlinux-x86_64-standard:6.0" Type: "LINUX_CONTAINER" EnvironmentVariables: - Name: BUCKET_REPORT diff --git a/deployment/aiml-security-single-account.yaml b/deployment/aiml-security-single-account.yaml index 95280cc..86c0981 100644 --- a/deployment/aiml-security-single-account.yaml +++ b/deployment/aiml-security-single-account.yaml @@ -410,7 +410,7 @@ Resources: Name: AIMLSecurityCodeBuild Environment: ComputeType: BUILD_GENERAL1_SMALL - Image: "aws/codebuild/amazonlinux2-x86_64-standard:4.0" + Image: "aws/codebuild/amazonlinux-x86_64-standard:6.0" Type: "LINUX_CONTAINER" EnvironmentVariables: - Name: BUCKET_REPORT From 8debcf07fb36f5517648c031d9ff382c40cb2be8 Mon Sep 17 00:00:00 2001 From: Agasthi Kothurkar Date: Sun, 14 Jun 2026 12:24:45 -0400 Subject: [PATCH 05/30] Derive Step Function poll timeout from CodeBuild timeout The post-build poll loop used a hardcoded 600s (10 min) wait for the Step Function, which is too short for all-region scans and caused false build failures when the assessment outlived the poll window. Derive the poll timeout from the CodeBuild timeout (passed through as the CODEBUILD_TIMEOUT_MINUTES env var) minus a buffer for build/deploy and report processing, with a floor guard and an SF_POLL_TIMEOUT override. --- buildspec.yml | 21 ++++++++++++++++++-- deployment/2-aiml-security-codebuild.yaml | 3 +++ deployment/aiml-security-single-account.yaml | 3 +++ 3 files changed, 25 insertions(+), 2 deletions(-) diff --git a/buildspec.yml b/buildspec.yml index 9c9bd7a..7b213d2 100644 --- a/buildspec.yml +++ b/buildspec.yml @@ -201,6 +201,23 @@ phases: echo "Assessment completed. Results in S3:$BUCKET_REPORT" + # Derive how long to wait for Step Function completion from the overall + # CodeBuild timeout, reserving a buffer for the build/deploy work that runs + # before this loop and the report processing that runs after it. An explicit + # SF_POLL_TIMEOUT override always wins; a floor guards against tiny/negative + # values when CODEBUILD_TIMEOUT_MINUTES is small. + SF_POLL_BUFFER_SECONDS=${SF_POLL_BUFFER_SECONDS:-600} + SF_POLL_FLOOR_SECONDS=${SF_POLL_FLOOR_SECONDS:-300} + if [[ -n "$SF_POLL_TIMEOUT" ]]; then + sf_poll_timeout=$SF_POLL_TIMEOUT + else + sf_poll_timeout=$(( ${CODEBUILD_TIMEOUT_MINUTES:-60} * 60 - SF_POLL_BUFFER_SECONDS )) + if [[ $sf_poll_timeout -lt $SF_POLL_FLOOR_SECONDS ]]; then + sf_poll_timeout=$SF_POLL_FLOOR_SECONDS + fi + fi + echo "Step Function poll timeout set to ${sf_poll_timeout}s (CodeBuild timeout: ${CODEBUILD_TIMEOUT_MINUTES:-60}m, buffer: ${SF_POLL_BUFFER_SECONDS}s, floor: ${SF_POLL_FLOOR_SECONDS}s)" + if [[ $MULTI_ACCOUNT_SCAN = 'true' ]]; then echo "Getting list of accounts for post-build processing" if [[ $MULTI_ACCOUNT_LIST_OVERRIDE != '' ]]; then @@ -258,7 +275,7 @@ phases: EXECUTION_ARN=$(cat /tmp/execution_arns/$accountId.txt) echo "Waiting for Step Function execution in account $accountId: $EXECUTION_ARN" if [[ $EXECUTION_ARN != "" && $EXECUTION_ARN != "None" ]]; then - timeout=${SF_POLL_TIMEOUT:-600} + timeout=$sf_poll_timeout elapsed=0 while [[ $elapsed -lt $timeout ]]; do STATUS=$(aws stepfunctions describe-execution --execution-arn $EXECUTION_ARN --query 'status' --output text 2>/dev/null) @@ -375,7 +392,7 @@ phases: EXECUTION_ARN=$(cat /tmp/single_account_execution_arn.txt) echo "Waiting for Step Function execution: $EXECUTION_ARN" if [[ $EXECUTION_ARN != "" && $EXECUTION_ARN != "None" ]]; then - timeout=${SF_POLL_TIMEOUT:-600} + timeout=$sf_poll_timeout elapsed=0 while [[ $elapsed -lt $timeout ]]; do STATUS=$(aws stepfunctions describe-execution --execution-arn $EXECUTION_ARN --query 'status' --output text 2>/dev/null) diff --git a/deployment/2-aiml-security-codebuild.yaml b/deployment/2-aiml-security-codebuild.yaml index 862e6fe..1cc1fc1 100644 --- a/deployment/2-aiml-security-codebuild.yaml +++ b/deployment/2-aiml-security-codebuild.yaml @@ -527,6 +527,9 @@ Resources: - Name: TARGET_REGIONS Value: !Ref TargetRegions Type: PLAINTEXT + - Name: CODEBUILD_TIMEOUT_MINUTES + Value: !Ref CodeBuildTimeout + Type: PLAINTEXT Description: Run AIML Security multi-account assessment ServiceRole: !GetAtt MultiAccountCodeBuildRole.Arn TimeoutInMinutes: !Ref CodeBuildTimeout diff --git a/deployment/aiml-security-single-account.yaml b/deployment/aiml-security-single-account.yaml index 86c0981..64e5c03 100644 --- a/deployment/aiml-security-single-account.yaml +++ b/deployment/aiml-security-single-account.yaml @@ -437,6 +437,9 @@ Resources: - Name: TARGET_REGIONS Value: !Ref TargetRegions Type: PLAINTEXT + - Name: CODEBUILD_TIMEOUT_MINUTES + Value: !Ref CodeBuildTimeout + Type: PLAINTEXT Description: Run AIML Security single-account assessment ServiceRole: !GetAtt CodeBuildRole.Arn TimeoutInMinutes: !Ref CodeBuildTimeout From ce24543deff5540c82f46aba7234eec52aa768c8 Mon Sep 17 00:00:00 2001 From: Agasthi Kothurkar Date: Sun, 14 Jun 2026 12:24:51 -0400 Subject: [PATCH 06/30] Remove unused imports and fix f-string flagged by ruff Clear all 25 ruff findings (F401 unused imports, F811 duplicate import, F541 extraneous f-string prefix) across the Python sources. --- .../security/generate_consolidated_report/schema.py | 4 +--- .../test_generate_report.py | 1 - .../functions/security/iam_permission_caching/app.py | 10 +--------- .../security/iam_permission_caching/schema.py | 4 +--- sample-reports/scripts/capture_screenshots.py | 3 +-- 5 files changed, 4 insertions(+), 18 deletions(-) diff --git a/aiml-security-assessment/functions/security/generate_consolidated_report/schema.py b/aiml-security-assessment/functions/security/generate_consolidated_report/schema.py index 19aec8f..6234877 100644 --- a/aiml-security-assessment/functions/security/generate_consolidated_report/schema.py +++ b/aiml-security-assessment/functions/security/generate_consolidated_report/schema.py @@ -1,7 +1,5 @@ from enum import Enum -from typing import Dict, List, Any -from pydantic import BaseModel, Field, HttpUrl, validator -from datetime import datetime +from pydantic import BaseModel, Field, validator class SeverityEnum(str, Enum): HIGH = "High" diff --git a/aiml-security-assessment/functions/security/generate_consolidated_report/test_generate_report.py b/aiml-security-assessment/functions/security/generate_consolidated_report/test_generate_report.py index a155817..879b807 100644 --- a/aiml-security-assessment/functions/security/generate_consolidated_report/test_generate_report.py +++ b/aiml-security-assessment/functions/security/generate_consolidated_report/test_generate_report.py @@ -1,7 +1,6 @@ # test_generate_report.py import unittest import os -import webbrowser from app import generate_html_report from report_template import generate_html_report as generate_report_direct diff --git a/aiml-security-assessment/functions/security/iam_permission_caching/app.py b/aiml-security-assessment/functions/security/iam_permission_caching/app.py index 742da9f..8c1cde5 100644 --- a/aiml-security-assessment/functions/security/iam_permission_caching/app.py +++ b/aiml-security-assessment/functions/security/iam_permission_caching/app.py @@ -1,18 +1,10 @@ import boto3 -import csv import os import logging -from datetime import datetime, timedelta, timezone -import time -from typing import Dict, List, Any, Optional -from io import StringIO -import asyncio +from datetime import datetime, timezone import json from botocore.config import Config -from botocore.exceptions import ClientError -import random -from datetime import datetime def get_current_utc_date(): return datetime.now(timezone.utc).strftime("%Y/%m/%d") diff --git a/aiml-security-assessment/functions/security/iam_permission_caching/schema.py b/aiml-security-assessment/functions/security/iam_permission_caching/schema.py index d745e06..6515a38 100644 --- a/aiml-security-assessment/functions/security/iam_permission_caching/schema.py +++ b/aiml-security-assessment/functions/security/iam_permission_caching/schema.py @@ -1,7 +1,5 @@ from enum import Enum -from typing import Dict, List, Any -from pydantic import BaseModel, Field, HttpUrl, validator -from datetime import datetime +from pydantic import BaseModel, Field, validator class SeverityEnum(str, Enum): HIGH = "High" diff --git a/sample-reports/scripts/capture_screenshots.py b/sample-reports/scripts/capture_screenshots.py index 1f2555b..7e5b080 100755 --- a/sample-reports/scripts/capture_screenshots.py +++ b/sample-reports/scripts/capture_screenshots.py @@ -18,7 +18,6 @@ python sample-reports/scripts/capture_screenshots.py """ -import os import sys from pathlib import Path from playwright.sync_api import sync_playwright @@ -225,7 +224,7 @@ def main(): print(" 3. Commit the screenshots to the repository") except ImportError as e: - print(f"\nERROR: Required library not installed") + print("\nERROR: Required library not installed") print(f" {e}") print("\nPlease install required dependencies:") print(" source .venv/bin/activate") From eceaa2f9ed993a2836431e5b73c87c50708ec899 Mon Sep 17 00:00:00 2001 From: Agasthi Kothurkar Date: Sun, 14 Jun 2026 13:19:01 -0400 Subject: [PATCH 07/30] Add Global region option to HTML report filters Global/IAM findings (Region == "Global") appear in the report tables but were absent from the Region filter dropdowns because "Global" is intentionally excluded from the region count/tiles. Surface a "Global" filter option when such findings exist, and show the dropdown whenever there is more than one distinct filterable value (regions + Global), without inflating the region count. --- .../report_template.py | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/aiml-security-assessment/functions/security/generate_consolidated_report/report_template.py b/aiml-security-assessment/functions/security/generate_consolidated_report/report_template.py index 8e79d07..8054183 100644 --- a/aiml-security-assessment/functions/security/generate_consolidated_report/report_template.py +++ b/aiml-security-assessment/functions/security/generate_consolidated_report/report_template.py @@ -741,14 +741,25 @@ def generate_html_report( service_findings.get("agentcore", []), include_data_attrs=True ) - # Build region filter HTML (shared across modes, only shown when multiple regions) - if regions and len(regions) > 1: + # Build region filter HTML (shared across modes, only shown when multiple regions). + # "Global" tags IAM-only findings and is intentionally excluded from `regions` + # (it must not inflate the region count / tiles), but those findings still appear + # in the tables, so surface a "Global" filter option when any are present. + has_global = any( + (f.get("region") or f.get("Region")) == "Global" for f in all_findings + ) + # Show the filter when there is more than one distinct value to choose from: + # multiple scanned regions, or a single scanned region alongside Global findings. + num_real_regions = len(regions) if regions else 0 + if num_real_regions + (1 if has_global else 0) > 1: region_options = "".join( [ f'' - for r in sorted(regions) + for r in sorted(regions or []) ] ) + if has_global: + region_options += '' region_filter = f'
' bedrock_region_filter = f'
' sagemaker_region_filter = f'
' From 84c543b89de9cbb9b2ac0c8ec49c2af50c60b8be Mon Sep 17 00:00:00 2001 From: Agasthi Kothurkar Date: Sun, 14 Jun 2026 15:16:25 -0400 Subject: [PATCH 08/30] Gate regional Bedrock/AgentCore findings on resource footprint Probe regional Bedrock list APIs (guardrails, prompts, agents, knowledge bases, flows, custom models) and AgentCore runtimes before emitting findings. When no resources exist in a region, report BR-02, BR-05, and AC-08 as N/A/Informational instead of WARN/Failed, avoiding false positives in regions with no AI/ML footprint. --- .../security/agentcore_assessments/app.py | 31 +++ .../security/bedrock_assessments/app.py | 181 ++++++++++++++++-- tests/test_agentcore_checks.py | 2 + tests/test_bedrock_checks.py | 56 +++++- 4 files changed, 251 insertions(+), 19 deletions(-) diff --git a/aiml-security-assessment/functions/security/agentcore_assessments/app.py b/aiml-security-assessment/functions/security/agentcore_assessments/app.py index 14ed84c..1793a40 100644 --- a/aiml-security-assessment/functions/security/agentcore_assessments/app.py +++ b/aiml-security-assessment/functions/security/agentcore_assessments/app.py @@ -1458,9 +1458,40 @@ def check_agentcore_vpc_endpoints() -> List[Dict[str, Any]]: """ findings = [] + if agentcore_client is None: + findings.append( + create_finding( + check_id="AC-08", + finding_name="AgentCore VPC Endpoints Check", + finding_details="AgentCore client not available in this region", + resolution="Deploy in a region where Amazon Bedrock AgentCore is available", + reference="https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/vpc.html", + severity=SeverityEnum.INFORMATIONAL, + status=StatusEnum.NA, + ) + ) + return findings + try: logger.info("Checking for AgentCore VPC endpoints") + runtimes_response = agentcore_client.list_agent_runtimes() + runtimes = runtimes_response.get("agentRuntimes", []) + + if not runtimes: + findings.append( + create_finding( + check_id="AC-08", + finding_name="AgentCore VPC Endpoints Check", + finding_details="No AgentCore resources found", + resolution="No action required", + reference="https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/vpc.html", + severity=SeverityEnum.INFORMATIONAL, + status=StatusEnum.NA, + ) + ) + return findings + # Get all VPCs vpcs_response = ec2_client.describe_vpcs() vpcs = vpcs_response.get("Vpcs", []) diff --git a/aiml-security-assessment/functions/security/bedrock_assessments/app.py b/aiml-security-assessment/functions/security/bedrock_assessments/app.py index 98db843..c47a746 100644 --- a/aiml-security-assessment/functions/security/bedrock_assessments/app.py +++ b/aiml-security-assessment/functions/security/bedrock_assessments/app.py @@ -41,6 +41,12 @@ "OptInRequired", } +ACCESS_DENIED_ERROR_CODES = { + "AccessDenied", + "AccessDeniedException", + "UnauthorizedOperation", +} + def describe_api_error(error: Exception, api_label: str, region: str = "") -> str: """ @@ -61,6 +67,120 @@ def describe_api_error(error: Exception, api_label: str, region: str = "") -> st return f"Unable to check {api_label}: {error_text}" +def _probe_bedrock_resource_list(probe_label: str, probe_func) -> Optional[bool]: + """ + Probe a Bedrock list API and report whether it found any regional resources. + + Returns: + True if the API found at least one resource + False if the API was successfully queried and returned no resources + None if the result is inconclusive (for example, AccessDenied) + """ + try: + return bool(probe_func()) + except EndpointConnectionError: + raise + except ClientError as e: + code = e.response.get("Error", {}).get("Code", "") + error_text = str(e) + + if code in REGION_UNAVAILABLE_ERROR_CODES: + raise + + if code in ACCESS_DENIED_ERROR_CODES: + logger.warning( + f"Unable to determine regional Bedrock footprint from {probe_label}: {code}" + ) + return None + + if code == "ValidationException" or "UnknownOperation" in error_text or "Unknown operation" in error_text: + logger.info(f"{probe_label} API not available in this region") + return False + + logger.warning( + f"Unexpected error probing {probe_label} for regional Bedrock footprint: {error_text}" + ) + return None + except Exception as e: + error_text = str(e) + if "UnknownOperation" in error_text or "Unknown operation" in error_text: + logger.info(f"{probe_label} API not available in this region") + return False + + logger.warning( + f"Unexpected error probing {probe_label} for regional Bedrock footprint: {error_text}" + ) + return None + + +def _first_page_items(paginator, result_key: str) -> List[Dict[str, Any]]: + """Return at most the first page of items from a paginator-based Bedrock list API.""" + for page in paginator.paginate(PaginationConfig={"MaxItems": 1, "PageSize": 1}): + return page.get(result_key, []) + return [] + + +def detect_bedrock_regional_footprint(region: str = "") -> Optional[bool]: + """ + Detect whether a region has Bedrock-managed resources that justify regional findings. + + Returns: + True if Bedrock-managed resources exist in the region + False if supported APIs were probed and no resources were found + None if the footprint could not be determined confidently + """ + bedrock_client = boto3.client("bedrock", config=boto3_config, region_name=region) + bedrock_agent_client = boto3.client( + "bedrock-agent", config=boto3_config, region_name=region + ) + + probes = [ + ( + "Bedrock Guardrails", + lambda: bedrock_client.list_guardrails().get("guardrails", []), + ), + ( + "Bedrock Prompts", + lambda: bedrock_agent_client.list_prompts().get("promptSummaries", []), + ), + ( + "Bedrock Agents", + lambda: bedrock_agent_client.list_agents().get("agents", []), + ), + ( + "Bedrock Knowledge Bases", + lambda: _first_page_items( + bedrock_agent_client.get_paginator("list_knowledge_bases"), + "knowledgeBaseSummaries", + ), + ), + ( + "Bedrock Flows", + lambda: _first_page_items( + bedrock_agent_client.get_paginator("list_flows"), + "flowSummaries", + ), + ), + ( + "Bedrock Custom Models", + lambda: _first_page_items( + bedrock_client.get_paginator("list_custom_models"), + "modelSummaries", + ), + ), + ] + + indeterminate = False + for probe_label, probe_func in probes: + probe_result = _probe_bedrock_resource_list(probe_label, probe_func) + if probe_result is True: + return True + if probe_result is None: + indeterminate = True + + return None if indeterminate else False + + def get_permissions_cache(execution_id: str) -> Optional[Dict[str, Any]]: """ Retrieve and parse the permissions cache JSON file from S3 @@ -736,6 +856,24 @@ def check_bedrock_access_and_vpc_endpoints(permission_cache, region: str = "") - break if bedrock_access_found: + bedrock_footprint_found = detect_bedrock_regional_footprint(region=region) + + if bedrock_footprint_found is False: + findings["details"] = "No regional Bedrock resources found" + findings["csv_data"].append( + create_finding( + check_id="BR-02", + finding_name="Amazon Bedrock private connectivity check", + finding_details="No regional Bedrock resources found to assess private connectivity", + resolution="No action required", + reference="https://docs.aws.amazon.com/bedrock/latest/userguide/vpc-interface-endpoints.html", + severity="Informational", + status="N/A", + region=region, + ) + ) + return findings + vpc_endpoint_check = check_bedrock_vpc_endpoints(region=region) if not vpc_endpoint_check["has_endpoints"]: @@ -849,20 +987,37 @@ def check_bedrock_guardrails(region: str = "") -> Dict[str, Any]: ) ) else: - findings["status"] = "WARN" - findings["details"] = "No Bedrock guardrails configured" - findings["csv_data"].append( - create_finding( - check_id="BR-05", - finding_name="Bedrock Guardrails Check", - finding_details="No Amazon Bedrock Guardrails are configured. This may expose your application to potential risks such as harmful content, sensitive information disclosure, or hallucinations.", - resolution="Configure Bedrock Guardrails to implement safeguards such as:\n- Content filters to block harmful content\n- Denied topics to prevent undesirable discussions\n- Sensitive information filters to protect PII\n- Contextual grounding checks to prevent hallucinations", - reference="https://docs.aws.amazon.com/bedrock/latest/userguide/guardrails.html", - severity="Medium", - status="Failed", - region=region, + bedrock_footprint_found = detect_bedrock_regional_footprint(region=region) + + if bedrock_footprint_found is False: + findings["details"] = "No regional Bedrock resources found" + findings["csv_data"].append( + create_finding( + check_id="BR-05", + finding_name="Bedrock Guardrails Check", + finding_details="No regional Bedrock resources found to protect with guardrails", + resolution="No action required", + reference="https://docs.aws.amazon.com/bedrock/latest/userguide/guardrails.html", + severity="Informational", + status="N/A", + region=region, + ) + ) + else: + findings["status"] = "WARN" + findings["details"] = "No Bedrock guardrails configured" + findings["csv_data"].append( + create_finding( + check_id="BR-05", + finding_name="Bedrock Guardrails Check", + finding_details="No Amazon Bedrock Guardrails are configured. This may expose your application to potential risks such as harmful content, sensitive information disclosure, or hallucinations.", + resolution="Configure Bedrock Guardrails to implement safeguards such as:\n- Content filters to block harmful content\n- Denied topics to prevent undesirable discussions\n- Sensitive information filters to protect PII\n- Contextual grounding checks to prevent hallucinations", + reference="https://docs.aws.amazon.com/bedrock/latest/userguide/guardrails.html", + severity="Medium", + status="Failed", + region=region, + ) ) - ) except bedrock_client.exceptions.ValidationException as e: findings["status"] = "ERROR" diff --git a/tests/test_agentcore_checks.py b/tests/test_agentcore_checks.py index 0cce9b8..24d8f2c 100644 --- a/tests/test_agentcore_checks.py +++ b/tests/test_agentcore_checks.py @@ -372,6 +372,8 @@ def test_ac08_no_runtimes_returns_na(self, mock_ac, mock_ec2): result = agentcore_app.check_agentcore_vpc_endpoints() findings = extract_csv_data(result) assert len(findings) >= 1 + assert findings[0]["Status"] == "N/A" + assert findings[0]["Finding_Details"] == "No AgentCore resources found" @patch("agentcore_app.agentcore_client") def test_ac08_exception_returns_error_finding(self, mock_ac): diff --git a/tests/test_bedrock_checks.py b/tests/test_bedrock_checks.py index 0c5a592..947586e 100644 --- a/tests/test_bedrock_checks.py +++ b/tests/test_bedrock_checks.py @@ -92,8 +92,9 @@ def test_br02_no_bedrock_access_returns_no_findings(self, empty_permission_cache assert "csv_data" in result @patch("bedrock_app.check_bedrock_vpc_endpoints") + @patch("bedrock_app.detect_bedrock_regional_footprint", return_value=True) def test_br02_bedrock_access_with_endpoints_returns_passed( - self, mock_vpc, permission_cache_compliant + self, mock_footprint, mock_vpc, permission_cache_compliant ): check = bedrock_app.check_bedrock_access_and_vpc_endpoints mock_vpc.return_value = { @@ -113,8 +114,9 @@ def test_br02_bedrock_access_with_endpoints_returns_passed( assert findings[0]["Check_ID"] == "BR-02" @patch("bedrock_app.check_bedrock_vpc_endpoints") + @patch("bedrock_app.detect_bedrock_regional_footprint", return_value=True) def test_br02_bedrock_access_no_endpoints_returns_failed( - self, mock_vpc, permission_cache_compliant + self, mock_footprint, mock_vpc, permission_cache_compliant ): check = bedrock_app.check_bedrock_access_and_vpc_endpoints mock_vpc.return_value = { @@ -129,8 +131,24 @@ def test_br02_bedrock_access_no_endpoints_returns_failed( assert findings[0]["Severity"] == "Medium" @patch("bedrock_app.check_bedrock_vpc_endpoints") + @patch("bedrock_app.detect_bedrock_regional_footprint", return_value=False) + def test_br02_no_regional_footprint_returns_na( + self, mock_footprint, mock_vpc, permission_cache_compliant + ): + check = bedrock_app.check_bedrock_access_and_vpc_endpoints + result = check(permission_cache_compliant, region="eu-west-3") + findings = extract_csv_data(result) + assert len(findings) >= 1 + assert findings[0]["Status"] == "N/A" + assert findings[0]["Finding_Details"] == ( + "No regional Bedrock resources found to assess private connectivity" + ) + mock_vpc.assert_not_called() + + @patch("bedrock_app.check_bedrock_vpc_endpoints") + @patch("bedrock_app.detect_bedrock_regional_footprint", return_value=True) def test_br02_exception_returns_error_finding( - self, mock_vpc, permission_cache_compliant + self, mock_footprint, mock_vpc, permission_cache_compliant ): check = bedrock_app.check_bedrock_access_and_vpc_endpoints mock_vpc.side_effect = Exception("VPC check failed") @@ -141,7 +159,10 @@ def test_br02_exception_returns_error_finding( assert "Error" in findings[0]["Finding_Details"] @patch("bedrock_app.check_bedrock_vpc_endpoints") - def test_br02_schema_valid(self, mock_vpc, permission_cache_compliant): + @patch("bedrock_app.detect_bedrock_regional_footprint", return_value=True) + def test_br02_schema_valid( + self, mock_footprint, mock_vpc, permission_cache_compliant + ): check = bedrock_app.check_bedrock_access_and_vpc_endpoints mock_vpc.return_value = { "has_endpoints": True, @@ -279,12 +300,32 @@ def test_br05_no_guardrails_returns_failed(self, mock_client): mock_bedrock = MagicMock() mock_client.return_value = mock_bedrock mock_bedrock.list_guardrails.return_value = {"guardrails": []} - result = check() + with patch( + "bedrock_app.detect_bedrock_regional_footprint", return_value=True + ): + result = check() findings = extract_csv_data(result) assert len(findings) >= 1 assert findings[0]["Status"] == "Failed" assert findings[0]["Severity"] == "Medium" + @patch("boto3.client") + def test_br05_no_guardrails_and_no_regional_footprint_returns_na(self, mock_client): + check = bedrock_app.check_bedrock_guardrails + mock_bedrock = MagicMock() + mock_client.return_value = mock_bedrock + mock_bedrock.list_guardrails.return_value = {"guardrails": []} + with patch( + "bedrock_app.detect_bedrock_regional_footprint", return_value=False + ): + result = check(region="eu-west-3") + findings = extract_csv_data(result) + assert len(findings) >= 1 + assert findings[0]["Status"] == "N/A" + assert findings[0]["Finding_Details"] == ( + "No regional Bedrock resources found to protect with guardrails" + ) + @patch("boto3.client") def test_br05_exception_returns_error_finding(self, mock_client): check = bedrock_app.check_bedrock_guardrails @@ -300,7 +341,10 @@ def test_br05_schema_valid(self, mock_client): mock_bedrock = MagicMock() mock_client.return_value = mock_bedrock mock_bedrock.list_guardrails.return_value = {"guardrails": []} - result = check() + with patch( + "bedrock_app.detect_bedrock_regional_footprint", return_value=True + ): + result = check() for f in extract_csv_data(result): assert_finding_schema(f) From 23804041db73c1f0bbfa726775d9848b81550331 Mon Sep 17 00:00:00 2001 From: Agasthi Kothurkar Date: Sun, 14 Jun 2026 15:45:53 -0400 Subject: [PATCH 09/30] Prevent TargetRegions whitespace from truncating multi-region scans MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The CodeBuild env var TARGET_REGIONS allowed spaces after commas (e.g. "us-east-1, us-east-2"). 'sam deploy --parameter-overrides' re-splits its argument on spaces, so only the first chunk ("us-east-1,") reached the stack and every other region was silently dropped — the cause of build #14 scanning a single region. - buildspec.yml: strip all whitespace from TARGET_REGIONS before deploy and validate the result, failing fast on invalid characters. - Add AllowedPattern/ConstraintDescription to the TargetRegions parameter in all four templates as defense-in-depth at the CloudFormation layer. --- .../template-multi-account.yaml | 4 ++++ aiml-security-assessment/template.yaml | 4 ++++ buildspec.yml | 14 ++++++++++++++ deployment/2-aiml-security-codebuild.yaml | 4 ++++ deployment/aiml-security-single-account.yaml | 4 ++++ 5 files changed, 30 insertions(+) diff --git a/aiml-security-assessment/template-multi-account.yaml b/aiml-security-assessment/template-multi-account.yaml index bd82963..450eda6 100644 --- a/aiml-security-assessment/template-multi-account.yaml +++ b/aiml-security-assessment/template-multi-account.yaml @@ -9,6 +9,10 @@ Parameters: TargetRegions: Type: String Default: "" + AllowedPattern: "^[a-zA-Z0-9,-]*$" + ConstraintDescription: > + TargetRegions must be a comma-separated list of region names with no spaces + (e.g., us-east-1,us-west-2,eu-west-1), 'all', or empty. Description: > Comma-separated list of AWS regions to scan (e.g., us-east-1,us-west-2,eu-west-1). Use 'all' to scan all regions where the service is available. diff --git a/aiml-security-assessment/template.yaml b/aiml-security-assessment/template.yaml index a055de6..6372750 100644 --- a/aiml-security-assessment/template.yaml +++ b/aiml-security-assessment/template.yaml @@ -9,6 +9,10 @@ Parameters: TargetRegions: Type: String Default: "" + AllowedPattern: "^[a-zA-Z0-9,-]*$" + ConstraintDescription: > + TargetRegions must be a comma-separated list of region names with no spaces + (e.g., us-east-1,us-west-2,eu-west-1), 'all', or empty. Description: > Comma-separated list of AWS regions to scan (e.g., us-east-1,us-west-2,eu-west-1). Use 'all' to scan all regions where the service is available. diff --git a/buildspec.yml b/buildspec.yml index 7b213d2..8b3cf39 100644 --- a/buildspec.yml +++ b/buildspec.yml @@ -40,6 +40,20 @@ phases: echo "ERROR: Missing required environment variables: ${missing_vars[*]}" exit 1 fi + - | + # Normalize TARGET_REGIONS: strip all whitespace so the comma-separated + # value survives 'sam deploy --parameter-overrides', which re-splits its + # argument on spaces and would otherwise drop every region after the first. + TARGET_REGIONS=$(echo "${TARGET_REGIONS:-}" | tr -d '[:space:]') + export TARGET_REGIONS + + # Validate the normalized value matches the CloudFormation AllowedPattern. + if [[ -n "$TARGET_REGIONS" && ! "$TARGET_REGIONS" =~ ^[a-zA-Z0-9,-]+$ ]]; then + echo "ERROR: TARGET_REGIONS contains invalid characters: '$TARGET_REGIONS'" + echo "Expected a comma-separated list of region names (e.g., us-east-1,us-west-2), 'all', or empty." + exit 1 + fi + echo "Using TARGET_REGIONS: '${TARGET_REGIONS:-}'" - echo "Multi-account scan is $MULTI_ACCOUNT_SCAN" - cd aiml-security-assessment - ls -la diff --git a/deployment/2-aiml-security-codebuild.yaml b/deployment/2-aiml-security-codebuild.yaml index 1cc1fc1..3ac5c69 100644 --- a/deployment/2-aiml-security-codebuild.yaml +++ b/deployment/2-aiml-security-codebuild.yaml @@ -73,6 +73,10 @@ Parameters: TargetRegions: Type: String + AllowedPattern: "^[a-zA-Z0-9,-]*$" + ConstraintDescription: > + TargetRegions must be a comma-separated list of region names with no spaces + (e.g., us-east-1,us-west-2,eu-west-1), 'all', or empty. Description: > Comma-separated list of AWS regions to scan (e.g., us-east-1,us-west-2,eu-west-1). Use 'all' to scan all regions where the service is available. diff --git a/deployment/aiml-security-single-account.yaml b/deployment/aiml-security-single-account.yaml index 64e5c03..4d5e732 100644 --- a/deployment/aiml-security-single-account.yaml +++ b/deployment/aiml-security-single-account.yaml @@ -38,6 +38,10 @@ Parameters: TargetRegions: Type: String + AllowedPattern: "^[a-zA-Z0-9,-]*$" + ConstraintDescription: > + TargetRegions must be a comma-separated list of region names with no spaces + (e.g., us-east-1,us-west-2,eu-west-1), 'all', or empty. Description: > Comma-separated list of AWS regions to scan (e.g., us-east-1,us-west-2,eu-west-1). Use 'all' to scan all regions where the service is available. From 6eb00aa1b0c7b8da3b19117dbe513a019f28c4a9 Mon Sep 17 00:00:00 2001 From: Agasthi Kothurkar Date: Sun, 14 Jun 2026 16:18:26 -0400 Subject: [PATCH 10/30] Extend regional footprint gating to BR-04, BR-06, and BR-09 Gate model invocation logging (BR-04) and Bedrock CloudTrail coverage (BR-06) on the regional Bedrock footprint, reporting N/A when no resources exist instead of Failed. Return False from detect_bedrock_regional_footprint when any probe succeeds with no resources, so a single accessible empty API is enough to conclude the region is empty. For BR-09, treat Knowledge Base API errors (including AccessDenied) as N/A rather than a High/Failed finding. --- .../security/bedrock_assessments/app.py | 58 +++++++++++-- tests/test_bedrock_checks.py | 81 +++++++++++++++++-- 2 files changed, 124 insertions(+), 15 deletions(-) diff --git a/aiml-security-assessment/functions/security/bedrock_assessments/app.py b/aiml-security-assessment/functions/security/bedrock_assessments/app.py index c47a746..367f8fa 100644 --- a/aiml-security-assessment/functions/security/bedrock_assessments/app.py +++ b/aiml-security-assessment/functions/security/bedrock_assessments/app.py @@ -171,13 +171,19 @@ def detect_bedrock_regional_footprint(region: str = "") -> Optional[bool]: ] indeterminate = False + successful_empty_probe = False for probe_label, probe_func in probes: probe_result = _probe_bedrock_resource_list(probe_label, probe_func) if probe_result is True: return True + if probe_result is False: + successful_empty_probe = True if probe_result is None: indeterminate = True + if successful_empty_probe: + return False + return None if indeterminate else False @@ -1071,6 +1077,23 @@ def check_bedrock_logging_configuration(region: str = "") -> Dict[str, Any]: "csv_data": [], } + bedrock_footprint_found = detect_bedrock_regional_footprint(region=region) + if bedrock_footprint_found is False: + findings["details"] = "No regional Bedrock resources found" + findings["csv_data"].append( + create_finding( + check_id="BR-04", + finding_name="Bedrock Model Invocation Logging Check", + finding_details="No regional Bedrock resources found to monitor with invocation logging", + resolution="No action required", + reference="https://docs.aws.amazon.com/bedrock/latest/userguide/model-invocation-logging.html", + severity="Informational", + status="N/A", + region=region, + ) + ) + return findings + bedrock_client = boto3.client("bedrock", config=boto3_config, region_name=region) try: @@ -1180,6 +1203,23 @@ def check_bedrock_cloudtrail_logging(region: str = "") -> Dict[str, Any]: "csv_data": [], } + bedrock_footprint_found = detect_bedrock_regional_footprint(region=region) + if bedrock_footprint_found is False: + findings["details"] = "No regional Bedrock resources found" + findings["csv_data"].append( + create_finding( + check_id="BR-06", + finding_name="Bedrock CloudTrail Logging Check", + finding_details="No regional Bedrock resources found to audit with Bedrock-specific CloudTrail coverage", + resolution="No action required", + reference="https://docs.aws.amazon.com/bedrock/latest/userguide/logging-using-cloudtrail.html", + severity="Informational", + status="N/A", + region=region, + ) + ) + return findings + cloudtrail_client = boto3.client("cloudtrail", config=boto3_config, region_name=region) try: @@ -1546,20 +1586,22 @@ def check_bedrock_knowledge_base_encryption(region: str = "") -> Dict[str, Any]: ) ) - except bedrock_agent_client.exceptions.ValidationException as e: - findings["status"] = "ERROR" - findings["details"] = ( - f"Error validating Knowledge Base configuration: {str(e)}" + except Exception as e: + findings["status"] = "WARN" + findings["details"] = describe_api_error( + e, "Bedrock Knowledge Base API", region ) findings["csv_data"].append( create_finding( check_id="BR-09", finding_name="Bedrock Knowledge Base Encryption Check", - finding_details=f"Error checking Knowledge Base encryption: {str(e)}", - resolution="Verify your AWS credentials and permissions to access Bedrock Knowledge Bases", + finding_details=describe_api_error( + e, "Bedrock Knowledge Base API", region + ), + resolution="Verify your AWS credentials and permissions to access Bedrock Knowledge Bases, then retry the assessment.", reference="https://docs.aws.amazon.com/bedrock/latest/userguide/encryption-kb.html", - severity="High", - status="Failed", + severity="Informational", + status="N/A", region=region, ) ) diff --git a/tests/test_bedrock_checks.py b/tests/test_bedrock_checks.py index 947586e..0a4df33 100644 --- a/tests/test_bedrock_checks.py +++ b/tests/test_bedrock_checks.py @@ -219,7 +219,10 @@ class TestBR04LoggingConfiguration: """BR-04: Check model invocation logging.""" @patch("boto3.client") - def test_br04_logging_enabled_s3_returns_passed(self, mock_client): + @patch("bedrock_app.detect_bedrock_regional_footprint", return_value=True) + def test_br04_logging_enabled_s3_returns_passed( + self, mock_footprint, mock_client + ): check = bedrock_app.check_bedrock_logging_configuration mock_bedrock = MagicMock() mock_client.return_value = mock_bedrock @@ -236,7 +239,10 @@ def test_br04_logging_enabled_s3_returns_passed(self, mock_client): assert findings[0]["Check_ID"] == "BR-04" @patch("boto3.client") - def test_br04_logging_disabled_returns_failed(self, mock_client): + @patch("bedrock_app.detect_bedrock_regional_footprint", return_value=True) + def test_br04_logging_disabled_returns_failed( + self, mock_footprint, mock_client + ): check = bedrock_app.check_bedrock_logging_configuration mock_bedrock = MagicMock() mock_client.return_value = mock_bedrock @@ -249,6 +255,21 @@ def test_br04_logging_disabled_returns_failed(self, mock_client): assert findings[0]["Status"] == "Failed" assert findings[0]["Severity"] == "Medium" + @patch("boto3.client") + @patch("bedrock_app.detect_bedrock_regional_footprint", return_value=False) + def test_br04_no_regional_footprint_returns_na( + self, mock_footprint, mock_client + ): + check = bedrock_app.check_bedrock_logging_configuration + result = check(region="eu-west-1") + findings = extract_csv_data(result) + assert len(findings) >= 1 + assert findings[0]["Status"] == "N/A" + assert findings[0]["Finding_Details"] == ( + "No regional Bedrock resources found to monitor with invocation logging" + ) + mock_client.assert_not_called() + @patch("boto3.client") def test_br04_exception_returns_error_finding(self, mock_client): check = bedrock_app.check_bedrock_logging_configuration @@ -259,7 +280,8 @@ def test_br04_exception_returns_error_finding(self, mock_client): assert findings[0]["Status"] == "Failed" @patch("boto3.client") - def test_br04_schema_valid(self, mock_client): + @patch("bedrock_app.detect_bedrock_regional_footprint", return_value=True) + def test_br04_schema_valid(self, mock_footprint, mock_client): check = bedrock_app.check_bedrock_logging_configuration mock_bedrock = MagicMock() mock_client.return_value = mock_bedrock @@ -356,7 +378,10 @@ class TestBR06CloudTrailLogging: """BR-06: Check CloudTrail logging for Bedrock.""" @patch("boto3.client") - def test_br06_trail_is_logging_returns_passed(self, mock_client): + @patch("bedrock_app.detect_bedrock_regional_footprint", return_value=True) + def test_br06_trail_is_logging_returns_passed( + self, mock_footprint, mock_client + ): check = bedrock_app.check_bedrock_cloudtrail_logging mock_ct = MagicMock() mock_client.return_value = mock_ct @@ -383,7 +408,8 @@ def test_br06_trail_is_logging_returns_passed(self, mock_client): assert findings[0]["Check_ID"] == "BR-06" @patch("boto3.client") - def test_br06_no_trails_returns_failed(self, mock_client): + @patch("bedrock_app.detect_bedrock_regional_footprint", return_value=True) + def test_br06_no_trails_returns_failed(self, mock_footprint, mock_client): check = bedrock_app.check_bedrock_cloudtrail_logging mock_ct = MagicMock() mock_client.return_value = mock_ct @@ -395,7 +421,10 @@ def test_br06_no_trails_returns_failed(self, mock_client): assert findings[0]["Severity"] == "High" @patch("boto3.client") - def test_br06_trail_not_logging_returns_failed(self, mock_client): + @patch("bedrock_app.detect_bedrock_regional_footprint", return_value=True) + def test_br06_trail_not_logging_returns_failed( + self, mock_footprint, mock_client + ): check = bedrock_app.check_bedrock_cloudtrail_logging mock_ct = MagicMock() mock_client.return_value = mock_ct @@ -413,6 +442,21 @@ def test_br06_trail_not_logging_returns_failed(self, mock_client): assert len(findings) >= 1 assert findings[0]["Status"] == "Failed" + @patch("boto3.client") + @patch("bedrock_app.detect_bedrock_regional_footprint", return_value=False) + def test_br06_no_regional_footprint_returns_na( + self, mock_footprint, mock_client + ): + check = bedrock_app.check_bedrock_cloudtrail_logging + result = check(region="eu-west-1") + findings = extract_csv_data(result) + assert len(findings) >= 1 + assert findings[0]["Status"] == "N/A" + assert findings[0]["Finding_Details"] == ( + "No regional Bedrock resources found to audit with Bedrock-specific CloudTrail coverage" + ) + mock_client.assert_not_called() + @patch("boto3.client") def test_br06_exception_returns_error_finding(self, mock_client): check = bedrock_app.check_bedrock_cloudtrail_logging @@ -423,7 +467,8 @@ def test_br06_exception_returns_error_finding(self, mock_client): assert findings[0]["Status"] == "Failed" @patch("boto3.client") - def test_br06_schema_valid(self, mock_client): + @patch("bedrock_app.detect_bedrock_regional_footprint", return_value=True) + def test_br06_schema_valid(self, mock_footprint, mock_client): check = bedrock_app.check_bedrock_cloudtrail_logging mock_ct = MagicMock() mock_client.return_value = mock_ct @@ -599,6 +644,28 @@ def test_br09_kb_exists_returns_findings(self, mock_client): assert len(findings) >= 1 assert findings[0]["Check_ID"] == "BR-09" + @patch("boto3.client") + def test_br09_access_denied_returns_na(self, mock_client): + check = bedrock_app.check_bedrock_knowledge_base_encryption + mock_agent = MagicMock() + mock_client.return_value = mock_agent + paginator = MagicMock() + mock_agent.get_paginator.return_value = paginator + paginator.paginate.side_effect = ClientError( + { + "Error": { + "Code": "AccessDeniedException", + "Message": "missing permission", + } + }, + "ListKnowledgeBases", + ) + result = check(region="eu-west-1") + findings = extract_csv_data(result) + assert len(findings) >= 1 + assert findings[0]["Status"] == "N/A" + assert "Bedrock Knowledge Base API" in findings[0]["Finding_Details"] + @patch("boto3.client") def test_br09_exception_returns_error_finding(self, mock_client): check = bedrock_app.check_bedrock_knowledge_base_encryption From 1fa8bb107ec01252c1d2285c9a4dd157bdda859b Mon Sep 17 00:00:00 2001 From: Agasthi Kothurkar Date: Sun, 14 Jun 2026 17:03:35 -0400 Subject: [PATCH 11/30] Rename CODEBUILD_TIMEOUT_MINUTES to avoid reserved CodeBuild prefix CodeBuild reserves the CODEBUILD_ prefix for its own built-in environment variables and rejects user-defined variables that use it, causing UpdateProject to fail with InvalidInputException and the stack to enter UPDATE_FAILED. Rename the variable to BUILD_TIMEOUT_MINUTES in both CodeBuild project templates and update the consuming buildspec logic. --- buildspec.yml | 6 +++--- deployment/2-aiml-security-codebuild.yaml | 2 +- deployment/aiml-security-single-account.yaml | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/buildspec.yml b/buildspec.yml index 8b3cf39..a34fc23 100644 --- a/buildspec.yml +++ b/buildspec.yml @@ -219,18 +219,18 @@ phases: # CodeBuild timeout, reserving a buffer for the build/deploy work that runs # before this loop and the report processing that runs after it. An explicit # SF_POLL_TIMEOUT override always wins; a floor guards against tiny/negative - # values when CODEBUILD_TIMEOUT_MINUTES is small. + # values when BUILD_TIMEOUT_MINUTES is small. SF_POLL_BUFFER_SECONDS=${SF_POLL_BUFFER_SECONDS:-600} SF_POLL_FLOOR_SECONDS=${SF_POLL_FLOOR_SECONDS:-300} if [[ -n "$SF_POLL_TIMEOUT" ]]; then sf_poll_timeout=$SF_POLL_TIMEOUT else - sf_poll_timeout=$(( ${CODEBUILD_TIMEOUT_MINUTES:-60} * 60 - SF_POLL_BUFFER_SECONDS )) + sf_poll_timeout=$(( ${BUILD_TIMEOUT_MINUTES:-60} * 60 - SF_POLL_BUFFER_SECONDS )) if [[ $sf_poll_timeout -lt $SF_POLL_FLOOR_SECONDS ]]; then sf_poll_timeout=$SF_POLL_FLOOR_SECONDS fi fi - echo "Step Function poll timeout set to ${sf_poll_timeout}s (CodeBuild timeout: ${CODEBUILD_TIMEOUT_MINUTES:-60}m, buffer: ${SF_POLL_BUFFER_SECONDS}s, floor: ${SF_POLL_FLOOR_SECONDS}s)" + echo "Step Function poll timeout set to ${sf_poll_timeout}s (CodeBuild timeout: ${BUILD_TIMEOUT_MINUTES:-60}m, buffer: ${SF_POLL_BUFFER_SECONDS}s, floor: ${SF_POLL_FLOOR_SECONDS}s)" if [[ $MULTI_ACCOUNT_SCAN = 'true' ]]; then echo "Getting list of accounts for post-build processing" diff --git a/deployment/2-aiml-security-codebuild.yaml b/deployment/2-aiml-security-codebuild.yaml index 3ac5c69..4e4620a 100644 --- a/deployment/2-aiml-security-codebuild.yaml +++ b/deployment/2-aiml-security-codebuild.yaml @@ -531,7 +531,7 @@ Resources: - Name: TARGET_REGIONS Value: !Ref TargetRegions Type: PLAINTEXT - - Name: CODEBUILD_TIMEOUT_MINUTES + - Name: BUILD_TIMEOUT_MINUTES Value: !Ref CodeBuildTimeout Type: PLAINTEXT Description: Run AIML Security multi-account assessment diff --git a/deployment/aiml-security-single-account.yaml b/deployment/aiml-security-single-account.yaml index 4d5e732..98984da 100644 --- a/deployment/aiml-security-single-account.yaml +++ b/deployment/aiml-security-single-account.yaml @@ -441,7 +441,7 @@ Resources: - Name: TARGET_REGIONS Value: !Ref TargetRegions Type: PLAINTEXT - - Name: CODEBUILD_TIMEOUT_MINUTES + - Name: BUILD_TIMEOUT_MINUTES Value: !Ref CodeBuildTimeout Type: PLAINTEXT Description: Run AIML Security single-account assessment From 4c7231f37becd3dbdf4edef681b8e3255c156054 Mon Sep 17 00:00:00 2001 From: Agasthi Kothurkar Date: Fri, 19 Jun 2026 18:38:06 -0400 Subject: [PATCH 12/30] Add FinServ region scope to reports --- .../security/finserv_assessments/app.py | 39 ++++++++++++++++ .../security/finserv_assessments/schema.py | 3 ++ .../security/finserv_tests/test_checks.py | 45 +++++++++++++++++++ .../security/finserv_tests/test_schema.py | 13 ++++++ .../generate_consolidated_report/app.py | 2 +- .../report_template.py | 20 ++++++++- .../test_generate_report.py | 4 ++ .../statemachine/assessments.asl.json | 3 +- aiml-security-assessment/template.yaml | 2 + consolidate_html_reports.py | 2 +- 10 files changed, 129 insertions(+), 4 deletions(-) diff --git a/aiml-security-assessment/functions/security/finserv_assessments/app.py b/aiml-security-assessment/functions/security/finserv_assessments/app.py index bdc5e1b..72c85c4 100644 --- a/aiml-security-assessment/functions/security/finserv_assessments/app.py +++ b/aiml-security-assessment/functions/security/finserv_assessments/app.py @@ -6070,6 +6070,7 @@ def generate_csv_report(findings: List[Dict[str, Any]]) -> str: "Reference", "Severity", "Status", + "Region", "Compliance_Frameworks", ] writer = csv.DictWriter(csv_buffer, fieldnames=fieldnames) @@ -6080,6 +6081,42 @@ def generate_csv_report(findings: List[Dict[str, Any]]) -> str: return csv_buffer.getvalue() +def _normalized_target_regions(value: str) -> List[str]: + """Parse the CloudFormation TargetRegions parameter value.""" + value = (value or "").strip() + if not value or value.lower() == "all": + return [] + return [region.strip() for region in value.split(",") if region.strip()] + + +def _get_region_scope(event: Dict[str, Any]) -> str: + """Return the configured FinServ region scope without assuming a fixed region.""" + target_regions = event.get("TargetRegions") + if isinstance(target_regions, list): + regions = [str(region).strip() for region in target_regions if str(region).strip()] + if regions: + return ", ".join(regions) + + regions = _normalized_target_regions(os.environ.get("TARGET_REGIONS", "")) + if regions: + return ", ".join(regions) + + return ( + event.get("Region") + or os.environ.get("AWS_REGION") + or os.environ.get("AWS_DEFAULT_REGION") + or "" + ) + + +def _stamp_region(findings: List[Dict[str, Any]], region: str) -> None: + """Populate missing CSV Region values so reports show FinServ scope.""" + for finding in findings: + for row in finding.get("csv_data", []): + if not row.get("Region"): + row["Region"] = region + + def write_to_s3(execution_id: str, csv_content: str, bucket_name: str) -> str: """Write CSV report to S3 bucket.""" s3_client = boto3.client("s3", config=boto3_config) @@ -6406,6 +6443,7 @@ def lambda_handler(event, context): """ logger.info("Starting FinServ GenAI security assessment") all_findings = [] + region_scope = _get_region_scope(event) execution_id = event.get("Execution", {}).get("Name", "local-test") permission_cache = get_permissions_cache(execution_id) or { @@ -6437,6 +6475,7 @@ def lambda_handler(event, context): all_findings.append(result) # Generate and upload report + _stamp_region(all_findings, region_scope) csv_content = generate_csv_report(all_findings) bucket_name = os.environ.get("AIML_ASSESSMENT_BUCKET_NAME") if not bucket_name: diff --git a/aiml-security-assessment/functions/security/finserv_assessments/schema.py b/aiml-security-assessment/functions/security/finserv_assessments/schema.py index 657ad66..d55425e 100644 --- a/aiml-security-assessment/functions/security/finserv_assessments/schema.py +++ b/aiml-security-assessment/functions/security/finserv_assessments/schema.py @@ -40,6 +40,7 @@ class Finding(BaseModel): Reference: str = Field(..., description="Documentation reference URL") Severity: SeverityEnum = Field(..., description="Severity level of the finding") Status: StatusEnum = Field(..., description="Current status of the finding") + Region: str = Field(default="", description="AWS region where the finding was identified") Compliance_Frameworks: str = Field( default="", description=( @@ -76,6 +77,7 @@ def create_finding( reference: str, severity: SeverityEnum, status: StatusEnum, + region: str = "", compliance_frameworks: Optional[str] = "", ) -> Dict[str, Any]: """Create a validated finding dict. @@ -94,6 +96,7 @@ def create_finding( Reference=reference, Severity=severity, Status=status, + Region=region, Compliance_Frameworks=compliance_frameworks or "", ) return dict(finding.model_dump()) diff --git a/aiml-security-assessment/functions/security/finserv_tests/test_checks.py b/aiml-security-assessment/functions/security/finserv_tests/test_checks.py index f456443..0371153 100644 --- a/aiml-security-assessment/functions/security/finserv_tests/test_checks.py +++ b/aiml-security-assessment/functions/security/finserv_tests/test_checks.py @@ -3506,6 +3506,7 @@ def test_empty_findings_produces_header_only(self): lines = csv_content.strip().split("\n") assert len(lines) == 1 # header only assert "Check_ID" in lines[0] + assert "Region" in lines[0] def test_findings_produce_csv_rows(self): findings = [ @@ -3530,6 +3531,50 @@ def test_findings_produce_csv_rows(self): assert len(lines) == 2 # header + 1 data row assert "FS-01" in lines[1] + def test_region_scope_uses_configured_target_regions_from_event(self): + event = {"Region": "fallback-region", "TargetRegions": ["region-a", "region-b"]} + + assert app._get_region_scope(event) == "region-a, region-b" + + def test_region_scope_uses_target_regions_env_when_event_list_absent(self, monkeypatch): + monkeypatch.setenv("TARGET_REGIONS", "region-a,region-b") + + assert app._get_region_scope({"Region": "fallback-region"}) == "region-a, region-b" + + def test_stamp_region_populates_missing_csv_regions(self): + findings = [ + { + "check_name": "Test", + "status": "PASS", + "csv_data": [ + { + "Check_ID": "FS-01", + "Finding": "Test Finding", + "Finding_Details": "Details", + "Resolution": "Fix", + "Reference": "https://example.com", + "Severity": "High", + "Status": "Passed", + }, + { + "Check_ID": "FS-02", + "Finding": "Already Scoped", + "Finding_Details": "Details", + "Resolution": "Fix", + "Reference": "https://example.com", + "Severity": "Medium", + "Status": "Failed", + "Region": "Global", + }, + ], + } + ] + + app._stamp_region(findings, "region-a, region-b") + + assert findings[0]["csv_data"][0]["Region"] == "region-a, region-b" + assert findings[0]["csv_data"][1]["Region"] == "Global" + def test_multiple_findings_multiple_rows(self): findings = [ { diff --git a/aiml-security-assessment/functions/security/finserv_tests/test_schema.py b/aiml-security-assessment/functions/security/finserv_tests/test_schema.py index 5daead5..cee18d1 100644 --- a/aiml-security-assessment/functions/security/finserv_tests/test_schema.py +++ b/aiml-security-assessment/functions/security/finserv_tests/test_schema.py @@ -90,10 +90,23 @@ def test_output_has_all_csv_fields(self): "Reference", "Severity", "Status", + "Region", "Compliance_Frameworks", } assert set(result.keys()) == expected_keys + def test_region_defaults_to_empty_string(self): + result = create_finding( + check_id="FS-42", + finding_name="Name", + finding_details="Details", + resolution="Resolution", + reference="https://example.com", + severity="High", + status="Failed", + ) + assert result["Region"] == "" + @pytest.mark.parametrize( "check_id", ["FS-01", "FS-69", "BR-14", "SM-07", "AC-05"], diff --git a/aiml-security-assessment/functions/security/generate_consolidated_report/app.py b/aiml-security-assessment/functions/security/generate_consolidated_report/app.py index dbf6c52..f20fc9c 100644 --- a/aiml-security-assessment/functions/security/generate_consolidated_report/app.py +++ b/aiml-security-assessment/functions/security/generate_consolidated_report/app.py @@ -246,7 +246,7 @@ def generate_html_report(assessment_results: Dict[str, Any]) -> str: region = finding.get("Region", "") # "Global" tags IAM-only findings; it is not a scanned region # and must not inflate the region count / multi-region UI. - if region and region != GLOBAL_REGION_LABEL: + if region and region != GLOBAL_REGION_LABEL and "," not in region: regions.add(region) account_id = assessment_results.get("account_id", "Unknown") diff --git a/aiml-security-assessment/functions/security/generate_consolidated_report/report_template.py b/aiml-security-assessment/functions/security/generate_consolidated_report/report_template.py index c6be6ea..7d0ce52 100644 --- a/aiml-security-assessment/functions/security/generate_consolidated_report/report_template.py +++ b/aiml-security-assessment/functions/security/generate_consolidated_report/report_template.py @@ -936,6 +936,23 @@ def generate_html_report( finserv_failed = service_stats.get("finserv", {}).get("failed", 0) finserv_passed = service_stats.get("finserv", {}).get("passed", 0) if finserv_total > 0: + finserv_regions = sorted( + { + f.get("region", f.get("Region", "")) + for f in service_findings.get("finserv", []) + if f.get("region", f.get("Region", "")) + } + ) + finserv_region_options = "".join( + [f'' for r in finserv_regions] + ) + finserv_region_filter = ( + '
' + '
' + if finserv_regions + else "" + ) finserv_nav = ( '' + FINSERV_ICON @@ -954,10 +971,11 @@ def generate_html_report( '
' + FINSERV_ICON + "Financial Services GenAI Risk Findings
" - '
Scope: this assessment evaluates the deployment Region once per execution. Severities follow a documented Likelihood × Impact methodology (see docs).
' + '
Scope: this assessment records the configured CloudFormation TargetRegions for this execution. Severities follow a documented Likelihood × Impact methodology (see docs).
' '
' '
' + finserv_account_filter + + finserv_region_filter + '
' '
' '' diff --git a/aiml-security-assessment/functions/security/generate_consolidated_report/test_generate_report.py b/aiml-security-assessment/functions/security/generate_consolidated_report/test_generate_report.py index 01baaa1..f1868c5 100644 --- a/aiml-security-assessment/functions/security/generate_consolidated_report/test_generate_report.py +++ b/aiml-security-assessment/functions/security/generate_consolidated_report/test_generate_report.py @@ -276,6 +276,7 @@ def test_finserv_renders_when_present(self): "Reference": "https://docs.aws.amazon.com/waf/latest/developerguide/waf-chapter.html", "Severity": "Medium", "Status": "Failed", + "Region": "region-a, region-b", }, { "Account_ID": "123456789012", @@ -286,6 +287,7 @@ def test_finserv_renders_when_present(self): "Reference": "https://docs.aws.amazon.com/macie/latest/user/what-is-macie.html", "Severity": "High", "Status": "Passed", + "Region": "region-a, region-b", }, ] } @@ -295,6 +297,8 @@ def test_finserv_renders_when_present(self): self.assertIn('', html) def test_finserv_omitted_when_absent(self): """REQ-1/REQ-7: with no FinServ data the FinServ section is omitted cleanly.""" diff --git a/aiml-security-assessment/statemachine/assessments.asl.json b/aiml-security-assessment/statemachine/assessments.asl.json index 8d59f40..849e6b2 100644 --- a/aiml-security-assessment/statemachine/assessments.asl.json +++ b/aiml-security-assessment/statemachine/assessments.asl.json @@ -300,7 +300,8 @@ "Execution.$": "$.Execution", "StateMachine.$": "$.StateMachine", "Region.$": "$.Region", - "RegionIndex.$": "$.RegionIndex" + "RegionIndex.$": "$.RegionIndex", + "TargetRegions.$": "$.OriginalInput.ResolvedRegions.regions" } }, "Retry": [ diff --git a/aiml-security-assessment/template.yaml b/aiml-security-assessment/template.yaml index 4aa2119..7089c28 100644 --- a/aiml-security-assessment/template.yaml +++ b/aiml-security-assessment/template.yaml @@ -145,6 +145,7 @@ Resources: Environment: Variables: AIML_ASSESSMENT_BUCKET_NAME: !Ref AIMLAssessmentBucket + TARGET_REGIONS: !Ref TargetRegions Policies: - S3CrudPolicy: BucketName: !Ref AIMLAssessmentBucket @@ -451,6 +452,7 @@ Resources: Environment: Variables: AIML_ASSESSMENT_BUCKET_NAME: !Ref AIMLAssessmentBucket + TARGET_REGIONS: !Ref TargetRegions Policies: - S3CrudPolicy: BucketName: !Ref AIMLAssessmentBucket diff --git a/consolidate_html_reports.py b/consolidate_html_reports.py index fd22eb9..1b21607 100644 --- a/consolidate_html_reports.py +++ b/consolidate_html_reports.py @@ -110,7 +110,7 @@ def consolidate_html_reports(): # Map CSV columns to finding structure region = row.get("Region", "") # "Global" tags IAM-only findings; not a scanned region. - if region and region != GLOBAL_REGION_LABEL: + if region and region != GLOBAL_REGION_LABEL and "," not in region: regions.add(region) finding = { "account_id": account_id, From a9c3ffc6c34cb2b6f47ff79f1b9bec9adf54bf6e Mon Sep 17 00:00:00 2001 From: Agasthi Kothurkar Date: Fri, 19 Jun 2026 18:51:59 -0400 Subject: [PATCH 13/30] Split FinServ region filter values --- .../security/finserv_assessments/app.py | 34 +++++++++++++------ .../security/finserv_tests/test_checks.py | 19 ++++++----- .../report_template.py | 2 +- .../test_generate_report.py | 7 ++-- 4 files changed, 39 insertions(+), 23 deletions(-) diff --git a/aiml-security-assessment/functions/security/finserv_assessments/app.py b/aiml-security-assessment/functions/security/finserv_assessments/app.py index 72c85c4..6658748 100644 --- a/aiml-security-assessment/functions/security/finserv_assessments/app.py +++ b/aiml-security-assessment/functions/security/finserv_assessments/app.py @@ -6089,32 +6089,44 @@ def _normalized_target_regions(value: str) -> List[str]: return [region.strip() for region in value.split(",") if region.strip()] -def _get_region_scope(event: Dict[str, Any]) -> str: - """Return the configured FinServ region scope without assuming a fixed region.""" +def _get_region_scopes(event: Dict[str, Any]) -> List[str]: + """Return resolved target regions without assuming a fixed deployment region.""" target_regions = event.get("TargetRegions") if isinstance(target_regions, list): regions = [str(region).strip() for region in target_regions if str(region).strip()] if regions: - return ", ".join(regions) + return regions regions = _normalized_target_regions(os.environ.get("TARGET_REGIONS", "")) if regions: - return ", ".join(regions) + return regions - return ( + fallback_region = ( event.get("Region") or os.environ.get("AWS_REGION") or os.environ.get("AWS_DEFAULT_REGION") or "" ) + return [fallback_region] if fallback_region else [] -def _stamp_region(findings: List[Dict[str, Any]], region: str) -> None: - """Populate missing CSV Region values so reports show FinServ scope.""" +def _stamp_regions(findings: List[Dict[str, Any]], regions: List[str]) -> None: + """Expand missing CSV Region values so each target region is filterable.""" + regions = [region for region in regions if region] + if not regions: + return + for finding in findings: + expanded_rows = [] for row in finding.get("csv_data", []): - if not row.get("Region"): - row["Region"] = region + if row.get("Region"): + expanded_rows.append(row) + continue + for region in regions: + regional_row = dict(row) + regional_row["Region"] = region + expanded_rows.append(regional_row) + finding["csv_data"] = expanded_rows def write_to_s3(execution_id: str, csv_content: str, bucket_name: str) -> str: @@ -6443,7 +6455,7 @@ def lambda_handler(event, context): """ logger.info("Starting FinServ GenAI security assessment") all_findings = [] - region_scope = _get_region_scope(event) + region_scopes = _get_region_scopes(event) execution_id = event.get("Execution", {}).get("Name", "local-test") permission_cache = get_permissions_cache(execution_id) or { @@ -6475,7 +6487,7 @@ def lambda_handler(event, context): all_findings.append(result) # Generate and upload report - _stamp_region(all_findings, region_scope) + _stamp_regions(all_findings, region_scopes) csv_content = generate_csv_report(all_findings) bucket_name = os.environ.get("AIML_ASSESSMENT_BUCKET_NAME") if not bucket_name: diff --git a/aiml-security-assessment/functions/security/finserv_tests/test_checks.py b/aiml-security-assessment/functions/security/finserv_tests/test_checks.py index 0371153..566736c 100644 --- a/aiml-security-assessment/functions/security/finserv_tests/test_checks.py +++ b/aiml-security-assessment/functions/security/finserv_tests/test_checks.py @@ -3531,17 +3531,20 @@ def test_findings_produce_csv_rows(self): assert len(lines) == 2 # header + 1 data row assert "FS-01" in lines[1] - def test_region_scope_uses_configured_target_regions_from_event(self): + def test_region_scopes_use_configured_target_regions_from_event(self): event = {"Region": "fallback-region", "TargetRegions": ["region-a", "region-b"]} - assert app._get_region_scope(event) == "region-a, region-b" + assert app._get_region_scopes(event) == ["region-a", "region-b"] - def test_region_scope_uses_target_regions_env_when_event_list_absent(self, monkeypatch): + def test_region_scopes_use_target_regions_env_when_event_list_absent(self, monkeypatch): monkeypatch.setenv("TARGET_REGIONS", "region-a,region-b") - assert app._get_region_scope({"Region": "fallback-region"}) == "region-a, region-b" + assert app._get_region_scopes({"Region": "fallback-region"}) == [ + "region-a", + "region-b", + ] - def test_stamp_region_populates_missing_csv_regions(self): + def test_stamp_regions_expands_missing_csv_regions(self): findings = [ { "check_name": "Test", @@ -3570,10 +3573,10 @@ def test_stamp_region_populates_missing_csv_regions(self): } ] - app._stamp_region(findings, "region-a, region-b") + app._stamp_regions(findings, ["region-a", "region-b"]) - assert findings[0]["csv_data"][0]["Region"] == "region-a, region-b" - assert findings[0]["csv_data"][1]["Region"] == "Global" + regions = [row["Region"] for row in findings[0]["csv_data"]] + assert regions == ["region-a", "region-b", "Global"] def test_multiple_findings_multiple_rows(self): findings = [ diff --git a/aiml-security-assessment/functions/security/generate_consolidated_report/report_template.py b/aiml-security-assessment/functions/security/generate_consolidated_report/report_template.py index 7d0ce52..edec76b 100644 --- a/aiml-security-assessment/functions/security/generate_consolidated_report/report_template.py +++ b/aiml-security-assessment/functions/security/generate_consolidated_report/report_template.py @@ -971,7 +971,7 @@ def generate_html_report( '
' + FINSERV_ICON + "Financial Services GenAI Risk Findings
" - '
Scope: this assessment records the configured CloudFormation TargetRegions for this execution. Severities follow a documented Likelihood × Impact methodology (see docs).
' + '
Scope: this assessment records findings against each resolved CloudFormation TargetRegions entry. Severities follow a documented Likelihood × Impact methodology (see docs).
' '
' '
' + finserv_account_filter diff --git a/aiml-security-assessment/functions/security/generate_consolidated_report/test_generate_report.py b/aiml-security-assessment/functions/security/generate_consolidated_report/test_generate_report.py index f1868c5..62f8199 100644 --- a/aiml-security-assessment/functions/security/generate_consolidated_report/test_generate_report.py +++ b/aiml-security-assessment/functions/security/generate_consolidated_report/test_generate_report.py @@ -276,7 +276,7 @@ def test_finserv_renders_when_present(self): "Reference": "https://docs.aws.amazon.com/waf/latest/developerguide/waf-chapter.html", "Severity": "Medium", "Status": "Failed", - "Region": "region-a, region-b", + "Region": "region-a", }, { "Account_ID": "123456789012", @@ -287,7 +287,7 @@ def test_finserv_renders_when_present(self): "Reference": "https://docs.aws.amazon.com/macie/latest/user/what-is-macie.html", "Severity": "High", "Status": "Passed", - "Region": "region-a, region-b", + "Region": "region-b", }, ] } @@ -298,7 +298,8 @@ def test_finserv_renders_when_present(self): self.assertIn("FS-01", html) self.assertIn('data-service="finserv"', html) self.assertIn('id="finservRegionFilter"', html) - self.assertIn('', html) + self.assertIn('', html) + self.assertIn('', html) def test_finserv_omitted_when_absent(self): """REQ-1/REQ-7: with no FinServ data the FinServ section is omitted cleanly.""" From b58d653a582a2972b8e26cfa02d75830cf7f5add Mon Sep 17 00:00:00 2001 From: Agasthi Kothurkar Date: Fri, 19 Jun 2026 19:03:08 -0400 Subject: [PATCH 14/30] Show FinServ in assessment scope --- .../report_template.py | 18 +++++++++++++++++- .../test_generate_report.py | 2 ++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/aiml-security-assessment/functions/security/generate_consolidated_report/report_template.py b/aiml-security-assessment/functions/security/generate_consolidated_report/report_template.py index edec76b..2bd0413 100644 --- a/aiml-security-assessment/functions/security/generate_consolidated_report/report_template.py +++ b/aiml-security-assessment/functions/security/generate_consolidated_report/report_template.py @@ -966,6 +966,12 @@ def generate_html_report( + f' FinServ
{finserv_total}
' + f'
{finserv_failed} Failed \u00b7 {finserv_passed} Passed
' ) + finserv_scope_chip = ( + '
' + + FINSERV_ICON_SMALL + + 'Financial Services GenAI Risk
' + ) finserv_section = ( '
' '
' @@ -989,12 +995,13 @@ def generate_html_report( finserv_nav = "" finserv_filter_option = "" finserv_service_card = "" + finserv_scope_chip = "" finserv_section = "" # Fill template html_template = get_html_template() - return html_template.format( + rendered_html = html_template.format( title=title, sidebar_subtitle=sidebar_subtitle, account_info=account_info, @@ -1053,3 +1060,12 @@ def generate_html_report( account_risk_section=account_risk_section, region_risk_section=region_risk_section, ) + if finserv_scope_chip: + rendered_html = rendered_html.replace( + 'Amazon Bedrock AgentCore

Amazon Bedrock AgentCore' + + finserv_scope_chip + + '

region-a', html) self.assertIn('', html) + self.assertIn('data-scope-service="finserv"', html) def test_finserv_omitted_when_absent(self): """REQ-1/REQ-7: with no FinServ data the FinServ section is omitted cleanly.""" html = generate_html_report(self.test_assessment_results) self.assertNotIn('id="finserv"', html) self.assertNotIn('AWS guide for Financial Services risk management of the use of Generative AI.' + ) finserv_section = ( '

' '
' + FINSERV_ICON + "Financial Services GenAI Risk Findings
" - '
Scope: this assessment records findings against each resolved CloudFormation TargetRegions entry. Severities follow a documented Likelihood × Impact methodology (see docs).
' + '
Scope: this assessment records findings against each resolved CloudFormation TargetRegions entry. These checks are derived from the ' + f'AWS guide for Financial Services risk management of the use of Generative AI. ' + 'Severities follow a documented Likelihood × Impact methodology (see docs).
' '
' '
' + finserv_account_filter @@ -996,6 +1006,7 @@ def generate_html_report( finserv_filter_option = "" finserv_service_card = "" finserv_scope_chip = "" + finserv_scope_source = "" finserv_section = "" # Fill template @@ -1068,4 +1079,10 @@ def generate_html_report( + '

region-a', html) self.assertIn('', html) self.assertIn('data-scope-service="finserv"', html) + self.assertIn("global-FinServ-ComplianceGuide-GenAIRisks-public.pdf", html) def test_finserv_omitted_when_absent(self): """REQ-1/REQ-7: with no FinServ data the FinServ section is omitted cleanly.""" @@ -308,6 +309,7 @@ def test_finserv_omitted_when_absent(self): self.assertNotIn('id="finserv"', html) self.assertNotIn('

' ) - finserv_scope_chip = ( - '
' + finserv_scope_industry_block = ( + '
' + '
Industry
' + '
' + FINSERV_ICON_SMALL - + 'Financial Services GenAI Risk
' + + 'Financial Services GenAI Risk
' ) finserv_scope_source = ( f' Includes Financial Services GenAI Risk checks derived from the ' @@ -1003,9 +1019,10 @@ def generate_html_report( ) else: finserv_nav = "" + industry_nav = "" finserv_filter_option = "" finserv_service_card = "" - finserv_scope_chip = "" + finserv_scope_industry_block = "" finserv_scope_source = "" finserv_section = "" @@ -1064,19 +1081,20 @@ def generate_html_report( bedrock_region_filter=bedrock_region_filter, sagemaker_region_filter=sagemaker_region_filter, agentcore_region_filter=agentcore_region_filter, - finserv_nav=finserv_nav, + industry_nav=industry_nav, finserv_filter_option=finserv_filter_option, finserv_service_card=finserv_service_card, finserv_section=finserv_section, account_risk_section=account_risk_section, region_risk_section=region_risk_section, ) - if finserv_scope_chip: + if finserv_scope_industry_block: rendered_html = rendered_html.replace( 'Amazon Bedrock AgentCore

Amazon Bedrock AgentCore' - + finserv_scope_chip - + '

By Industry

", 1 + )[0] + self.assertNotIn("Financial Services", by_service_nav) def test_missing_data_fields(self): """Test handling of assessment results with missing fields""" @@ -301,14 +324,28 @@ def test_finserv_renders_when_present(self): self.assertIn('', html) self.assertIn('', html) self.assertIn('data-scope-service="finserv"', html) + self.assertIn('class="scope-industry"', html) + self.assertIn('class="scope-chip industry-chip"', html) + self.assertIn('class="nav-section industry-nav"', html) + self.assertIn("Financial Services Risk", html) + self.assertIn("Assessment Area", html) + self.assertIn("All Assessment Areas", html) self.assertIn("global-FinServ-ComplianceGuide-GenAIRisks-public.pdf", html) + self.assertIn("

By Industry

", html) + by_service_nav = html.split("

By Service

", 1)[1].split( + "

By Industry

", 1 + )[0] + self.assertNotIn("Financial Services", by_service_nav) def test_finserv_omitted_when_absent(self): """REQ-1/REQ-7: with no FinServ data the FinServ section is omitted cleanly.""" html = generate_html_report(self.test_assessment_results) self.assertNotIn('id="finserv"', html) + self.assertNotIn("

By Industry

", html) self.assertNotIn('
-
Assessment Methodology
+
Assessment Methodology

Severity Levels & Status Values

HighDirect security riskFailedRemediation needed
MediumDefense-in-depth gapPassedMeets requirements
LowBest practiceN/ANot applicable
InformationalNo action required

Remediation Guidance

High7 daysAddress immediately; block deployment if unresolved
Medium30 daysSchedule in next sprint; may require change window
Low90 daysInclude in backlog; address during regular maintenance

Assessment Notes

Point-in-time: Security posture changes as resources are modified. Scope limited: Passed checks verify tested controls only. Context matters: Adjust severity for compliance requirements and environment type.
-

Assessment Scope

Amazon Bedrock
Amazon SageMaker
Amazon Bedrock AgentCore

Based on AWS Well-Architected Framework (Generative AI Lens) and service-specific security documentation.

+

Assessment Scope

Amazon Bedrock
Amazon SageMaker
Amazon Bedrock AgentCore
Industry
Financial Services GenAI Risk

Bedrock, SageMaker, and AgentCore checks are based on the AWS Well-Architected Framework Generative AI Lens. Financial Services GenAI Risk checks are based on the AWS User Guide to Governance, Risk, and Compliance for Responsible AI Adoption.

@@ -7612,6 +16853,7 @@

{ const rowText = row.textContent.toLowerCase(); const rowAccount = row.dataset.account || ''; + const rowRegion = row.dataset.region || ''; const rowService = row.dataset.service || ''; const rowSeverity = row.dataset.severity || ''; const rowStatus = row.dataset.status || ''; let show = true; if (searchText && !rowText.includes(searchText)) show = false; if (accountFilter && rowAccount !== accountFilter) show = false; + if (regionFilter && rowRegion.toLowerCase() !== regionFilter) show = false; if (serviceFilter && rowService !== serviceFilter) show = false; if (severityFilter && rowSeverity !== severityFilter) show = false; if (statusFilter && rowStatus !== statusFilter) show = false; @@ -7634,6 +16878,7 @@

{ const rowText = row.textContent.toLowerCase(); const rowAccount = row.dataset.account || ''; + const rowRegion = row.dataset.region || ''; const rowSeverity = row.dataset.severity || ''; const rowStatus = row.dataset.status || ''; let show = true; if (searchText && !rowText.includes(searchText)) show = false; if (accountValue && rowAccount !== accountValue) show = false; + if (regionValue && rowRegion.toLowerCase() !== regionValue) show = false; if (severityValue && rowSeverity !== severityValue) show = false; if (statusValue && rowStatus !== statusValue) show = false; row.style.display = show ? '' : 'none'; @@ -7735,22 +16989,25 @@

\ No newline at end of file + + \ No newline at end of file diff --git a/sample-reports/security_assessment_multi_region_sample.html b/sample-reports/security_assessment_multi_region_sample.html deleted file mode 100644 index e20cccd..0000000 --- a/sample-reports/security_assessment_multi_region_sample.html +++ /dev/null @@ -1,873 +0,0 @@ - - - - - - AI/ML Security Assessment Report - - - - -
- -
-
- -
-
Security Checks
9
Evaluated across 3 regions
-
Total Findings
16
Across 3 regions
-
Actionable Findings
11
High, Medium, and Low severity
-
High Severity
4/9
44.4% passed · Immediate action required
-
Medium Severity
0/2
0.0% passed · Should be addressed
-
Low Severity
0/0
0% passed · Best practices
-
-

Priority Recommendations

-
2
-
-
Bedrock Full Access Roles
-
Bedrock
-
-
-
1
-
-
Bedrock Logging Configuration
-
Bedrock
-
-
-
1
-
-
SageMaker Direct Internet Access
-
SageMaker
-
-
-
1
-
-
Bedrock Guardrails Check
-
Bedrock
-
-
-
-

Severity Legend

View full methodology
-
- - - - - - - - -
SeverityMeaningRecommended Action
HighDirect security risk - IAM/access control gaps, missing audit trails, guardrail bypasses that could lead to unauthorized access or data exposureRemediate within 7 days
MediumDefense-in-depth gaps - encryption, logging, or configuration issues that reduce security postureRemediate within 30 days
LowBest practice deviations - optimization opportunities that improve security hygieneRemediate within 90 days
InformationalNo resources found or advisory recommendations - check does not apply or suggests optional improvementsNo action required
-
-
-
-
-
All Security Findings
-
-
- -
-
-
-
- -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Account IDRegionCheck IDFindingDetailsResolutionReferenceSeverityStatus
123456789012us-east-1BR-01Bedrock Full Access RolesFound 2 roles with AmazonBedrockFullAccess policy attached: BedrockAdmin, MLEngineerReview and restrict to least privilege access.HighFailed
123456789012us-east-1BR-05Bedrock Guardrails CheckAmazon Bedrock Guardrails are properly configured with 3 guardrailsNo action required.HighPassed
123456789012us-east-1BR-04Bedrock Logging ConfigurationModel invocation logging is enabledNo action required.HighPassed
123456789012us-west-2BR-01Bedrock Full Access RolesFound 2 roles with AmazonBedrockFullAccess policy attachedReview and restrict to least privilege access.HighFailed
123456789012us-west-2BR-05Bedrock Guardrails CheckNo Bedrock Guardrails configured in this region.Configure Bedrock Guardrails.MediumFailed
123456789012us-west-2BR-04Bedrock Logging ConfigurationModel invocation logging is not enabledEnable model invocation logging.HighFailed
123456789012eu-central-1BR-00Bedrock Service AvailabilityAmazon Bedrock is not available in region eu-central-1.No action required.InformationalN/A
123456789012us-east-1SM-01SageMaker Direct Internet AccessFound 1 notebook with direct internet access: ml-notebook-devDisable direct internet access.HighFailed
123456789012us-east-1SM-03SageMaker Data EncryptionAll training jobs use KMS encryptionNo action required.HighPassed
123456789012us-west-2SM-01SageMaker Direct Internet AccessNo notebook instances foundNo action required.InformationalPassed
123456789012us-west-2SM-03SageMaker Data EncryptionFound 2 training jobs without KMS encryptionConfigure KMS keys.HighFailed
123456789012eu-central-1SM-01SageMaker Direct Internet AccessNo notebook instances foundNo action required.InformationalPassed
123456789012us-east-1AC-01AgentCore VPC ConfigurationAll runtimes deployed in private subnetsNo action required.HighPassed
123456789012us-east-1AC-03AgentCore EncryptionECR repositories lack KMS encryptionEnable KMS encryption for ECR.MediumFailed
123456789012us-west-2AC-00AgentCore Service AvailabilityAgentCore not available in us-west-2.No action required.InformationalN/A
123456789012eu-central-1AC-00AgentCore Service AvailabilityAgentCore not available in eu-central-1.No action required.InformationalN/A
-
-
-
Risk Distribution
-

Pass Rate by Severity

-
-
HIGH
44.4%
4 of 9 checks passed
-
MEDIUM
0.0%
0 of 2 checks passed
-
LOW
0%
0 of 0 checks passed
-
Overall
36.4%
4 of 11 actionable checks
-
- -

Risk by Region

-
eu-central-1
0
0 High · 0 Med · 0 Low
us-east-1
3
2 High · 1 Med · 0 Low
us-west-2
4
3 High · 1 Med · 0 Low
-

Findings by Service

-
-
Bedrock
7
4 Failed · 2 Passed
-
SageMaker
5
2 Failed · 3 Passed
-
AgentCore
4
1 Failed · 1 Passed
-
-
-
-
Amazon Bedrock Findings
-
-
- -
-
-
- -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Account IDRegionCheck IDFindingDetailsResolutionReferenceSeverityStatus
123456789012us-east-1BR-01Bedrock Full Access RolesFound 2 roles with AmazonBedrockFullAccess policy attached: BedrockAdmin, MLEngineerReview and restrict to least privilege access.HighFailed
123456789012us-east-1BR-05Bedrock Guardrails CheckAmazon Bedrock Guardrails are properly configured with 3 guardrailsNo action required.HighPassed
123456789012us-east-1BR-04Bedrock Logging ConfigurationModel invocation logging is enabledNo action required.HighPassed
123456789012us-west-2BR-01Bedrock Full Access RolesFound 2 roles with AmazonBedrockFullAccess policy attachedReview and restrict to least privilege access.HighFailed
123456789012us-west-2BR-05Bedrock Guardrails CheckNo Bedrock Guardrails configured in this region.Configure Bedrock Guardrails.MediumFailed
123456789012us-west-2BR-04Bedrock Logging ConfigurationModel invocation logging is not enabledEnable model invocation logging.HighFailed
123456789012eu-central-1BR-00Bedrock Service AvailabilityAmazon Bedrock is not available in region eu-central-1.No action required.InformationalN/A
-
-
-
Amazon SageMaker Findings
-
-
- -
-
-
- -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Account IDRegionCheck IDFindingDetailsResolutionReferenceSeverityStatus
123456789012us-east-1SM-01SageMaker Direct Internet AccessFound 1 notebook with direct internet access: ml-notebook-devDisable direct internet access.HighFailed
123456789012us-east-1SM-03SageMaker Data EncryptionAll training jobs use KMS encryptionNo action required.HighPassed
123456789012us-west-2SM-01SageMaker Direct Internet AccessNo notebook instances foundNo action required.InformationalPassed
123456789012us-west-2SM-03SageMaker Data EncryptionFound 2 training jobs without KMS encryptionConfigure KMS keys.HighFailed
123456789012eu-central-1SM-01SageMaker Direct Internet AccessNo notebook instances foundNo action required.InformationalPassed
-
-
-
Amazon Bedrock AgentCore Findings
-
-
- -
-
-
- -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Account IDRegionCheck IDFindingDetailsResolutionReferenceSeverityStatus
123456789012us-east-1AC-01AgentCore VPC ConfigurationAll runtimes deployed in private subnetsNo action required.HighPassed
123456789012us-east-1AC-03AgentCore EncryptionECR repositories lack KMS encryptionEnable KMS encryption for ECR.MediumFailed
123456789012us-west-2AC-00AgentCore Service AvailabilityAgentCore not available in us-west-2.No action required.InformationalN/A
123456789012eu-central-1AC-00AgentCore Service AvailabilityAgentCore not available in eu-central-1.No action required.InformationalN/A
-
-
-
Assessment Methodology
-

Severity Levels & Status Values

HighDirect security riskFailedRemediation needed
MediumDefense-in-depth gapPassedMeets requirements
LowBest practiceN/ANot applicable
InformationalNo action required
-

Remediation Guidance

High7 daysAddress immediately; block deployment if unresolved
Medium30 daysSchedule in next sprint; may require change window
Low90 daysInclude in backlog; address during regular maintenance
-

Assessment Notes

Point-in-time: Security posture changes as resources are modified. Scope limited: Passed checks verify tested controls only. Context matters: Adjust severity for compliance requirements and environment type.
-

Assessment Scope

Amazon Bedrock
Amazon SageMaker
Amazon Bedrock AgentCore

Based on AWS Well-Architected Framework (Generative AI Lens) and service-specific security documentation.

-
-
-
-
Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. Licensed under MIT-0. This report is provided as-is for informational purposes only and does not constitute professional security advice, compliance certification, or audit evidence. You are responsible for validating findings and determining applicability to your environment. See the AWS Customer Agreement for terms of use.
- - - \ No newline at end of file diff --git a/sample-reports/security_assessment_single_account.html b/sample-reports/security_assessment_single_account.html index baa32cf..8a65452 100644 --- a/sample-reports/security_assessment_single_account.html +++ b/sample-reports/security_assessment_single_account.html @@ -1,9 +1,10 @@ - - - + + + AI/ML Security Assessment Report + - +
- @@ -3660,6 +4172,7 @@

{ const rowText = row.textContent.toLowerCase(); const rowAccount = row.dataset.account || ''; + const rowRegion = row.dataset.region || ''; const rowService = row.dataset.service || ''; const rowSeverity = row.dataset.severity || ''; const rowStatus = row.dataset.status || ''; let show = true; if (searchText && !rowText.includes(searchText)) show = false; if (accountFilter && rowAccount !== accountFilter) show = false; + if (regionFilter && rowRegion.toLowerCase() !== regionFilter) show = false; if (serviceFilter && rowService !== serviceFilter) show = false; if (severityFilter && rowSeverity !== severityFilter) show = false; if (statusFilter && rowStatus !== statusFilter) show = false; @@ -3682,6 +4197,7 @@

{ const rowText = row.textContent.toLowerCase(); const rowAccount = row.dataset.account || ''; + const rowRegion = row.dataset.region || ''; const rowSeverity = row.dataset.severity || ''; const rowStatus = row.dataset.status || ''; let show = true; if (searchText && !rowText.includes(searchText)) show = false; if (accountValue && rowAccount !== accountValue) show = false; + if (regionValue && rowRegion.toLowerCase() !== regionValue) show = false; if (severityValue && rowSeverity !== severityValue) show = false; if (statusValue && rowStatus !== statusValue) show = false; row.style.display = show ? '' : 'none'; @@ -3783,22 +4308,25 @@

\ No newline at end of file + + diff --git a/test_consolidate_finserv.py b/tests/test_consolidate_finserv.py similarity index 87% rename from test_consolidate_finserv.py rename to tests/test_consolidate_finserv.py index a1b95e9..f01fa0f 100644 --- a/test_consolidate_finserv.py +++ b/tests/test_consolidate_finserv.py @@ -1,13 +1,11 @@ -"""Multi-account consolidation: FS-* Check-IDs must categorize to the finserv -service (not be mislabeled as bedrock). Patches S3 and the renderer to capture -the service_findings/service_stats passed to the shared template.""" +"""Multi-account consolidation categorizes FS-* checks as finserv.""" -import os import csv +import os import shutil import tempfile import unittest -from unittest.mock import patch, MagicMock +from unittest.mock import MagicMock, patch import consolidate_html_reports as chr @@ -79,9 +77,8 @@ def fake_render(**kwargs): bedrock_ids = {f["check_id"] for f in sf["bedrock"]} self.assertIn("FS-01", finserv_ids) self.assertIn("FS-44", finserv_ids) - self.assertNotIn("FS-01", bedrock_ids) # the bug was FS-* -> bedrock + self.assertNotIn("FS-01", bedrock_ids) self.assertIn("BR-01", bedrock_ids) - # finserv stats counted self.assertEqual(captured["service_stats"]["finserv"]["failed"], 1) self.assertEqual(captured["service_stats"]["finserv"]["passed"], 1) From 3ed453c6b4f36916378cef28c27ef34ab0d64e24 Mon Sep 17 00:00:00 2001 From: Agasthi Kothurkar Date: Fri, 19 Jun 2026 22:55:17 -0400 Subject: [PATCH 20/30] Anonymize sample report account IDs --- sample-reports/dashboard-overview-dark.png | Bin 146347 -> 145186 bytes sample-reports/dashboard-overview-light.png | Bin 147899 -> 146593 bytes sample-reports/findings-table.png | Bin 186723 -> 179514 bytes .../security_assessment_multi_account.html | 5922 ++++++++--------- .../security_assessment_single_account.html | 1396 ++-- 5 files changed, 3659 insertions(+), 3659 deletions(-) diff --git a/sample-reports/dashboard-overview-dark.png b/sample-reports/dashboard-overview-dark.png index fb2fe7147f7f5a2dc11b7613be35212c3aeeb65a..f868852368986497f391d9ed0caacb979b2ed37f 100644 GIT binary patch literal 145186 zcmb5W1yoeu+cta%X~7^xT0rTLk{Cgyk?wA!yGuk+QlzD%8M=n8ZUzSCJO2L9 z?|I*6t?&ER`>r+2n%HyB*?Zq{UDtgdzAJo`c!={12LOPFQj+hL0N}xI06?S0LIpq3 z%24tFPyj&cy{L-&?EZp_`<&Voa7^d4-+|9<+^(@1V`!RFWSnEy;5INYU&PGK&HXho zG4UYb;*LgwGpL$q=xAqcOD)b+vq`OR&1sl^9<3Wy%;Hhbux6=wzqk-S7ERzIF)W&M zDx+nZxBvAf@R5N(+J8R(`z1%q=f6Y#8HY}~{C_(N70;gfpWpuf9_xRMLc!qwe?Q~@ z=cqsc?fHLS{r}l0F@F^N|GuvO{fz(PSpQ=bO$&he?@Ih1j`|-<>Bc+?-pdAxL1m>s zb{~HG_eZUMg6})0y19i{ygl3CGPzgWiL#Ae(0VmC?YwI5^E^_Z#+Uo^%~5ou@Y^Ez ziB}1^$k*l)H!Vc%dEnWJy1Ye+-^TvurhS_De)w{Vda*9Q_i4{%!4`h1NS*OuekT%6 z&f#>k{;=5_wi9{O6-n`V;qkwQ5rE`v;dGl+$v3pI5%4s5eQx6&QXMY{=rqUvcAMHy zEGsS^^2DX`u;&qdgtpio-t1Gc+?2S9>gyooVP%yRo!MT%E^>hwBS2Q$y;WAO5KE8g z-H;nB8h=7%7nu|vx!B+x)4gVd`QhJvU0%YoA4q=yTZvh!S&|M%#eDbG*;nv zV`FS+i0v}vrtfR;>ItQQtLIUx@%YcqGxOmj!OX_YwH=a0bpP0X;-O^T9W$NP92>7x zPivq3^)lqu?roOV#jMOQ(2{iItEQ-Vu}6?r$1kWzfLz;RpJ2L}r0@!r?qBjfcmPaS z+FZdyr}DhW@VbVVe5|{;>zy~c^po6-)W!c@eUymSU)*%W!0OZJNJ_|EAFe#sHA3oX zSF(@~iYH64faUww7DUUxVU?9?lj9f!XZp>y@Lc_SEC4k3tHD%!V9K^5oI?1A7LfzC zR8i3ZZB(b1UE{uio!55H&LzHsSFnaxN^M21@990c%G~7So0lzir-5aAl)iCn5^sEF zv)CcNp_B~VtgeW_GP)zbbpa+g?dumULzBETuVNQ}M_0U4F|klq#=X!dG4IanQ3&wg z&O453r$h0lZkcbRW#S?P+=cLgz#w#N*4JeT-%1StewWL(#4~kk>q4P`q3%>G*+b(zKh*%$*#%Z`(JmeNe@p2IA?fGjpPCBqXf46+cVwH&$#KmEqRI_6`O=?c7xoW#D^Pw z5A5IYz8)$V8MclG=che4>vW2f&2{%8p2}}V5;Ld?2_I%}zM~hQM+00BgwcVh6&l|r zz46AgO<6SmjMeh%4#NHJfAnS^C$OBSi6HM>6mhLY>i0b}upB;NytAvi!E zb3tE!0p5|Y&!v*7%frZS-Fh=#7{QdWAdX>WPcrXIy${hOqv#lSTuoq)A#u*T_mh28Tv zp5P)?d&4i-LB}Ld`gLG!a|>e>bfrpo?KApEHFBg~yYi^cQ}^k~Ow(CQPLWr+6E3hk z5~ZAyq6BDEU5fyZj^Sm|T_`Pwcfn;(>HdxF?;b5Da9c3}CR>{_*<2z^ES#JVfobc> zb7-`u`}p)ICt$xO9x&>q$}|yo;DurUU-gg|E=%oRQ&Uk*6Y%o*QLm+-aoUnO%&v7Z zviPNY+>$4F@c!n^?cl%~g_@uL5imcmbAP{9UqtRVUw6_C+OTpux}QEffQ5~X?ms^t zaMLAjY2`&g>2>`b`S-8D&SLvkST$@kHF~bQ)0u>i~_}&+izu zwC4sNAhL0{P+KMsye2F!el%<%0#>g?5WDTeO_%_Fl3Z*{X$36jkVs2I0ja^F1_opY zDS`O@2Y|K}Ghnfk-)(s6Z>cI~S{4?=|H;BS%aQZO>gw^Zrk*IWj7Essz;^ zOlfz)AMwl}`aUO6o&w_j_0zKGtzHvHpvHRxqv2i%;1`@ld#k3VXX4&7Sd%4@>fO6d z%vJ7~N&D`uUfKZgFDP)*kfa858Xr$h&MuCRTbf&uFCV=tSzg;Ptf8nCn>j1apLk)h z**Ad=G2PhA%`yKPj3Mlf#8Fo_2c{d3ur3Y$wGS+ev{p{@O+uh&pf8+K_{%-tryHZ) zMa?lra-Z>O#dZ{GVLBN*%BR9}u7~lx8$hYm(aL92;gRB{5KMqA_-x=QL79duP-8dr zdbfCeTo#ZV>_PQk8C{8H2uq(t!0Y9QD3>nQs;QIqH zA-Bhae0m;gOi`$B4HdD0_aB19gMxt;FvxX>lL`3ivc6)@eRg(~v|?aqZq}7~Gz;5$ z1S3EfHQ8J@HQ6*E5I5j}JPvMea=OyDFck6GeZg!cZt_$Oa;e91g2 zEM$?03`6nHWE}1z!twMlRz8chss@v~fY)YBGw;!B@8xYpPI1O>5AH^v%P~(0xAJi_ zbG+<=%Ds4^_oh+bt9RJXI>QL1^15UEJZyjDN6YOLi*B7ou?|~xB_YaNB}Y$BGhQHB zz-?zWY#K}rh~takjab#H$w@OaA(+LhHnEN5;Ez*?n{bm)w za;`oyNAEVffX{q5nRfY%4+$6Mp|>%02@j1n_KURC^EC|CFk)M7K3!EVX}BH_PVd(*s*c znpz>x$dd@3i=1qau`6GQ#7a*f8*dg<(IocBpUvwe4-U9@(r-ix!MTKUv88_6HJt5C z9^B^VTd@v2u;wjjDm!V6Y+5Y41z>I9GW*`R(8Wf#Z8%=;Lrza}mmV(X^_iSvQu&Cy z5?J79{7|fbxZE-WERG&g3A*6dvu9l2dQl0KZbSu7hLKW!Vq31zDE$2vX)3dMv{+_% z(pKv>f=kBj%AaP30a)`U^<`%qyU(jK#}{hV2IgO%5ok7CH_GVlv>Hfyys2Fl9I5lx_`S?E! z!$-6RGvY2j%@O_wj`X^yot(VH9wWwAT^R8#tALqSB&trpa@6cy9BlV2?5u3~U;&}g znZI6y$C1!rwY4w6%Ja2GwTr?(hl}u6`Bi{k7qNgTa*x2=Z>s*j<3zkaQ+@(@9{tn) ztTH8q73kRrsR?}XGcX~~-qU_8WFk_p`LUnGb15ejWXC84CFS7+5A2M%um}Wrakp@f zG(i{v@Q2%JbH4gC&D|2?%VW1SLY~|w8!166WxBmeB$t)=DOCaZ?FvbI~WFZzEF2LktlIHtis4+!4L($3S~ z^8vr7G7Rh0p#1?U?AOtVb3wJuA`nX1>-i`)5D(P{eU zTT64fa)|)odMW<%EK%0X!s2&mg|dx}kF;fw5_G?ot)v+cbHi-y%cwdw0%Dt$>!oevmgB2I%4Gl8?kbEO|;GdROom-!z zlr1YMxs!f#`a`Hh(xP?69_4p!_6Mj+`OH4(5fYg785w`}=e-ZMa0qZW&)`y0P~ibd z8$&iMd`dGb^XPc>AMxrY&gOZ!nf1ZBx!MN)`kvyhA539Gzv5`|Ll0IqXD{$i^QyIr zRVu40pZQz{Q|i1cL0)>L+0E1?S@v&)?D4IZEPYdls_ad&VfvRDiw`7=z&oI4Dtwu0 zRyJ0#{Y0K0QuwX$L0d1Mth#LSGdYi70&41OuR(imwx9ST-C`LlZ}OkZq~J z2hP|R{bo_Vu#gpM{>jQNlL0r=jJdjee4N?l$Rb|a^t`{Us(g1X_djX@+H;0c{ZfTy z^lLZ>Qk-3--42DrB+y3R2+}*JjIMA-onZfh#y8)tTpjsdFLZPweAt`MW1=(BoaK6x z6Jl<5%$Kss2l|Z-*{=7a-OPp(28=!p5lLq|4mZ*1`<>yY2o0|}JY5@7IM8%bGcnux zL*RG%{bE??f&5!1KDk5m>(A(~YtLzn zCjH_!^>uKVF^(+ux}WLZeu#m8mZ_!yRnhb1q-kxxJiH05v%08bEYWF9Z#>iYy9XM82r#%s*{EPW-XW}8GL8Kx#%i$kzH%GePrz`9) zx?SH9G>xb3_jbxsWmod zg}85DL(Uth8j)QM9^5Df3pcqw4Z?apa7{{ib^lJ7n_(;Pw;2-p^aVh zm52Rt>2{typiyqJ_+5ER>Zg#iK}A96Q^5P~pk&DxnRI!>7pP41CRDrkVnk~N}6agG=H?Dc^DQMupQ5@axiJG?0a10W8CIl|i->v^ZAwFu0r z7j%=;+`aFDj~d0pGszNauGh!M@$&Ds+Y4Yv0$YhUXG_oS1mJXxK|$ z3i2UGqN2x7;b-P##SKn#NBwaFQz8u3yj~X}olAbV*o_@Cy(inPMV>rdMTM+Oo@Yo( zt!5nOT-%wOEBSL(W`HAcptN+^uKDI|$=m%A5%!HDK0WWPqWGO{3;@j3@LM~5yZgOh zo`U#4XAika!}5Ac!<+qbbtg~(5%{%Vq)0>Gs z|B`4ypxO%|d;Jvgh*hNhAcmxnJ8q{s@hL;EEmMLxRCqa$v;{8QZhm*_oMhJCK)@{akFw z558JzvO*3O&xHCel-BO8+7vc7^WD9*OuzcgNubGC z>Lj_GnZbLy=1Se~nPP?E`vo8%^*y*C$U|}C&XIdYyew11YuxpM35bbTue3aPTCr6; zeLTgxTb!Hb{;k!(d4k(@whnG?*|&QgxIU$aT<(ltY;xSdkot*-u~xvoNY3Xv4moM~ z&9j&ex^r$C^5*h(X?K@rjj&A&wL6k(Vdm9BgXxq6m7lJPiZpBFH!RJX--4T`v&_TA zT1{Ah2^W008F6u($&R?WHM68MOcy2hMNS^`vuktdLzQe0Ufs7;zKL;Ba;%kAWuEtm z5<4>I2lbcnaq0@l!9munMO)=oT*mH0kD9T1UW+UcOpl^G$PYde{QaFD9sT=d<)IY_z9yDUFYiQ}>kp!_asTsz;vH7GxH zNxSKV<4)z3Q$#7Q$TCEG?$>bA?Hr&#Jj<1Ee@OLMiib_7Cd6FvFH1wgR}{rsU=Tr` zb|V5Z;`O{ke)%WfJQU!fJ-<)~Wwk*9`IK78l3L3Rqusu(l5oMryJaWKm7Jn_J3RqI z8)H)&V;Vts|da5CDHf2oj&0Pj`ygN^nG>`b5p$^0Sxlb_O=Zm37=l6 zFik{4kRHd!-+%pjQ)dEf%k%r}4AjbisWtL@1mK_lYZlzCw_abLG7iPYsw+t`6BO&q zE!A6gishG?&QTteQwJrdJl}cQl}?unyk&_>7W6YcO#TFv+P6MGcv56&D5{c~k+C}t z#l`tb3q}aO=8?rA44^}IgQ!g`04lB|Jv(8=heA!qeAN~1P8^C22vNo}g>YJJ66U|P zR&t!(b&*$Szq&kxlsmLvDI0ZzK40sUt4KgKyOsTlFa(p*Bcd+=z7yZ9^n`t6hZNKU z1tlU=G5nW!tp$9H2#DTc8qT41l%`kr#_}O0YL7so{;1Ic-gx-090sRGD=`J zLUUrIH|Qc8-%<=e^s~7=+>0b;OLz!~ilY3opP))YS>B?8qcgJK^0wHC(je2 zPI00;)6Y&rVY{6gQ+%%piQ}?z%mRah6{Mb@8x+q}}=B@^alzkCF?QgJ>L`psJkM`Ymv*GxK5(>zac=S+c&#T#+6rue;F{y)3DO#x<7yE z?v}4RT9MG_cU+Ir$f%tMnHc=>Y=71&y5F;t z``MNOcPg|g1PtaAqCf%xP{;OF9nCMD5fif{$lZyg{raV-mXnCOa~$>b9Atx75$og5 z`73*SF8BNQQ~F-MZP%QdGqsE#Qy>nzLO$Us^E~!uo#hWOuWUSbdokPF-wo;3h!u%T zkY!}j<%v5uXj(`~3 z@LL$*N9FlQ+E^L4aZ89To{gMS%<6aG=8oejEc3$_nk49cV_k-9dF&a3 zL*%?{KRs3_u1LW@J$4<_bQSHSyF)tuGWKMR$5=_^{#=_Wh~J@&6!;br^81fRu}zJKngzpS&Z{%!P#P-#bQ}=n7f_R_X zJG%lk7F0R$E~$0_{c{#Qj$Eq<^O~b3GL@w6QFNX_;?1!fxi^mMgcnN<4H;Ju#OdIa zz5!PvrERgikSFqKS^kU1~RM>^jZ7P0JXZcKKIn~{gv4Hgy_Kho0pbd6VYk3@KM%zN8vo&C`OUIgr59eTzb$Nn5* zt70KZ9oo5SKOM~wLC)>Djz%EjbaBH9C@{s!3t!C!h0n#YHQAl#t%!)E0$_cmSg*{z zYh0>~o4ERQH7bw<70>}oP0o*6sck>t!_WS#b0r@Brg52iUht+r*76SV3_sX3VJW@a ze&E~U7<3|EwCMu=wG*VODtJa6$CAn@5eX~5_TcL=&TrK7YM~o#*;Zq@A5s5CHjr9V zEvzZmF*)Py(3g z+~ws-&yup0$Qox7wo5B3WsCHO(7fDnOw&s2c5r3dxe|Z9?aE1s6simwG@F~9McmI# zsklsNJ4hwg?Jyd=@G-V685S`_SZI?c1$BuZFi*2L-^2S${D_a?Yh4l7OXTbz;&kHQ ze}HSI_!;BI@r?jI1+Z^zV?Oxnw5wqF1a4MU#jDIHKV6T+0_LS9Qw4mufWLrG(3N7_ zMLuZP1C#ww?TL>^v*V!gyZPMuHmtD4E(IpS5YYG+n{6KeLYF;tdsAz$NHo*I-R!X( z3p1vfile+s-PETL0z>?wAi=(XxCvE%zk@$DP#n&Sb3-EpbMD>T55)F_*TPXT{* z9CVE@Xf)^g0#iJE>sh^PmF7`d!Ak`y`GM$z8a|#Q_fEq?VkMvK%x}BxmaG@A+c(1@ zct_6?pOByO!$|36u$PloWqY$z0%^)#!`GboCCaRvE1Q*TM+&iZc2=4G z(a^POH29uUWRa3qhHk8Ha(t5FP`VszFC-taFt=YS%i!cYl@K$i?Wzd-uivvh znrpa~TBenrkUdhmF6%*bFXH+OUtYB_xUR^Kvo?i+g?B_1_KwaPO_lXM{U8Y#PeopI zyU|A3``fvF-#nO1I-1Ob+|SQ0(qI6*Nq7Cj`W+uBC3Gjy{72fnH%`A&H57cmiH(V& z5Xw^CUEPQGv$7TekN5|UYIAwGYe85bCa9cwM3tz=^O{%b=_FU;K%|KKmuG1|fBqC5 zRyLMiy8V7>QpSF_H1<1@{t5#CVMh#z%-93pyx(&0#sRk^5a9kBprX0A*cvbUD$qp} zaIXd3toeOz#6%TOIQ*7wH!X{M!SFWvm8jZgNzh|e1LS@tqduSsfP4rnZ6*u4{R*xp z2M7rX8R>IH)qDNVNQ%2YQepy2z85QniORS*Apu`dK^%#x%bovcgO#ho5&B&cEbDr# z?VfxI{s@+mWmDcb7{t=G_L!7uzL0u?-fC8{R-7`>Hxw)RM!m#KY)j0oo%rAsJyvys z(W~Hu^V2G6{(Pob`t@p(KAwz<^=w0QAHGKAb`ylp7Slg4Yn(C7{SgoZzb)6?mr*aC zo7O1NtPj&d_QABBZ9*lwOCI=_K>R>T<<~dJiiM9)DlDyxecBhBq_Wo0>l6v`Y)(nB zS>|X)^Wvj`c&N?Or1IK4;V})e$UfXZNss%Y|T7x#~`O80&~Ig_uXC3d1uoX3Ft5( z(Pvcu#>Q;B^MW)5mvM>6h~emz7`BOU2hUBc@LOybw`kKx(;C76xZDZ zA-y&z4rXL2eO5ccB{N$MEJGIsRW!0X9BtsOqx21O@jmeWNWS@=$EFWmUfvC`cP{lq zjJg0LG;xU2TLw$Z0Z~gEtEJl8<-QGEl$L=o5(*+){#-M)kBjWhcAkhRm+_dFN!5ot zE@s4@-hJ8qe0Z7UeI(q0al`Q%8hICZGY?)Hn2HG(oVM%Pbn2>H!x>&qj*R39|HAvHFlGM@ z^i11OMJ9D(7CpE7wuzu1@f*xbP%E_&yeiUw+Z(@&zU%{;i|_vT_#yoo>0B2O*GHxa z@i|@Ml#$!0Rd~X-4+uE_HP>R>Ry9v=}*QzK)D*#~w^i^zWC7;hurg#h>URQe8$G z&aZ5wwHB>904Nd43tgOgQF$wBX^9hMKOWu8^!z%r`wZBe>wZzmQL)3w z^vL;b5G%79Bwv`*+}C$BgeQ_x$hjfEE~sf+?xU1`&H3j8!7EWW_%MwxxG$iH>xby& zJHhUCtTpSuxOfObUS4IG?p=85HiNTOFZ6cbs}d&DtUZtOa$WC%COWAhU=9+YwzHC| zE`#bz2rj3?5OAJrJ`As5Sy^HCC3-jFeh{w4pVFqY(QIk;_NpcO1+RSe8FfVynP){G zj^sy6Qw`(B7n2nwhWyP|&JuKBXV|LZTMLf=w1#!fnV3JP*k72}IX%VM4&{ud@(sYk zKacO*`1+MzSbz7}T}PgPkD0l@ukTk#9}{fHLv>)l{c}44H&iB^;XPu^)dudrKRd@7_6qIFc{6k+-h%QLrqOhN?yS-q&}%2JN6|bW64&*4UOMo z>)J_;=x3CP30RPF2%?`9F9>YTsi?@*NzcnKgceZs(f$I-BO>!=TK@0F2mb9)tE)hJ zepNWKY}g%PksmUY+$hA%NDztr_iK>-@$la;J6^y1`9k@L?Sf%RD)lr=l^D&Gwvg*8 zVL&l})=%hLVJ^yratqUyOb3b~l?{;V{LHjlP+xC-UxoJpJ*gSUUYBBrquOOXGr% zQ|`MZv*7<~XhJcwCpA4<*U?uVG~qg+NXch}0e znVVyrOm^p2_;_fxYfI;XGDGZXjgFO-plH@trSuw)3))MuK#ZNwF3fqaJ~1xdrnndpPR<-QF=U#mF;zKIA3w{(LQ11bHS)yBi&}l^vp~; z>X&%H^XDlk$-2?Dy7KbU&aMvCPWI?PYNF;_14&<7wYI@MkfwcPvt^Z&`|zOlps47T zl!FQ$YS@e1Qi^09E$v_7hpb717boNl>P)seZ}ioa2#LvT%ou(fk(qhl7g@!ZS8$<< ziG8K*FQ&k<$t!rp%F@#-8!!3h)QM~C=aW)g+Sl&p!}@woNM)--Kg+xJ;d2EvqW`r9 zIPvXkXAcJD-)mR!iGfw;uiNGd9lZQV`h25}5)DItfR+ zE1$ZyxD#0zLOTsOnERyn_AF6a?6Cm=`+Z@Q? zz*`$R(+{7po#k#`9_+_RzWHYfau`tl%kmBVGfVbUQozbic#$xWry`BX7#~u;I`q-O zP|tP!cvL#Op)jkaA*;v!dy9^Ru@#@;E}o?zUVvd2iHdrxw$@x&(rZ{|V-Yoby%YnF z4l1DZiBUCGnT5FDlq*{a1u$~JjBiH)yd)R}F(ndpz?K=Q%Ia!Gc~MNR4vGU6EeQgg z({fX;PXpU0pxpun&UPrj8;ioJUlV+?vx1dZZQ6Uh4bWl8BzCQ=_Lzn{!{*A`|Fd0S zD5*B~Z*Lz<>NTqObO(UoeisLql9$XF{^dV2{;blt=JS56%B@a%9tp9^0BdT zYahsP_u59tfHCc^ConkKt z6Oz#U6W?-pkGoTU^Z%`&k9-T$(p6V3Qj3;l=(m?D@tT;Q-rimL07DB9OVOE^ErR~& zuQ4+d9sslzm8_4p2R2T?YXrW%SBs0A1X_$LQgjx`$)8E_%>4PYzIiA`?^#ph@tS9n zoLQe~D>2a!`R?BfUWN=W+dI2XunkN7H1V-1`V#H*L9^^jW`6>9nRK=oRE2kPsz54s z@ky+7V$@GkVjl1#$J+V%u-DYRKQj==d5`I=4>VX@RXw0e=^Q$38}?p{1#JQIBgc7) zYYb2E#ApwDh6lM|W)j*-g%wVJ#uEiJcVH8-LCihtf9ifo6Ue7EB+G)f?O#{-tl3Z$ zk2t!0GcKN3xgsws_eG#YxV@u`d07H%VqzkeNL-B3l>T%23Z-5X7~_rE-~T%cjPLdY z&u`^zezh8KCgOa}2&M;GHtpBB1ax~*0S)Ua>Fht0BZf6u>UbDms7aY(9WC@@E*I4Z zpuywReso3y@V-0K)V)l0SOi_jluo1luiuDa9c}~Ny7IKP>IJIq&;N5E z@MtYScS-@Y421yoKe-X4s%|$dO#e;>OqTy!U-bXzcz*PM-Io17+QsX>aDH$-bSHfD z437$FsQKv?5^n1bzh=>E;u4@q)jJ(oldpQs2YbzTb)ykL{hT5N3*#MI^v;6m(f02c zW>qFts;O#NH6|*d;|KlAnSp_UKeJl2#N>;nG=w4JfiR4c`3-hCvwL}+J>99j4ed^MR>Ud{qNgin9 zeBbP>*vpu2^ui6Yl*3_zW@BEsfLMA@->f*xW2+mcD(luy0r`@Vq!B zrEz+i{B@wa&|+TMTGz*9(r1*0ugdd_ih1YXMfV=$HtD+1T1%-s{$nTSmu2 zX=VDenbqfJXI#Dsy6aS&nSZ43>>f=IH!YY<(AMBc#7mTI@@PLgI5^0u8Y@XjYc_#q z_x$WNni1qzQCCBKE2?+6^R(Lsf!G?-ZviD!SvB+A^_J8HRiX{>&`@r(Ao2Hy3uVG| ztP{u3+`$B4huxT?W<*0(4hHZ%C3#BZTERRwu-D`ndM#8@@EMoi$)6wJj=SpK z2x4{%&sCY4n*zX+jy>`VL6SDshND2z;NtbIUHHQb@pfU5-#IEOwnhvl<+Y6D-%f#D zvX#|UGt)B%i%qN$#-xzthVxBrFy)VD?(SMu+ssSCp5U4o?~7k_2uGT2 zzOe8n^>2I^47*ZtBN%?gZGLkD_d6fbuBvi%P6928qOhT`a>~`!%xwRBr(GgKT3$-P zcip~uRs#PpGK@^b(2)ho(2U$YOV$5a;^VmRg+LqYt5V{4+k8f2=G}3VkLle$Irr9L zJgM8>EO4{p*_xw3!kbbJK{sZYoL;q2&xuANR|j>k>dt#^GbRebO={ESk^A zgb{5sW8&4N-V?h%f^LMw>re5z+U14Fo)Jg=jN$hl-+r7afxKV4VjUdB!*IH6@VJ6p zA5%e)S!xAg#q8ykT9wut5k*N%{e4&g8ZOA2o`mg=8h(zG&^xq9p}6FP(v|o$6iErj zEwBbvsm#(l)Mvnvz&y2(_A`dm?d}Z_78#zxYc+JgASl;}@I63WHuItS!@>`g*&D9U z+?FiFUa+zCtC!5cY^)4@DS4t?OF8%mB8M0K)M?bw(I2pCl(XF4;QPesxT_D5qR6>@%dpn{+XeOBH1a%dNgb(98H;-^eHxQJ&)OZ?CWK?>la;e-AYu zMs3N0D(4O+o0=af=&R3F-%}|osu5EXIi2h$B6qp@S@`*1vlw-6His`3C>0E)2<_}` zl}wp&VAXxO$d-Ggpr%Fp=ooVZ!~e3XYEPXll2Wi~Dt;_p!~=2MS{Q~0(6Vr?-Um~` z*Bjkpd}Q#_5r_jm;6-ZVKSASfDBphgCXc1$<-joWz_6g-P0XdPUDG-3MBYnwCwu4D zH}(Oo{4NL$Ls1dnhoy^;O-d~6p^KFF`7+8bs$P*Ag|PebMD>!3vtwa#F>jr(#(Q^H zaZgiCvQ~p1mat1$BaOTrRjsTi93imV%rl|7^)W4kQh-*FxxKeDv3*5h^kG12jpMyH z!Q9T#kh-ecGuszJOm8?vB0_9|x0a5pr7bPLe~ehgv-9xr6x0O;3Z0IReXq8qr|(eC z{6Ng6-DG^0Lu55IIv3Szssi>ER#zzb3w=L3A#TfOuvdUF*QLC{%V;j!;l$_kvCykEB5g#rS-u<0W-7D9UAz; zDxXOvBxD;1hN2lcSA*T@+noB|*CJ3gZqk9D85v+)LQw6tfZz_0tnrAhTa{~Yahe^_ z1mlAbwtY=qJveMl0-s&4NldVXljWuRd{(cktE-2=Ogy_@UP)0l{!o~JlaO#jf4{Wd zVoh9pO3`=X&X3=}g>0U<*9y#Z6Hux25aCBefX#N(*K;M~IBN{Dw4@!ep(G6QDT}jz z_BZwftu+*smDkdJyz-ft8D%3Vgndzi+dU4m)s=q!%r9UzA56JgAW0h)RMylRGAYNK z8L_B znAxDK(^X!6KEL0M$|na-V0uPyK)-yvbu?;tt01PY{Li0<&4^0-o$|tg^Z7y+01FR? zjL+j|W23Q@)JiX%1k^eTwntUxyK$NY9Ro>MzqItF^%6s@>*+P(Lw}r?mzTP_s=km{ z(9QBc{rM=wWFN3Tu_=u27KS-{mikAg`DsdVwi-+e@{dn$>ljW>s0h9hAlIb=tgR0O z-@H*1F1;@z5b?Y?aAWNF_?gnLvSf6@-Npr$8O;q$&(83SPYfprZ}}bh9XDS=bm}a5 zLI)}HQ}~*=hla*UpK8h3fFq+9El>DtV`AeJ4&b5j9Sb#jy4g?J1YEb@o*%E^#<6JE zS;DH*mJ*+KqfqlPGSdl%>oHnhe*^m@Ojcu{x30VESp(ondbjjS%c}(3c4J!094Q6N z2Ee_kBw1{54evE7(nKyN!*RD*G)nEJ@JC|)ltz9d6R}-=Pi~d;pJ6+GC}Aoso4fxrll4j@a!iSp$o+56l%HonR)J#Q2E-!5w({QmvVmhC_vn#nouzr;O^j*+#@^$>X*OJwgnGA@Q^qC@ni5IToRr7$BS(IB-(gROB)CG)WcgOX)sRmP2~|$ zXeX+ZQ|2BTm~g_8rG~kYRCBc+72zodvlj%?9TeErwwiOZL4iS^Y_jM$**+TB(mW<# zs)50Z;v?m?z2?9(gStH!ma0G}5T2_hTNpBMZMr?3nAfp0e?jr&bMDtg%U4q0Rp@Bn z1lh6Gc4*~7}zHyJ$uMqceSc&@Zv z6vU$}|A9LVH=R2g%@zQ-6;Q? z8521aOAz)Z=f~h1lJyNGyUEhukMtoBrm0u=o8i0pOiWdzf<71khlVUJE{#5QPj4R! z>nn^Wn6>}hpbFkWy5J%m8tQdF>OK&N-Duk}1u;0UIcY{-Qvj0mKO-((wY3FzQNI{j znWx1iTRZyzKYC4KiHKAPe>YWKo_;+Or7;8zh?5krN(~1iv=hVsrcSHb5T&ldeR^tY z0>xjJ$&`z%n}7n9T9iT1;kpMzJdzxw;?>;CDO0sS3W_RV6~q0#qrb4ahQ_b4n636e zoP2+B7OB;b1%Nkz?u1N$Xg-~7f;PjLm{t3Ja4T(BxPh9znwtG;HaoPW>C9GIOw1;H zlPeWxa(!#PP^aSKt&!v8yoX1vfXZ=zFB_1+W9RFuHnbDnoySzc-f-&qQO(RsCe$@C zHX`Ek&mSXozR0X=er3##aoheZE-<2WM74uz24H_OKLh$Xe|@cLTyPM<3(LcxbL^qGL!e%ZTV6{hUyP#IS!6(VXOahsizGgbd2t`O(o=aS2IDGH*KE4xymZbI@MB z81sxg&?sMCi7f>ZpB63Xwdf6%)lyb|2;hH=c&s>jNU3-JMs*`rI=j5C+;KijN(%dg zt}Bd{oppk_G5yMHVp2s=uvQ5L-_qc*OC1LvAErO9`NzRJyZh+j#+Ss^OEsQERx$sO z=f8&V6_nn>Kc0!=ViN=}<|OEr8*NNBBQtU{@}R}17gxFL-$7B#%M0^HX=M9`x2ozK zBNm4~x)<;ds;hI#y-=RF%Fj2-mD_TxDK8Eo5b?d|QB?)N49=?la;JbgR=O9wzZ)xa zj%<2p4_aJ&esY#e_a0r;{q`mihx9`vsiCROT_18WCtm3GPk;PSYQ4i9&TyEvRtD5V zQAeZ39qx$nMQWIi+pV>2KKZ$2@zX?|$?@@*gx_~9ttdSay%gHK%1`Z8R8>!}&8+d0 zZeo)RIK;!rF@dKHaye;-ne5HJw-f676wkK{>Nj&X!B|YLT|XHYmk>o4ii4B%j6AR2 zU5DFpQX4dd`L?!gTd)_rXTz~}a=w5m_NNp=nGRK2CIsSxT(}ouvb@FujxVmpa6Hh> zOtbiScoUM`7?%^0w4J5t<-$RkIrTRWq)e+QuGeHW0-xC`sklk>TVdg^XU`H?;>gm5 zP0Cf6=m8B)&FZSEx9XkI_51tjBn#A78NnjhT4;Sbe#Cw~*Y9mpF=?iRPJz;Rh18tG;qBZte<@P&0Hba9xC%X47ZW)1Sq$2sGH0$THyKV26JT z$;sh)#nhwTvjK+I@AVET)Zco?a<(>2t~eH~{oEEuimaxL#lxNp`yvLdrV{YP_sOnLsf0v#)+=S{5FA3sul2d{FqoW&2bt7QCYYtz{`%8k0<@NIQJG*EBy@am zm&d1n1KCc+HjOFqYXNC8&;ugf?G?H{8EF$o&9@3a6kZm-WJUwX1#d^b^-v_;$4O@| zHu+xS!IR0`-aj1yQ~O+R%0xN7`yY_H!8Sdo(lP;Y;I zA;rtXy9rL*GM0U`odnApfq?j4**FB7ZOgsWeP=RoTo`t~KM?jkI@io3ON#ypqj`Mn z738>GU7v7$LoS80f3&wYr><{cV1G0>ZK2Ud#zj@tRc&(p)0nI6jO@?#!0BnQGLVxa z9zacsPRGH|4~hyyt*ui_?3^M^!Rn)`uU#a^7O>P<&3|lV@}L`g8b_}9?>b~fiF zfgHD7)oR@lUKdQ65myPTyhkkXfr1EVo{9AaQE#aYI7kI%G#MkF?-<^JGt!-_0z-Jj zi_h(bjPxB>$#0wdwjw~n+jKm;_X4|Ua1hJ}pAMM}kFThjS@j!j|BQc<#mLNP@V=U4 zeFr-;K#=SY*^u)M{et)z?GvJY>pMM7(dHX{8aiS&m_+4v=ZP33_Y}A^neyX9UTlGv z2C|Kh9N+3@pIMp>g3(?BYrxHB$#Y}X1N2$-`>T(+?*|M@!8LyoD+w4ZMNsrl75m(! zT*WYD*EAY;$H~n`awSMdXT>G+J6epM5`hT`yf;v}2fTNA{_UWz?LpEKaQbHQU(RwcFsL5|-KQZX0t`?WX@4nT`?NFfABJK4Ch`%C5 zF3vkE%*l6m@ek(e&FM}l#Sz1_CHXD=wkHdyeE$fvz+3R9xXw)f z5d+z@3WMReaO6ZqiAFuXw+l-yvYcyYg7hgE>az^k1B3KN4paq-Pw+4z`zH#1 zORYH8TK;^E=)Fpt^S}=-V3^1$fIRYx@+f=G)=KS+ ziFu4^A2LXbv~ZYhx#VMr;JZlpsRL52n?L1__CkZu8`kr-k| zN=l@Wh5-?Tp@#1IAMg8qzW@DvYrWt1x@0Y8t>G8v?0xn=dtcXea)QMgd@X|qi>q_} ziH!NEV>(Az%G#c!?)2m7TZf|0R({vnm1!MDH#R<0Qo{GfjfiL5gz`N(``A*vcyiub z$t>#;mCj_a6}8iU5+pqu_);!Un|QY`7C0!u^ZUvY8GgK+thu}Ap~ z5Z1&VykHVgluYThlNqn$I5QZ|{lTg}bOG8LcC@J65)WE4mBUM~JB8oTqIo9ecj-~+ zg%H{q7md}};M)i+$St+f(E^UfsuhssfEDi6yWc;#bH{A?lPQZ9c=FiNj|6@M_4sxC!3h z-ySLub-l~}M{RAbfFOUHO*oAA87Z>;0c!+8bJ?x&`Fo7xlyREoz(Bmr#l4tLYPAmT zc;-R}(!Z+pf38FRlbp)D0)rvDe~4S(ttOwQez@Y=a!%6+6k+)1jgrRBc)Pj~yT4LNw;zi;+$<(3Nv zBhZy-&Hrog|9l}&ihg~+DiqZ7U$ymrc!iJk&!0gei~g_P%fEG1{;L`Aztd>>-+k-9 zIu`%F3R{rN6$-lq#U(z}M){glvRha+!)_)Ds|9Gu^ zttSg?a%cg?1P?lVO>DQZ)(7&K7_%Q!Ctv zA9SdhdTxuk38I_cEsUdc>22Qf+dMiYHx)0Tp=%uWfiLTWL$q)I$^iBBa7I!ZU}5Ca zu}`?$G+$-mN)enw1Fq|}R%iXP7ik;oL%niw?)tTuw92H&qw{^8b7VIzB8G}&<@;FH z_ZjZKKkVI3-|Km8srMb)viTWYBhh#%4%D<1%bPT<){B-=QdfeJ3OcDN8Cmb{r1V6r zkpa?+f|{BjOYnS%99K*_?vy|GE9enM{~nzo#h!x?w1zr=8iOThIHBGwID_ z*B%40URWe;_uFgYsp>GJNy30gRt9Gl5fR?NPs5wVH8qV@Rk?LyQZg<`)$#@@Y)1W? zvpr)jfL6dLh7VbDDgi|TknMvsbLlQ!^Oi3mX$tR66T*kLK-*_-m;eF-c7HDos1f4P z(R`AO{uDpIEo?*Z3Jc;5AKNVFFXx|B`5gK4DHtFhiVcoQm7$gi#j-95{4dbgoKJA% z)D+4KiZskdr;TSnI=;JsE1$rDg{zW~?j!5^l2xvS#M}jfT?;E#TCG)~xX$kK3b(vz ztx;oM_Y8uIuEn~ZYmr|i*5ur@86Q0{x9Eh_*;Oy_h_pnG=a(EWIE-d{|vkwo`laueJl~HMtpbtBKzb-4A z?Z0+twZL0xc2V;AXI~cc&)iG>WBhAM9dB?U(GBb}oK^5by7_gftinQXW!rl>f{>5` zQz)TF4gNVkFc>WPyanO*Kh1Md5z)r-@@r(fS#ydC>^6PJfe_`_NaMGOqTiTDg6Q8x z{Ya$TzW0|E`B0oIyoxxViP|fHY&oz)Eb%!*y6GN6SlbZ*_JU5?1w2!@Y4>0FfVqI-Fry9vTC@(K-zBF-X z$(;G<>Af>D`=o2-MU86KzhkVVWUlAw?sK@X{rNX;=+vmb_&1l+W3paVwG*;@28_jh z;rwoFxra~PJ-+fkb>MCu_Qfi$t*QR@!8YC5yT6>vA~y+|m!UX37QsA|n}oD_3(xa(emf+W z+RS+{otspN)dh={wmTxlA|E<1DnU`j4K;51KVo-rVv*w&RHURDX%3%*#oE0}H8M{` z=J~C|XI?)tMumGMU+bPVb(EPdt|>Fuojhgf631isxqX=ZYg1-tx!-boFCHUKMYUsP z#jC74HEZS(i6;(`yIq}mFNhlQKKbTv+mBW>25DF@#w1eoLQP+N8Xp(-}V|tq8 z9$8_<@yHWe3xiuGz%qn9oS)-*ez`b*?x0A_kbP61wBIwMi6dPEQF?y7TU%>>{8Gucc7GoQrf+K~Yi0L&1tyzA%nk?Lww?FIOWq&U76H7Ex z7B${Azf-J@qe7v76LOtZ<<&+BT&0kGXpv8j*E0W;kKxh>Uf2leWe&)ZPcgXiUQ2b#uV-qjsSu0U z4}39?e9Z|qXGze-U=s5TDQZf8MQ~}o6v{@*gNJL9CM~ZL$xVV|`8P61YL(@55BKuR zoPN~uhPFDQNQUS;tS3Ku-m9I-4N^nb5>!hO=4T+WP@5I(kYBUoajy(su|E5RxH42> z3?+1%oII|peZTOD2{oLaq2}Upff>sB)cmTp_TIwTbHTvU+uXsEhL130#hk`(ScpY)1k4ob8;WN04IS;B80tp} zHH-}|3i*{rvk#rnPuGVTNGpgq5awketx~)5^b~Aby`hL|lYI{;L>)!J@s{a?AM!-1 zlLXU2ObSAA<_&7ps%^D_h)AiF*9t#SZOEl+(VE3E$;g1n+1kZ|)P+nQWHCMzUJeK3 zFIzYvQF|u2^WIReJ#qE%X(0n&oKsz2MQA*Ze;;b75}DV-`7*c=^Joh$_GZZ)y;MW| z7>pXs_n{{u4re%fLbYY_ih%ls_6>w*mGLu(BkWm+qWeVt3u>Dxqkkn!z6NAgX;oNq znW!Q?UmUav!xL%Cb}xpAG7KJi3>9)Yj$*~v^$lfoL&a7kof`S#>^r#-*J1GT41GgM z*}Y>Q4cod2HI_-;1!NB|K-yY*PLA=vTYM9es?Oy@5hn9jLZ&jrG^IFAb?OaO21DG@ zx(*8&MlcMSgI6;0RjkD8)*eL(>4t`~;+gn{o=2|3t zLKqaJyTq=>fIlOAg(&Ql%ptY=h)Igz>z}2Db%O&vfMyG-K;}_xp`B4A?Q~X1H7oKi zXScT|qFls}x>L)L@`o*$h#q>zCh$_=(sF{{&?mRoU?Jne!kHqS4^SjFk(aVkBIid~ zq*780;J>uWKZ*R1InzzHQ*B>G=Z@C~K_nl*Uq&^LqK5jXhl;^v4S>`2qK=i;VQ(e9 zq5h?9c^Q>?GkCOSD3a=K1cM1&IqFLK*NH2>xS2T<_PMmYgRULeg^QRU3Iz)~IN(cP z`_Fw)FIiu144{S{2nM(cK?1d zSv={DXg+lrr{`W#+KzK67q!(CaSW8JD~lCPZ`-Zn!LRG6PZF*`B&0r_z-AT?WIf2a!dt{bi_Xi0{E&6#wn2p_9;RAapq;*<#e=1Ti@OagA!u8|+ z7bby}J@8pvVtS1~_~LhD;1_cZnYCC$mcyNq7AW87)jdWD)q@)Sr=Zi50Uxwx*agY6sj9<5YyQjQ4ZlK^QIPu{Zp;OQM zo_wcuw^QDKhV|78dtj$yif3MG6RAT^I8#9`mB7K6(-bClAzzhEOUEBoZeoP_Q29c& z)A|EZT$OFP8e1l=XFnz;U|-4^wsN~Kz3P!lZC>@OW5~l#NSAYQ+Altm4iA2O+_S_q zLhwOWVi2_3wkHaYt)Y<~X2ak@h&(U#;wzt@oE*5eTrEDSJ8w6vysEbHHSn=|VAR^a zPILCW(Yz&10o!jucNWgT10iRk+W%Fv{>-JUVqX1Zzf?BzB%s_>bg=Ku(Sd_t)A3r$ zbcSxpduYX{E=_v*YvO&^?Au<>K^I5lc=K#9Wzkl7m)EY@&0p3QelEg>P|wmJy@tI+ zL@-s9DXXGEU(79^+h^reD!I5Ux&~uD9^(EpX+JG`enJ*c=s(T~*@CI5#Nh4MWAG&2 zA{drJ)?r@dqrk;-4W0k^!(UFB)*3O&P>+2yey{3f`xkbFitWeR!^MS}`Tkg?g^g0M8L%>UEUjqa1RwrIjr;`I=$`l)uM$f2_ z7m|L_@ikMcL?y|@r6JCw&|Zj+dO6T(_r@9<&d)XLoJ2i!_-~JgFXVZG49yiO#~fXO zfYO)j`h0`Ll!}gSP5P;!(O(M-neP*rq};{5Q)+51ruLvk0Q%TBz~k{5?&0bE#Y^<1 zNow*tEzwLymuuLUmMzU%u622NbXsnIRVzVT9)z2TJX#|Z32FulL^+FqUp^pY_&E5G zUzwmtn&=r(-cU>%J>!fIbN&oAR`9`h31YlF1K*Yx)QPKGTRYvB0lnXyFK4};2n{Bs zXG1xC-rLvkeLVb3ql;=`e}U1R?H#PFEOkW$N>n*_+d))Tcw@bK8RwB)d+?H3lOzhKxkwI zKOm>c4F)6KG)v;~7W@tZpRyjvbK=`&j6>O_J{2`I>=t($=qlw%?rX_b-Id|5%q{w| zHB(i4bK1yM_bN=m)89`)_haEV>$adOYU)>KhJzv^i-(`zX-2ExzBv@|fm?!8iS=i> ziP-u^c*b;ZrI`k!$lb3qy-BQ5jWt}=Mtx-)AJ(CIO7Yh{J-uyfj6EH+a>w7qXw_F$ zXNq`YQ=umJyCZdOy!@B%q_$yQfH9^oM-H2tD^Sv{Hev9ndTB=x3{A6#VEH%)ZljDC z$D=JpVqE~wGWlalCw2=K84o)L#(_xWi{O>mZqRTFT^ePiPNbv%H9s`DkLhlio-!Fb z^26=+wQN?c|1q+c{T!oMQm$Vz7q}AXkZp7@*{)WJQm!Z~OX^MfD3*|IzCF6g8_#m= zH~xzIO(3bt?$^R&;e7VH)r7SvEVAC6y+3|eTF$#VZ=v5STl(#-cYRDIO8NDr$t?J4 zm8np{Z_^$iw>4ba16ZzhM?8;YcYj(Zse12s6qZgT?%+oLJ$VRU+BDfhEpOfX zPHkcgo4122gQzH}HWoKa3`zwpRFsv!gX{n^yW4rja^bP+WIf2Ry74|X*4&{XaQHP1 zq;*d0EVVAt!iizV_fPUHr@(+sS@!OHp`A1&0|T;fV{U zlM}2{j{!jfzy+pKQtnETMtql%*F%DM{n0 z%J37PzV3+@1VVQ=zG)i?$r*GYCvGyXE!16Yty@NKlo8`--7e;{%{1- z;7tIP?zD9}@JZ62Ud1d-jRINKxw({}C)Gw+STBv!Ru#`iFt3_H*d4k=wlD5*P!LSK znb?bLig$R*%gX?=bJ!P|tFXK|S#kA`bc5>1MC}GM0io;04whHwBr8v3Kz%k zeFEr{_HiDjZ^f#``cIXh=Z4_mimiBuVu{~qxYV5DhFMNae9#c^>&yA1IX+JMCxL60u zpqXje(|hUD+*tQ>WIYFhX6m1nRF*QG-%mY#z~CdTM=faDwz6k;w|`9E#bvX2QHfh4 zEj>FSA@luEN|4avuKhezkXIzg(Q-QL#pdafr(odK0gczQ(Yayw%ded+7yF^5r2&I1 zhZch)$>ZZ{$>ndOI-iL}_FHYCnraG*R}W7pBTm2dqim+WkDVQE-02(4r=cM3LL87m zq0mhfJU@OWjZx}%$4)*(s5Yk=Ah9DN9*T*LN{NeeadKK918+o}*5ht6VGiO~nR;ze zBxprcES0CBrB-vgY3dIfuw?{`ATXL#aVOme+WLx~P5I?|f&S-(88pfsAOa}=@P>Yj z&&bY9d;gYME1@9iTXiG+;h*%l<-fA75U?F$_3mn%Rm6&mh)AZfxRe$_zO|@+v57tO z{gbkCU*JO0pcMY;46B!h^KNkE3(=?6eVN9T9($_I7k;I>#a~+(uhX4KwLj@=cAd-4 znsF*ACBU=U8TqX$4{-k@A-lwhkraiGgbM~-UH;zG8N2>J(1--pMEpYHt zSJ@y^(m1OH9flVt#uabVc23BokCm9MP zvknxXVkKZ@++fUqW%FY8yh(u#Dk2i#)k%5M+4va#_*84ENwLcV8f9^~a+KAgr-SGD z@}#|&`OKqz1r+^=;}yy3yDYMz#{zmMguI=XhK`!aP)T}Qc@QOeowQ5l&-+L6mD8Oe zIp4M$$rVn69YzaO3x9buLgY%urd!?u;CLr7%TqhM#|9?;m-{?-oZolaYo&>`&&+(v z&zAwGEeKyyL&{9AhAx|s#Y--RzDGsqg-EiEz1_o|-NUC&PK*-%eZz|QE}I*D{rnZ? zjrW3wDaZr`3l(nMpbNtEt!{t&R@V3Cm2^M6V{(VGP?ra8tg7wkC~sl$D1F&qrBFXx z#_wx;Z${^6rsRhYk=<_?A0{l7uTPBVCa!K06A@Zh8J#pX(hTGjQY@FdJmb>L{WEk!z^5%ISxq?fP%Bk_BK1R-Ze>?;~6Qp&zhGji)EUA z`nzmZ^Xl|9oR5;J;o)w;mjo(cK<)`zY01OSGd%M+e1MUIQ?cuY+?sJz*ZBbb zb09}XV@qAxu_1gYv+ar4=!7NGUjix>@M~!j_?4!LgOi7@?l1EfY#PCn8bQ)nt+TTL z&G$G?a{wUbl}MT2s!Dl_I3#6OyzGKjm=~tZNQuA6p|F8CEBEw^Wc{734dodjq+Xbd+L1DIg&5*Z{Lh%lFjb4=jP^^MfjNu z?tTU5DLmu4ovU~2PqtuAk-+}pSE*bb^Yk)4vlynC-1W#X&R#>zB*K_2 zr9I0eG(R5XY!9KWpHK1J#4}eH9ZvmERqRG;H{P$SOnjOB(vHJUnk6BJEg|XDCp4ei za&)eOy}HHpbBoGt*d1d^2R~$hud*b?2nB@*B|!+4r@^dxqlBdY z8`Z*TZ?BavJbTqK9`998GJB488Zx;JWTqxZo{RimK3-U?XPi}dNQjHQH+EEh8E`f4 z(b#9y+{L;EgE)?{2&anJK|mN0vhZE3D{gB$9+X0;bEWhwJ=au1w-CCUAp@UYe&rN# z81(ku%GRE0|Ka>Ktj9qs`*`oj5-_!>`7P!7Ek8RFB=$_!y0=H$D7CZS3cNT6@aXAe zQYa|y0FoQz=XS%(HZ}}~nLCcTxU}^5-pMjGHN*|mR}2hibZO}#z~`|+a!OKC2QiDh zOmhFigHB?>ZxbgAqdh&*2!XN7Y#pPwN+h~~;y_5>0$GW=bXM?h{U))^|XZ;2S1Rg)8a?s3r+e>wJ zv?1wviVIQCx*ytU?Q(pPqIh-`OD|%945hniWF;josdp82>ju07hz_hNv*j&FGrKNY zwpb!P{KhjSZoT7#ykVBQ&nk3`Zo@GZyX>*A!NCb#<+CjLM9|02)uqYbH#on2CYVI^ zdyj@c&mgtMQ3X)mDrJL$*Skck3Ty@|m>?mpr$0|lPXJz53<{-Q`hD$Mq%M*9?5zDj zcEH8jZ`t6p#=N{217EH|!sb*UkY_F~e|UI(mJO*rwg5vIuZVvX9v0F0hIUGkMQE%I zU_jN>G%hwLSVVs4t?-*!J3D`3KnZ!gfLwG4J0leD(h@_+Uq3JtAcH(G7_ZWE$dnE6 z-|Hgb<8t18U%bz$WaoFn(sCL2`&T&64~OB54;J*HJ#HR81o3ZXkwG4JL`k0NxS2*&VYfEZDDcQjt#wPTxh+N$?u9Q94PX|cV&?kWU*Y! ziH{dN`1pgxM~_7dLMvtUHK~^ddT|hGqS2+9QBgm2d~BMRcS9|?lK48Ywa=K=jqA8X z)PlXrYA2iBEF*e))B+ZFZbmzmlqe{$-wR&;@`V6zcy8RjR*iTpM`+Y?qSB06>cuT@ zDhB!+!;?c`O$GIX#FLn0;EClKV$71C1^J|PZ`Y1q?jLOPbKa3e)aX?&OytB6?$YoZ zQ_BQ)R4~oNGfCd_=rd}PHHsx60XXJ$mKV=v!&LO)GC5?$S0*Y^cq1yz@W9V?u$UV&ql&M_es|;gv*O=*H^(kJ@<0OXm8vY z(>C;td;~>w*?A4deh4Bub!m!m?w95nlF@o?Kk%NjCb`O}OxEWdakZ5Fxiw1XolOZt zN#qIcT{?B_DApEo1~tG;53*v;=O%6)Mp}-r{FM?Cw=I~u*`AfD9pD% zuaxd>{yc`%-wVFA1L>}0@VP=*L9>T$oK^x%oy*+uCj|i@5Z#mRz21Lq8A8rT@4w!4 zfH;*b*7x81&Pha+2gnrR+UVuw7rT=>(MGe{mi58&*_x=FVPYmJkD{{L2WNurN&!fP z3iQT%(3Jv+@4kJn0DL89XNENu-rimlQsf9vBNjBA3op-~|qGGg+~qG}qq~ayG9j-r~5&$IH$HslNN8xYQSNorFZ|O*}6@4*=YP%-^iVKfHT!EejXT zZ*)BzhGG5`#{yU^u?Gh$1DS+G@f&$$scTO_Vs_#uQLXv(k-v$BCN-1G@J2$_UTAh} zE;&N6+S~ap4&d79n!C6-tEoA8e5i>bf;>Mv7K4h;A>7Mr^mHu@{1tgaaS*)mcPlLr zqr<}%#yZ(KDeFtWupy+Rq~YNwz^}@|U_LDdKq8d{pxD}PA5&IllAbPhe(I@YdS?DZ z<(0ypf5pLo2BE)QBsKK(*CGK#C7J+0-vNgJuwn)V2K+P1E6UFI>MeVFzjwvu9Dk$K z(nwg(mk+Cty*R z<0K;YueuIO0XXCL??Qmu2GPf#H%A(%PP7FX7#NUApMNWp*t?F`uD{arO__|C5F?p} zYiC~&4an361``z)$-GNcNxyqk2tdF!pKnX}p{JKlkBnJdyAb9aTN^Ft*e<$#XY=-7 z6h~4__Mh@6YN!})1)67$um#MT-_iz~%BP0rD$MBb#dq>LcZ-4U?s)%He_x+^D~*{} z^V-U)7WV4zHFIr~N$>KCH16Nura}X>FjW-BGKjTkKd|f(bEzmi&>oFeKVT z1}YeK8i^%mqG=Mkj+o*9!j9%nD2kc%WJM`&c1vk z#p5%*s3l`)Y3ciWB)ocrVuQ^ea2((@|-C!rzK%dcRfrQDBhk-LoK9kqgrJv8u zkIa5?Sn<(2athvae)jC=-Ze7;LtCDb(mcHY<$Sv(dItJj=VvvwHHm`c_f$E&;iBzI zvEP-|Vx!_JD#{98;I=`T@XVP9y9Z@u1qGiNu?k%W6*_#SmFe_50ZQHHUoZ>%UR*6Z zFYLA^Cnx1{?yaq_J$okFf}}ru$t;=b<4{ywd~mRrf91S(&N^`+v40Bh^%oWQI(=-T zcqF033V}!k1Z+O2kwg`UwBrLPy)MlHARU3-_KLlO@b!q4`mLS(NUYq?tg>vE{F=S<@ECch)F8T>5`y0Iv`Jh>O|Tn&gP|IH*1O=T zH`r+eEDqbDmQzzR_>o}<$Ii#jrKM6GOh7`Qx%tI4KR-~vq8ZE)8RLvt)1svhSvM5y z*37bJt95)Fj8u%AuLSjUs$4A8Ay|!n4MLy6-el@6Fm^cn!CK^IsC&pr_jh-0%h#5mkESCStO!UR6xs9GgTY)%U`2GGw=nJbop$^utsIJE z(@)nQv1!8e=hVXEt=Gv)kGi#!7J$Zus_rka02Q?~fPG-^_o1}DZ(ReLtU?pn^-1l^ z${ZBo3T))pO-$0U~Jdjwe{|}9CC76eM?i%rPdc~N@RgQ z@Ogy9Z$dINv*gz6$EVXvl9%mY=AL=zvyv+p>gv(N@|X7~f51>ieH$qO5t|V6xx-)hf_Wc z`t@9zIlfmUHL$g#{wAvB##)8eZGuzIKiKwu*6Srgkq!AW+`PSQ!)(HHOD7z%P;OSC zAywP7w0C$AKo^|yQ8KFnmzLY&^m8)ON~VMlzg+i0ky$XiKn=7YHDhF&U<7T&G}O5 z69S3g2$h0-vGkXsqw8ZG?#c-p==V$1D-UrW;y%ZlRgL*^hfop?nk)d+;|5hiKXc&&VP#A2H9yThcf3rRd|i6O_|2Qz`S4+FOh3E33ar(mg_6z~O)$mzqN( zVvQTwe+{A~xOG4#+x0|2YEp$~K3j}ZJ|F7i1w(rR_lq6O%uw&|*r~X) zWpvu#O+6MK)-%!FSjNczo&<>hulXN{cK3k9JLacHN0dV}Q2GEgMALd@0y~y-!KXk_lP^yX2?E zJfmOHsW$-aos;$rV3$Qi;4!BV9jVjNw8uMCE)mjUb!_XTbKC{{#bW(wfRWbKUD^bU zatD0K@-lV&{hfQFZ#kgQ?*4um>07UxH8HL!=!qPmdqOD!nOYi}qXY1J2mJ+kd3iN@ zcdhqn*D`uKda5E!*SG3#3MH@Kpc0u?xQz|riut{;YHVtbQe5fjQzj!M++m?kO-jRp zkdTnzLPX8t+Me(kH{a#p`_dit=!iv6)kER@n~qJ`Egg$FCX1y`<#G}m02KfQe$Y`q zh>gt}8MtvHUm3#H#t5~xhUt(JXg@Z!QL6{XnNyRSXF<5T52^|<0Pz2@Kh=}U=n^LNPd4MA_S%Vyyg zIalDbwqt)){!@?hm|Kg={Z@e{@Upu{J|r6YOvwR1z8y|sJQ7X4^K@tNLzgije+^Mh zbcK=Dk;@TBxgvm@`GejAXzBmKGpvpO9C}?c+f!3?PmJ2gxN%~%U=e5zHuCYyFfb4S zcM*K2e7HIHr!-5Uj2AjJJ>s$S^FHL8vKFPWvF!fjq6g57vt^PBe0z%JaunwRR=K!1 zI#M}(0|O3DE=X&*r$Y*#C7lqTHd_j3S9j#2{oPGAeo?#251oQv_P=AYW{N8X>tSXY zEp96I_VlzF_h47qC_RV1Ky+32<2y+Td3<-iwG^AqnGF^vc5h|0Y*%sTaMBKe=Q#d} z^{0U+?T>E#5+0+k%BiBD>S1l}QtqSrbs_W&6bTTXXUUXGaebDxMz^SmaH(koGYcs# zZ*2A zOYIoKJvd&3z8Xgs*k~Bj(JACPqHxFnoFiP}#c79d2|8jyCFFFvHlVxCkB9~&iaplC zYB4c_sTGkQ3`#Y;KNo~Gh#plM@;rV#Dphj|dh_as8?*O=7k;HKu%{B++duM$w{4VPTqIi;vC0mP ze0SQ!SqJ1WGv<2mnTMw;ascp0?~tcL#l*C!iXq=nQuY0&4bS@;-0khpXJ<13CzCl_ zb1R+)0?g>1*u9%KmyhhmKg}>;g_a_P9d?6$bUAB&?n#TDpb&7CYVaHGvt$3NRAbcl ztNO!_6fFw5x48nvx5a2ud2f19})hhHX!2$K9OsI!suLUW$(J2P{+IB7PK}!vR3woT061%KW~&c*1Z!fada>>h6$(A|reeab3R2dGjxt?D|gv^orpT4)AqA1o{jri!wr!0KDgCf-mC&Bec(Ja z*GtL}*3<0?n{QIlY|V14t2-JFLbO1&!oO>Xt=qOr{F$bMgJrW<1z-VY z9<%@c4w1v)5S0b29eSa;#Ww9g`_xr58lKokMHpT}hJ&l_EA(}C_Dg<#Q_D)E{9T$< zHA&93`@VTV9R39;?T#v!=Dz)$y3h`FJx|OwiR!mLYI2>wmQ}D4?_fD_!pAl9IfDQf zyUGZ=>EeTUs=#o!tkrWax)Q%fvB4t3i^cEnY$7_l{9ira&T+4LPzIeX+scaU7SX$N z6DKM!gQ$-sa3gv?__At?dt|0O@(r>P8$ywiB~*m8vVLTQT4O+XbH17NkT5IME!O8j zrW}2m3zL{vn4KC<0#G{QkWSmXbkBg4W7B9O>vOYg@TJ>S9}kE04+x1~cIr82pTd3| zW$E@j0jufwG_5TvHv6z`^wM$ehmMp3q;*V3>TFWw=ONvPgmi=q-?M8LZ23|q&nI8k z)^0v$EEv&jLgO7kd(ib*Y3feLBrjU$85+zilpT#ctg-hkw!g58_SEFV%o381b^{>p6$n X8Qt|rJ5}%3NqR^?pV`tb9 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

TB@^QJ$k0^?g^^1a$h;>V$d{pHSip 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%0Ar@MYJGjMZCSSTTUARfKvM@yRE=nc2e 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

All Security Findings
-
+
-
- +
Account IDRegionCheck IDFindingDetailsResolutionReferenceSeverityStatus
193047247167
+ @@ -297,8 +297,8 @@

Security Assessment Overview

- - + + @@ -308,8 +308,8 @@

Security Assessment Overview

- - + + @@ -319,8 +319,8 @@

Security Assessment Overview

- - + + @@ -330,8 +330,8 @@

Security Assessment Overview

- - + + @@ -341,8 +341,8 @@

Security Assessment Overview

- - + + @@ -352,8 +352,8 @@

Security Assessment Overview

- - + + @@ -363,8 +363,8 @@

Security Assessment Overview

- - + + @@ -374,8 +374,8 @@

Security Assessment Overview

- - + + @@ -385,8 +385,8 @@

Security Assessment Overview

- - + + @@ -396,8 +396,8 @@

Security Assessment Overview

- - + + @@ -407,8 +407,8 @@

Security Assessment Overview

- - + + @@ -418,8 +418,8 @@

Security Assessment Overview

- - + + @@ -429,8 +429,8 @@

Security Assessment Overview

- - + + @@ -440,8 +440,8 @@

Security Assessment Overview

- - + + @@ -451,8 +451,8 @@

Security Assessment Overview

- - + + @@ -462,8 +462,8 @@

Security Assessment Overview

- - + + @@ -473,8 +473,8 @@

Security Assessment Overview

- - + + @@ -484,8 +484,8 @@

Security Assessment Overview

- - + + @@ -495,8 +495,8 @@

Security Assessment Overview

- - + + @@ -506,8 +506,8 @@

Security Assessment Overview

- - + + @@ -517,8 +517,8 @@

Security Assessment Overview

- - + + @@ -528,8 +528,8 @@

Security Assessment Overview

- - + + @@ -539,8 +539,8 @@

Security Assessment Overview

- - + + @@ -550,8 +550,8 @@

Security Assessment Overview

- - + + @@ -561,8 +561,8 @@

Security Assessment Overview

- - + + @@ -572,8 +572,8 @@

Security Assessment Overview

- - + + @@ -583,8 +583,8 @@

Security Assessment Overview

- - + + @@ -594,8 +594,8 @@

Security Assessment Overview

- - + + @@ -605,8 +605,8 @@

Security Assessment Overview

- - + + @@ -616,8 +616,8 @@

Security Assessment Overview

- - + + @@ -627,8 +627,8 @@

Security Assessment Overview

- - + + @@ -638,8 +638,8 @@

Security Assessment Overview

- - + + @@ -649,8 +649,8 @@

Security Assessment Overview

- - + + @@ -660,8 +660,8 @@

Security Assessment Overview

- - + + @@ -671,8 +671,8 @@

Security Assessment Overview

- - + + @@ -682,8 +682,8 @@

Security Assessment Overview

- - + + @@ -693,8 +693,8 @@

Security Assessment Overview

- - + + @@ -704,8 +704,8 @@

Security Assessment Overview

- - + + @@ -715,8 +715,8 @@

Security Assessment Overview

- - + + @@ -726,8 +726,8 @@

Security Assessment Overview

- - + + @@ -737,8 +737,8 @@

Security Assessment Overview

- - + + @@ -748,8 +748,8 @@

Security Assessment Overview

- - + + @@ -759,8 +759,8 @@

Security Assessment Overview

- - + + @@ -770,8 +770,8 @@

Security Assessment Overview

- - + + @@ -781,8 +781,8 @@

Security Assessment Overview

- - + + @@ -792,8 +792,8 @@

Security Assessment Overview

- - + + @@ -803,8 +803,8 @@

Security Assessment Overview

- - + + @@ -814,8 +814,8 @@

Security Assessment Overview

- - + + @@ -825,8 +825,8 @@

Security Assessment Overview

- - + + @@ -836,8 +836,8 @@

Security Assessment Overview

- - + + @@ -847,8 +847,8 @@

Security Assessment Overview

- - + + @@ -858,8 +858,8 @@

Security Assessment Overview

- - + + @@ -869,8 +869,8 @@

Security Assessment Overview

- - + + @@ -880,8 +880,8 @@

Security Assessment Overview

- - + + @@ -891,8 +891,8 @@

Security Assessment Overview

- - + + @@ -902,8 +902,8 @@

Security Assessment Overview

- - + + @@ -913,8 +913,8 @@

Security Assessment Overview

- - + + @@ -924,8 +924,8 @@

Security Assessment Overview

- - + + @@ -935,8 +935,8 @@

Security Assessment Overview

- - + + @@ -946,8 +946,8 @@

Security Assessment Overview

- - + + @@ -957,8 +957,8 @@

Security Assessment Overview

- - + + @@ -968,8 +968,8 @@

Security Assessment Overview

- - + + @@ -979,8 +979,8 @@

Security Assessment Overview

- - + + @@ -990,8 +990,8 @@

Security Assessment Overview

- - + + @@ -1001,8 +1001,8 @@

Security Assessment Overview

- - + + @@ -1012,8 +1012,8 @@

Security Assessment Overview

- - + + @@ -1023,8 +1023,8 @@

Security Assessment Overview

- - + + @@ -1034,8 +1034,8 @@

Security Assessment Overview

- - + + @@ -1049,8 +1049,8 @@

Security Assessment Overview

- - + + @@ -1060,8 +1060,8 @@

Security Assessment Overview

- - + + @@ -1071,8 +1071,8 @@

Security Assessment Overview

- - + + @@ -1082,8 +1082,8 @@

Security Assessment Overview

- - + + @@ -1093,8 +1093,8 @@

Security Assessment Overview

- - + + @@ -1104,8 +1104,8 @@

Security Assessment Overview

- - + + @@ -1115,8 +1115,8 @@

Security Assessment Overview

- - + + @@ -1126,8 +1126,8 @@

Security Assessment Overview

- - + + @@ -1137,8 +1137,8 @@

Security Assessment Overview

- - + + @@ -1148,8 +1148,8 @@

Security Assessment Overview

- - + + @@ -1159,8 +1159,8 @@

Security Assessment Overview

- - + + @@ -1170,8 +1170,8 @@

Security Assessment Overview

- - + + @@ -1181,8 +1181,8 @@

Security Assessment Overview

- - + + @@ -1192,8 +1192,8 @@

Security Assessment Overview

- - + + @@ -1204,8 +1204,8 @@

Security Assessment Overview

- - + + @@ -1216,8 +1216,8 @@

Security Assessment Overview

- - + + @@ -1228,8 +1228,8 @@

Security Assessment Overview

- - + + @@ -1239,8 +1239,8 @@

Security Assessment Overview

- - + + @@ -1250,8 +1250,8 @@

Security Assessment Overview

- - + + @@ -1261,8 +1261,8 @@

Security Assessment Overview

- - + + @@ -1272,8 +1272,8 @@

Security Assessment Overview

- - + + @@ -1283,8 +1283,8 @@

Security Assessment Overview

- - + + @@ -1294,8 +1294,8 @@

Security Assessment Overview

- - + + @@ -1305,8 +1305,8 @@

Security Assessment Overview

- - + + @@ -1316,8 +1316,8 @@

Security Assessment Overview

- - + + @@ -1327,8 +1327,8 @@

Security Assessment Overview

- - + + @@ -1338,8 +1338,8 @@

Security Assessment Overview

- - + + @@ -1349,30 +1349,30 @@

Security Assessment Overview

- - + + - + - - + + - + - - + + @@ -1382,8 +1382,8 @@

Security Assessment Overview

- - + + @@ -1393,8 +1393,8 @@

Security Assessment Overview

- - + + @@ -1404,8 +1404,8 @@

Security Assessment Overview

- - + + @@ -1415,8 +1415,8 @@

Security Assessment Overview

- - + + @@ -1426,8 +1426,8 @@

Security Assessment Overview

- - + + @@ -1437,8 +1437,8 @@

Security Assessment Overview

- - + + @@ -1448,8 +1448,8 @@

Security Assessment Overview

- - + + @@ -1459,8 +1459,8 @@

Security Assessment Overview

- - + + @@ -1470,8 +1470,8 @@

Security Assessment Overview

- - + + @@ -1481,8 +1481,8 @@

Security Assessment Overview

- - + + @@ -1492,8 +1492,8 @@

Security Assessment Overview

- - + + @@ -1503,8 +1503,8 @@

Security Assessment Overview

- - + + @@ -1514,8 +1514,8 @@

Security Assessment Overview

- - + + @@ -1525,8 +1525,8 @@

Security Assessment Overview

- - + + @@ -1536,8 +1536,8 @@

Security Assessment Overview

- - + + @@ -1547,8 +1547,8 @@

Security Assessment Overview

- - + + @@ -1558,8 +1558,8 @@

Security Assessment Overview

- - + + @@ -1569,8 +1569,8 @@

Security Assessment Overview

- - + + @@ -1580,8 +1580,8 @@

Security Assessment Overview

- - + + @@ -1591,8 +1591,8 @@

Security Assessment Overview

- - + + @@ -1602,8 +1602,8 @@

Security Assessment Overview

- - + + @@ -1613,8 +1613,8 @@

Security Assessment Overview

- - + + @@ -1624,8 +1624,8 @@

Security Assessment Overview

- - + + @@ -1635,30 +1635,30 @@

Security Assessment Overview

- - + + - + - - + + - + - - + + @@ -1668,8 +1668,8 @@

Security Assessment Overview

- - + + @@ -1679,8 +1679,8 @@

Security Assessment Overview

- - + + @@ -1690,8 +1690,8 @@

Security Assessment Overview

- - + + @@ -1701,8 +1701,8 @@

Security Assessment Overview

- - + + @@ -1712,8 +1712,8 @@

Security Assessment Overview

- - + + @@ -1723,8 +1723,8 @@

Security Assessment Overview

- - + + @@ -1734,8 +1734,8 @@

Security Assessment Overview

- - + + @@ -1745,8 +1745,8 @@

Security Assessment Overview

- - + + @@ -1756,8 +1756,8 @@

Security Assessment Overview

- - + + @@ -1767,8 +1767,8 @@

Security Assessment Overview

- - + + @@ -1778,8 +1778,8 @@

Security Assessment Overview

- - + + @@ -1789,8 +1789,8 @@

Security Assessment Overview

- - + + @@ -1800,8 +1800,8 @@

Security Assessment Overview

- - + + @@ -1811,8 +1811,8 @@

Security Assessment Overview

- - + + @@ -1822,8 +1822,8 @@

Security Assessment Overview

- - + + @@ -1833,8 +1833,8 @@

Security Assessment Overview

- - + + @@ -1844,8 +1844,8 @@

Security Assessment Overview

- - + + @@ -1855,8 +1855,8 @@

Security Assessment Overview

- - + + @@ -1866,8 +1866,8 @@

Security Assessment Overview

- - + + @@ -1877,8 +1877,8 @@

Security Assessment Overview

- - + + @@ -1888,8 +1888,8 @@

Security Assessment Overview

- - + + @@ -1899,8 +1899,8 @@

Security Assessment Overview

- - + + @@ -1910,8 +1910,8 @@

Security Assessment Overview

- - + + @@ -1921,8 +1921,8 @@

Security Assessment Overview

- - + + @@ -1932,8 +1932,8 @@

Security Assessment Overview

- - + + @@ -1943,8 +1943,8 @@

Security Assessment Overview

- - + + @@ -1954,8 +1954,8 @@

Security Assessment Overview

- - + + @@ -1965,8 +1965,8 @@

Security Assessment Overview

- - + + @@ -1976,8 +1976,8 @@

Security Assessment Overview

- - + + @@ -1987,8 +1987,8 @@

Security Assessment Overview

- - + + @@ -1998,8 +1998,8 @@

Security Assessment Overview

- - + + @@ -2009,8 +2009,8 @@

Security Assessment Overview

- - + + @@ -2020,8 +2020,8 @@

Security Assessment Overview

- - + + @@ -2031,8 +2031,8 @@

Security Assessment Overview

- - + + @@ -2042,8 +2042,8 @@

Security Assessment Overview

- - + + @@ -2053,8 +2053,8 @@

Security Assessment Overview

- - + + @@ -2064,8 +2064,8 @@

Security Assessment Overview

- - + + @@ -2075,8 +2075,8 @@

Security Assessment Overview

- - + + @@ -2090,8 +2090,8 @@

Security Assessment Overview

- - + + @@ -2101,19 +2101,19 @@

Security Assessment Overview

- - + + - + - - + + @@ -2123,8 +2123,8 @@

Security Assessment Overview

- - + + @@ -2134,8 +2134,8 @@

Security Assessment Overview

- - + + @@ -2145,8 +2145,8 @@

Security Assessment Overview

- - + + @@ -2156,8 +2156,8 @@

Security Assessment Overview

- - + + @@ -2167,8 +2167,8 @@

Security Assessment Overview

- - + + @@ -2178,8 +2178,8 @@

Security Assessment Overview

- - + + @@ -2189,8 +2189,8 @@

Security Assessment Overview

- - + + @@ -2200,8 +2200,8 @@

Security Assessment Overview

- - + + @@ -2211,8 +2211,8 @@

Security Assessment Overview

- - + + @@ -2222,8 +2222,8 @@

Security Assessment Overview

- - + + @@ -2233,8 +2233,8 @@

Security Assessment Overview

- - + + @@ -2244,8 +2244,8 @@

Security Assessment Overview

- - + + @@ -2255,8 +2255,8 @@

Security Assessment Overview

- - + + @@ -2266,8 +2266,8 @@

Security Assessment Overview

- - + + @@ -2277,8 +2277,8 @@

Security Assessment Overview

- - + + @@ -2288,8 +2288,8 @@

Security Assessment Overview

- - + + @@ -2299,8 +2299,8 @@

Security Assessment Overview

- - + + @@ -2310,8 +2310,8 @@

Security Assessment Overview

- - + + @@ -2321,8 +2321,8 @@

Security Assessment Overview

- - + + @@ -2332,8 +2332,8 @@

Security Assessment Overview

- - + + @@ -2343,8 +2343,8 @@

Security Assessment Overview

- - + + @@ -2354,8 +2354,8 @@

Security Assessment Overview

- - + + @@ -2365,8 +2365,8 @@

Security Assessment Overview

- - + + @@ -2376,8 +2376,8 @@

Security Assessment Overview

- - + + @@ -2387,8 +2387,8 @@

Security Assessment Overview

- - + + @@ -2398,8 +2398,8 @@

Security Assessment Overview

- - + + @@ -2409,8 +2409,8 @@

Security Assessment Overview

- - + + @@ -2420,8 +2420,8 @@

Security Assessment Overview

- - + + @@ -2431,8 +2431,8 @@

Security Assessment Overview

- - + + @@ -2442,8 +2442,8 @@

Security Assessment Overview

- - + + @@ -2453,8 +2453,8 @@

Security Assessment Overview

- - + + @@ -2464,8 +2464,8 @@

Security Assessment Overview

- - + + @@ -2475,8 +2475,8 @@

Security Assessment Overview

- - + + @@ -2486,8 +2486,8 @@

Security Assessment Overview

- - + + @@ -2497,8 +2497,8 @@

Security Assessment Overview

- - + + @@ -2508,8 +2508,8 @@

Security Assessment Overview

- - + + @@ -2519,8 +2519,8 @@

Security Assessment Overview

- - + + @@ -2530,8 +2530,8 @@

Security Assessment Overview

- - + + @@ -2541,8 +2541,8 @@

Security Assessment Overview

- - + + @@ -2552,8 +2552,8 @@

Security Assessment Overview

- - + + @@ -2563,8 +2563,8 @@

Security Assessment Overview

- - + + @@ -2574,8 +2574,8 @@

Security Assessment Overview

- - + + @@ -2589,8 +2589,8 @@

Security Assessment Overview

- - + + @@ -2600,19 +2600,19 @@

Security Assessment Overview

- - + + - + - - + + @@ -2622,8 +2622,8 @@

Security Assessment Overview

- - + + @@ -2633,8 +2633,8 @@

Security Assessment Overview

- - + + @@ -2644,8 +2644,8 @@

Security Assessment Overview

- - + + @@ -2655,8 +2655,8 @@

Security Assessment Overview

- - + + @@ -2666,8 +2666,8 @@

Security Assessment Overview

- - + + @@ -2677,8 +2677,8 @@

Security Assessment Overview

- - + + @@ -2688,8 +2688,8 @@

Security Assessment Overview

- - + + @@ -2699,8 +2699,8 @@

Security Assessment Overview

- - + + @@ -2714,8 +2714,8 @@

Security Assessment Overview

- - + + @@ -2725,19 +2725,19 @@

Security Assessment Overview

- - + + - + - - + + @@ -2747,8 +2747,8 @@

Security Assessment Overview

- - + + @@ -2758,8 +2758,8 @@

Security Assessment Overview

- - + + @@ -2769,8 +2769,8 @@

Security Assessment Overview

- - + + @@ -2780,8 +2780,8 @@

Security Assessment Overview

- - + + @@ -2791,8 +2791,8 @@

Security Assessment Overview

- - + + @@ -2802,8 +2802,8 @@

Security Assessment Overview

- - + + @@ -2813,8 +2813,8 @@

Security Assessment Overview

- - + + @@ -2824,8 +2824,8 @@

Security Assessment Overview

- - + + @@ -2835,8 +2835,8 @@

Security Assessment Overview

- - + + @@ -2846,8 +2846,8 @@

Security Assessment Overview

- - + + @@ -2857,8 +2857,8 @@

Security Assessment Overview

- - + + @@ -2868,8 +2868,8 @@

Security Assessment Overview

- - + + @@ -2879,8 +2879,8 @@

Security Assessment Overview

- - + + @@ -2890,8 +2890,8 @@

Security Assessment Overview

- - + + @@ -2901,8 +2901,8 @@

Security Assessment Overview

- - + + @@ -2912,8 +2912,8 @@

Security Assessment Overview

- - + + @@ -2923,8 +2923,8 @@

Security Assessment Overview

- - + + @@ -2934,8 +2934,8 @@

Security Assessment Overview

- - + + @@ -2945,8 +2945,8 @@

Security Assessment Overview

- - + + @@ -2956,8 +2956,8 @@

Security Assessment Overview

- - + + @@ -2967,8 +2967,8 @@

Security Assessment Overview

- - + + @@ -2978,8 +2978,8 @@

Security Assessment Overview

- - + + @@ -2989,8 +2989,8 @@

Security Assessment Overview

- - + + @@ -3000,8 +3000,8 @@

Security Assessment Overview

- - + + @@ -3011,8 +3011,8 @@

Security Assessment Overview

- - + + @@ -3022,8 +3022,8 @@

Security Assessment Overview

- - + + @@ -3033,8 +3033,8 @@

Security Assessment Overview

- - + + @@ -3044,8 +3044,8 @@

Security Assessment Overview

- - + + @@ -3055,8 +3055,8 @@

Security Assessment Overview

- - + + @@ -3066,8 +3066,8 @@

Security Assessment Overview

- - + + @@ -3077,8 +3077,8 @@

Security Assessment Overview

- - + + @@ -3091,8 +3091,8 @@

Security Assessment Overview

- - + + @@ -3104,8 +3104,8 @@

Security Assessment Overview

- - + + @@ -3115,8 +3115,8 @@

Security Assessment Overview

- - + + @@ -3129,8 +3129,8 @@

Security Assessment Overview

- - + + @@ -3142,8 +3142,8 @@

Security Assessment Overview

- - + + @@ -3156,8 +3156,8 @@

Security Assessment Overview

- - + + @@ -3169,8 +3169,8 @@

Security Assessment Overview

- - + + @@ -3180,8 +3180,8 @@

Security Assessment Overview

- - + + @@ -3191,12 +3191,12 @@

Security Assessment Overview

- - + + - + - - + + @@ -3216,8 +3216,8 @@

Security Assessment Overview

- - + + @@ -3230,8 +3230,8 @@

Security Assessment Overview

- - + + @@ -3243,8 +3243,8 @@

Security Assessment Overview

- - + + @@ -3254,8 +3254,8 @@

Security Assessment Overview

- - + + @@ -3265,8 +3265,8 @@

Security Assessment Overview

- - + + @@ -3278,19 +3278,19 @@

Security Assessment Overview

- - + + - + - - + + @@ -3300,25 +3300,25 @@

Security Assessment Overview

- - + + - + - - + + - - + + @@ -3345,8 +3345,8 @@

Security Assessment Overview

- - + + @@ -3356,8 +3356,8 @@

Security Assessment Overview

- - + + @@ -3367,8 +3367,8 @@

Security Assessment Overview

- - + + @@ -3378,8 +3378,8 @@

Security Assessment Overview

- - + + @@ -3392,8 +3392,8 @@

Security Assessment Overview

- - + + @@ -3403,8 +3403,8 @@

Security Assessment Overview

- - + + @@ -3417,8 +3417,8 @@

Security Assessment Overview

- - + + @@ -3431,14 +3431,14 @@

Security Assessment Overview

- - + + - - + + @@ -3461,19 +3461,19 @@

Security Assessment Overview

- - + + - + - - + + @@ -3486,8 +3486,8 @@

Security Assessment Overview

- - + + @@ -3500,8 +3500,8 @@

Security Assessment Overview

- - + + @@ -3511,8 +3511,8 @@

Security Assessment Overview

- - + + @@ -3525,8 +3525,8 @@

Security Assessment Overview

- - + + @@ -3536,8 +3536,8 @@

Security Assessment Overview

- - + + @@ -3550,8 +3550,8 @@

Security Assessment Overview

- - + + @@ -3564,8 +3564,8 @@

Security Assessment Overview

- - + + @@ -3578,8 +3578,8 @@

Security Assessment Overview

- - + + @@ -3592,8 +3592,8 @@

Security Assessment Overview

- - + + @@ -3606,8 +3606,8 @@

Security Assessment Overview

- - + + @@ -3617,8 +3617,8 @@

Security Assessment Overview

- - + + @@ -3628,19 +3628,19 @@

Security Assessment Overview

- - + + - + - - + + @@ -3650,8 +3650,8 @@

Security Assessment Overview

- - + + @@ -3661,8 +3661,8 @@

Security Assessment Overview

- - + + @@ -3674,8 +3674,8 @@

Security Assessment Overview

- - + + @@ -3685,8 +3685,8 @@

Security Assessment Overview

- - + + @@ -3696,8 +3696,8 @@

Security Assessment Overview

- - + + @@ -3710,8 +3710,8 @@

Security Assessment Overview

- - + + @@ -3721,8 +3721,8 @@

Security Assessment Overview

- - + + @@ -3736,8 +3736,8 @@

Security Assessment Overview

- - + + @@ -3750,8 +3750,8 @@

Security Assessment Overview

- - + + @@ -3761,8 +3761,8 @@

Security Assessment Overview

- - + + @@ -3775,8 +3775,8 @@

Security Assessment Overview

- - + + @@ -3789,8 +3789,8 @@

Security Assessment Overview

- - + + @@ -3800,8 +3800,8 @@

Security Assessment Overview

- - + + @@ -3813,12 +3813,12 @@

Security Assessment Overview

- - + + - + - - + + @@ -3840,8 +3840,8 @@

Security Assessment Overview

- - + + @@ -3851,14 +3851,14 @@

Security Assessment Overview

- - + + +- 111111111111-us-east-1-kb-data-bucket @@ -3866,8 +3866,8 @@

Security Assessment Overview

- - + + @@ -3885,16 +3885,16 @@

Security Assessment Overview

- - + + @@ -3906,8 +3906,8 @@

Security Assessment Overview

- - + + @@ -3920,19 +3920,19 @@

Security Assessment Overview

- - + + - + - - + + @@ -3942,8 +3942,8 @@

Security Assessment Overview

- - + + @@ -3953,8 +3953,8 @@

Security Assessment Overview

- - + + @@ -3964,8 +3964,8 @@

Security Assessment Overview

- - + + @@ -3975,8 +3975,8 @@

Security Assessment Overview

- - + + @@ -3986,8 +3986,8 @@

Security Assessment Overview

- - + + @@ -3997,8 +3997,8 @@

Security Assessment Overview

- - + + @@ -4008,8 +4008,8 @@

Security Assessment Overview

- - + + @@ -4019,8 +4019,8 @@

Security Assessment Overview

- - + + @@ -4030,8 +4030,8 @@

Security Assessment Overview

- - + + @@ -4041,8 +4041,8 @@

Security Assessment Overview

- - + + @@ -4052,8 +4052,8 @@

Security Assessment Overview

- - + + @@ -4063,8 +4063,8 @@

Security Assessment Overview

- - + + @@ -4074,8 +4074,8 @@

Security Assessment Overview

- - + + @@ -4085,8 +4085,8 @@

Security Assessment Overview

- - + + @@ -4096,8 +4096,8 @@

Security Assessment Overview

- - + + @@ -4107,8 +4107,8 @@

Security Assessment Overview

- - + + @@ -4118,8 +4118,8 @@

Security Assessment Overview

- - + + @@ -4129,8 +4129,8 @@

Security Assessment Overview

- - + + @@ -4140,8 +4140,8 @@

Security Assessment Overview

- - + + @@ -4151,8 +4151,8 @@

Security Assessment Overview

- - + + @@ -4162,8 +4162,8 @@

Security Assessment Overview

- - + + @@ -4173,8 +4173,8 @@

Security Assessment Overview

- - + + @@ -4184,8 +4184,8 @@

Security Assessment Overview

- - + + @@ -4195,8 +4195,8 @@

Security Assessment Overview

- - + + @@ -4206,8 +4206,8 @@

Security Assessment Overview

- - + + @@ -4217,8 +4217,8 @@

Security Assessment Overview

- - + + @@ -4228,8 +4228,8 @@

Security Assessment Overview

- - + + @@ -4239,8 +4239,8 @@

Security Assessment Overview

- - + + @@ -4250,8 +4250,8 @@

Security Assessment Overview

- - + + @@ -4261,8 +4261,8 @@

Security Assessment Overview

- - + + @@ -4272,8 +4272,8 @@

Security Assessment Overview

- - + + @@ -4283,8 +4283,8 @@

Security Assessment Overview

- - + + @@ -4294,8 +4294,8 @@

Security Assessment Overview

- - + + @@ -4305,8 +4305,8 @@

Security Assessment Overview

- - + + @@ -4316,8 +4316,8 @@

Security Assessment Overview

- - + + @@ -4327,8 +4327,8 @@

Security Assessment Overview

- - + + @@ -4338,8 +4338,8 @@

Security Assessment Overview

- - + + @@ -4349,8 +4349,8 @@

Security Assessment Overview

- - + + @@ -4360,8 +4360,8 @@

Security Assessment Overview

- - + + @@ -4371,8 +4371,8 @@

Security Assessment Overview

- - + + @@ -4382,8 +4382,8 @@

Security Assessment Overview

- - + + @@ -4393,8 +4393,8 @@

Security Assessment Overview

- - + + @@ -4404,8 +4404,8 @@

Security Assessment Overview

- - + + @@ -4415,8 +4415,8 @@

Security Assessment Overview

- - + + @@ -4426,8 +4426,8 @@

Security Assessment Overview

- - + + @@ -4437,8 +4437,8 @@

Security Assessment Overview

- - + + @@ -4448,8 +4448,8 @@

Security Assessment Overview

- - + + @@ -4459,8 +4459,8 @@

Security Assessment Overview

- - + + @@ -4470,8 +4470,8 @@

Security Assessment Overview

- - + + @@ -4481,8 +4481,8 @@

Security Assessment Overview

- - + + @@ -4492,8 +4492,8 @@

Security Assessment Overview

- - + + @@ -4503,8 +4503,8 @@

Security Assessment Overview

- - + + @@ -4514,8 +4514,8 @@

Security Assessment Overview

- - + + @@ -4525,8 +4525,8 @@

Security Assessment Overview

- - + + @@ -4536,8 +4536,8 @@

Security Assessment Overview

- - + + @@ -4547,8 +4547,8 @@

Security Assessment Overview

- - + + @@ -4558,8 +4558,8 @@

Security Assessment Overview

- - + + @@ -4569,8 +4569,8 @@

Security Assessment Overview

- - + + @@ -4580,8 +4580,8 @@

Security Assessment Overview

- - + + @@ -4591,8 +4591,8 @@

Security Assessment Overview

- - + + @@ -4602,8 +4602,8 @@

Security Assessment Overview

- - + + @@ -4613,8 +4613,8 @@

Security Assessment Overview

- - + + @@ -4624,8 +4624,8 @@

Security Assessment Overview

- - + + @@ -4635,8 +4635,8 @@

Security Assessment Overview

- - + + @@ -4646,8 +4646,8 @@

Security Assessment Overview

- - + + @@ -4657,8 +4657,8 @@

Security Assessment Overview

- - + + @@ -4668,8 +4668,8 @@

Security Assessment Overview

- - + + @@ -4679,8 +4679,8 @@

Security Assessment Overview

- - + + @@ -4690,8 +4690,8 @@

Security Assessment Overview

- - + + @@ -4701,8 +4701,8 @@

Security Assessment Overview

- - + + @@ -4712,8 +4712,8 @@

Security Assessment Overview

- - + + @@ -4723,8 +4723,8 @@

Security Assessment Overview

- - + + @@ -4734,8 +4734,8 @@

Security Assessment Overview

- - + + @@ -4745,8 +4745,8 @@

Security Assessment Overview

- - + + @@ -4756,8 +4756,8 @@

Security Assessment Overview

- - + + @@ -4767,8 +4767,8 @@

Security Assessment Overview

- - + + @@ -4778,8 +4778,8 @@

Security Assessment Overview

- - + + @@ -4789,8 +4789,8 @@

Security Assessment Overview

- - + + @@ -4800,8 +4800,8 @@

Security Assessment Overview

- - + + @@ -4811,8 +4811,8 @@

Security Assessment Overview

- - + + @@ -4822,8 +4822,8 @@

Security Assessment Overview

- - + + @@ -4833,8 +4833,8 @@

Security Assessment Overview

- - + + @@ -4844,8 +4844,8 @@

Security Assessment Overview

- - + + @@ -4855,8 +4855,8 @@

Security Assessment Overview

- - + + @@ -4866,8 +4866,8 @@

Security Assessment Overview

- - + + @@ -4877,8 +4877,8 @@

Security Assessment Overview

- - + + @@ -4888,8 +4888,8 @@

Security Assessment Overview

- - + + @@ -4899,8 +4899,8 @@

Security Assessment Overview

- - + + @@ -4910,8 +4910,8 @@

Security Assessment Overview

- - + + @@ -4921,8 +4921,8 @@

Security Assessment Overview

- - + + @@ -4932,8 +4932,8 @@

Security Assessment Overview

- - + + @@ -4943,8 +4943,8 @@

Security Assessment Overview

- - + + @@ -4954,8 +4954,8 @@

Security Assessment Overview

- - + + @@ -4965,8 +4965,8 @@

Security Assessment Overview

- - + + @@ -4976,8 +4976,8 @@

Security Assessment Overview

- - + + @@ -4987,8 +4987,8 @@

Security Assessment Overview

- - + + @@ -4998,8 +4998,8 @@

Security Assessment Overview

- - + + @@ -5009,8 +5009,8 @@

Security Assessment Overview

- - + + @@ -5020,8 +5020,8 @@

Security Assessment Overview

- - + + @@ -5031,8 +5031,8 @@

Security Assessment Overview

- - + + @@ -5042,8 +5042,8 @@

Security Assessment Overview

- - + + @@ -5053,8 +5053,8 @@

Security Assessment Overview

- - + + @@ -5064,8 +5064,8 @@

Security Assessment Overview

- - + + @@ -5075,8 +5075,8 @@

Security Assessment Overview

- - + + @@ -5087,8 +5087,8 @@

Security Assessment Overview

- - + + @@ -5098,8 +5098,8 @@

Security Assessment Overview

- - + + @@ -5109,8 +5109,8 @@

Security Assessment Overview

- - + + @@ -5120,8 +5120,8 @@

Security Assessment Overview

- - + + @@ -5131,8 +5131,8 @@

Security Assessment Overview

- - + + @@ -5142,8 +5142,8 @@

Security Assessment Overview

- - + + @@ -5153,8 +5153,8 @@

Security Assessment Overview

- - + + @@ -5164,41 +5164,41 @@

Security Assessment Overview

- - + + - + - - + + - + - - + + - + - - + + @@ -5208,8 +5208,8 @@

Security Assessment Overview

- - + + @@ -5219,8 +5219,8 @@

Security Assessment Overview

- - + + @@ -5230,8 +5230,8 @@

Security Assessment Overview

- - + + @@ -5241,8 +5241,8 @@

Security Assessment Overview

- - + + @@ -5252,8 +5252,8 @@

Security Assessment Overview

- - + + @@ -5263,8 +5263,8 @@

Security Assessment Overview

- - + + @@ -5274,8 +5274,8 @@

Security Assessment Overview

- - + + @@ -5285,8 +5285,8 @@

Security Assessment Overview

- - + + @@ -5296,8 +5296,8 @@

Security Assessment Overview

- - + + @@ -5307,8 +5307,8 @@

Security Assessment Overview

- - + + @@ -5318,8 +5318,8 @@

Security Assessment Overview

- - + + @@ -5329,8 +5329,8 @@

Security Assessment Overview

- - + + @@ -5340,8 +5340,8 @@

Security Assessment Overview

- - + + @@ -5351,8 +5351,8 @@

Security Assessment Overview

- - + + @@ -5366,8 +5366,8 @@

Security Assessment Overview

- - + + @@ -5377,19 +5377,19 @@

Security Assessment Overview

- - + + - + - - + + @@ -5399,8 +5399,8 @@

Security Assessment Overview

- - + + @@ -5410,8 +5410,8 @@

Security Assessment Overview

- - + + @@ -5421,8 +5421,8 @@

Security Assessment Overview

- - + + @@ -5432,8 +5432,8 @@

Security Assessment Overview

- - + + @@ -5446,8 +5446,8 @@

Security Assessment Overview

- - + + @@ -5459,8 +5459,8 @@

Security Assessment Overview

- - + + @@ -5470,8 +5470,8 @@

Security Assessment Overview

- - + + @@ -5484,8 +5484,8 @@

Security Assessment Overview

- - + + @@ -5497,8 +5497,8 @@

Security Assessment Overview

- - + + @@ -5511,8 +5511,8 @@

Security Assessment Overview

- - + + @@ -5524,8 +5524,8 @@

Security Assessment Overview

- - + + @@ -5535,8 +5535,8 @@

Security Assessment Overview

- - + + @@ -5546,8 +5546,8 @@

Security Assessment Overview

- - + + @@ -5560,8 +5560,8 @@

Security Assessment Overview

- - + + @@ -5571,8 +5571,8 @@

Security Assessment Overview

- - + + @@ -5585,8 +5585,8 @@

Security Assessment Overview

- - + + @@ -5598,8 +5598,8 @@

Security Assessment Overview

- - + + @@ -5609,8 +5609,8 @@

Security Assessment Overview

- - + + @@ -5620,8 +5620,8 @@

Security Assessment Overview

- - + + @@ -5633,19 +5633,19 @@

Security Assessment Overview

- - + + - + - - + + @@ -5655,8 +5655,8 @@

Security Assessment Overview

- - + + @@ -5666,8 +5666,8 @@

Security Assessment Overview

- - + + @@ -5687,8 +5687,8 @@

Security Assessment Overview

- - + + @@ -5700,8 +5700,8 @@

Security Assessment Overview

- - + + @@ -5711,8 +5711,8 @@

Security Assessment Overview

- - + + @@ -5722,8 +5722,8 @@

Security Assessment Overview

- - + + @@ -5733,8 +5733,8 @@

Security Assessment Overview

- - + + @@ -5747,8 +5747,8 @@

Security Assessment Overview

- - + + @@ -5758,8 +5758,8 @@

Security Assessment Overview

- - + + @@ -5772,8 +5772,8 @@

Security Assessment Overview

- - + + @@ -5786,8 +5786,8 @@

Security Assessment Overview

- - + + @@ -5801,8 +5801,8 @@

Security Assessment Overview

- - + + @@ -5815,8 +5815,8 @@

Security Assessment Overview

- - + + @@ -5826,8 +5826,8 @@

Security Assessment Overview

- - + + @@ -5840,8 +5840,8 @@

Security Assessment Overview

- - + + @@ -5854,8 +5854,8 @@

Security Assessment Overview

- - + + @@ -5865,8 +5865,8 @@

Security Assessment Overview

- - + + @@ -5879,8 +5879,8 @@

Security Assessment Overview

- - + + @@ -5890,8 +5890,8 @@

Security Assessment Overview

- - + + @@ -5904,8 +5904,8 @@

Security Assessment Overview

- - + + @@ -5918,8 +5918,8 @@

Security Assessment Overview

- - + + @@ -5932,8 +5932,8 @@

Security Assessment Overview

- - + + @@ -5946,8 +5946,8 @@

Security Assessment Overview

- - + + @@ -5960,8 +5960,8 @@

Security Assessment Overview

- - + + @@ -5974,8 +5974,8 @@

Security Assessment Overview

- - + + @@ -5985,8 +5985,8 @@

Security Assessment Overview

- - + + @@ -5996,8 +5996,8 @@

Security Assessment Overview

- - + + @@ -6007,8 +6007,8 @@

Security Assessment Overview

- - + + @@ -6018,8 +6018,8 @@

Security Assessment Overview

- - + + @@ -6031,8 +6031,8 @@

Security Assessment Overview

- - + + @@ -6042,8 +6042,8 @@

Security Assessment Overview

- - + + @@ -6053,8 +6053,8 @@

Security Assessment Overview

- - + + @@ -6064,8 +6064,8 @@

Security Assessment Overview

- - + + @@ -6075,8 +6075,8 @@

Security Assessment Overview

- - + + @@ -6090,8 +6090,8 @@

Security Assessment Overview

- - + + @@ -6104,8 +6104,8 @@

Security Assessment Overview

- - + + @@ -6115,8 +6115,8 @@

Security Assessment Overview

- - + + @@ -6129,8 +6129,8 @@

Security Assessment Overview

- - + + @@ -6143,8 +6143,8 @@

Security Assessment Overview

- - + + @@ -6154,8 +6154,8 @@

Security Assessment Overview

- - + + @@ -6167,12 +6167,12 @@

Security Assessment Overview

- - + + - + - - + + @@ -6194,8 +6194,8 @@

Security Assessment Overview

- - + + @@ -6205,8 +6205,8 @@

Security Assessment Overview

- - + + @@ -6219,8 +6219,8 @@

Security Assessment Overview

- - + + @@ -6230,8 +6230,8 @@

Security Assessment Overview

- - + + @@ -6249,8 +6249,8 @@

Security Assessment Overview

- - + + @@ -6260,8 +6260,8 @@

Security Assessment Overview

- - + + @@ -6271,8 +6271,8 @@

Security Assessment Overview

- - + + @@ -6282,8 +6282,8 @@

Security Assessment Overview

- - + + @@ -6293,8 +6293,8 @@

Security Assessment Overview

- - + + @@ -6304,8 +6304,8 @@

Security Assessment Overview

- - + + @@ -6315,8 +6315,8 @@

Security Assessment Overview

- - + + @@ -6326,8 +6326,8 @@

Security Assessment Overview

- - + + @@ -6337,8 +6337,8 @@

Security Assessment Overview

- - + + @@ -6352,8 +6352,8 @@

Security Assessment Overview

- - + + @@ -6363,19 +6363,19 @@

Security Assessment Overview

- - + + - + - - + + @@ -6385,8 +6385,8 @@

Security Assessment Overview

- - + + @@ -6396,8 +6396,8 @@

Security Assessment Overview

- - + + @@ -6407,8 +6407,8 @@

Security Assessment Overview

- - + + @@ -6418,8 +6418,8 @@

Security Assessment Overview

- - + + @@ -6429,8 +6429,8 @@

Security Assessment Overview

- - + + @@ -6440,8 +6440,8 @@

Security Assessment Overview

- - + + @@ -6451,8 +6451,8 @@

Security Assessment Overview

- - + + @@ -6462,8 +6462,8 @@

Security Assessment Overview

- - + + @@ -6477,8 +6477,8 @@

Security Assessment Overview

- - + + @@ -6488,19 +6488,19 @@

Security Assessment Overview

- - + + - + - - + + @@ -6510,8 +6510,8 @@

Security Assessment Overview

- - + + @@ -6521,8 +6521,8 @@

Security Assessment Overview

- - + + @@ -6532,8 +6532,8 @@

Security Assessment Overview

- - + + @@ -6543,8 +6543,8 @@

Security Assessment Overview

- - + + @@ -6554,8 +6554,8 @@

Security Assessment Overview

- - + + @@ -6565,8 +6565,8 @@

Security Assessment Overview

- - + + @@ -6576,8 +6576,8 @@

Security Assessment Overview

- - + + @@ -6587,8 +6587,8 @@

Security Assessment Overview

- - + + @@ -6598,8 +6598,8 @@

Security Assessment Overview

- - + + @@ -6609,8 +6609,8 @@

Security Assessment Overview

- - + + @@ -6620,8 +6620,8 @@

Security Assessment Overview

- - + + @@ -6631,8 +6631,8 @@

Security Assessment Overview

- - + + @@ -6642,8 +6642,8 @@

Security Assessment Overview

- - + + @@ -6653,8 +6653,8 @@

Security Assessment Overview

- - + + @@ -6664,8 +6664,8 @@

Security Assessment Overview

- - + + @@ -6675,8 +6675,8 @@

Security Assessment Overview

- - + + @@ -6686,8 +6686,8 @@

Security Assessment Overview

- - + + @@ -6697,8 +6697,8 @@

Security Assessment Overview

- - + + @@ -6708,8 +6708,8 @@

Security Assessment Overview

- - + + @@ -6719,8 +6719,8 @@

Security Assessment Overview

- - + + @@ -6730,8 +6730,8 @@

Security Assessment Overview

- - + + @@ -6741,8 +6741,8 @@

Security Assessment Overview

- - + + @@ -6752,8 +6752,8 @@

Security Assessment Overview

- - + + @@ -6763,8 +6763,8 @@

Security Assessment Overview

- - + + @@ -6774,8 +6774,8 @@

Security Assessment Overview

- - + + @@ -6785,8 +6785,8 @@

Security Assessment Overview

- - + + @@ -6796,8 +6796,8 @@

Security Assessment Overview

- - + + @@ -6807,8 +6807,8 @@

Security Assessment Overview

- - + + @@ -6818,8 +6818,8 @@

Security Assessment Overview

- - + + @@ -6829,8 +6829,8 @@

Security Assessment Overview

- - + + @@ -6840,8 +6840,8 @@

Security Assessment Overview

- - + + @@ -6851,8 +6851,8 @@

Security Assessment Overview

- - + + @@ -6862,8 +6862,8 @@

Security Assessment Overview

- - + + @@ -6873,8 +6873,8 @@

Security Assessment Overview

- - + + @@ -6884,8 +6884,8 @@

Security Assessment Overview

- - + + @@ -6895,8 +6895,8 @@

Security Assessment Overview

- - + + @@ -6906,8 +6906,8 @@

Security Assessment Overview

- - + + @@ -6917,8 +6917,8 @@

Security Assessment Overview

- - + + @@ -6928,8 +6928,8 @@

Security Assessment Overview

- - + + @@ -6939,8 +6939,8 @@

Security Assessment Overview

- - + + @@ -6950,8 +6950,8 @@

Security Assessment Overview

- - + + @@ -6961,8 +6961,8 @@

Security Assessment Overview

- - + + @@ -6972,8 +6972,8 @@

Security Assessment Overview

- - + + @@ -6983,8 +6983,8 @@

Security Assessment Overview

- - + + @@ -6994,8 +6994,8 @@

Security Assessment Overview

- - + + @@ -7005,8 +7005,8 @@

Security Assessment Overview

- - + + @@ -7016,8 +7016,8 @@

Security Assessment Overview

- - + + @@ -7027,8 +7027,8 @@

Security Assessment Overview

- - + + @@ -7038,8 +7038,8 @@

Security Assessment Overview

- - + + @@ -7049,8 +7049,8 @@

Security Assessment Overview

- - + + @@ -7060,8 +7060,8 @@

Security Assessment Overview

- - + + @@ -7071,8 +7071,8 @@

Security Assessment Overview

- - + + @@ -7082,8 +7082,8 @@

Security Assessment Overview

- - + + @@ -7093,8 +7093,8 @@

Security Assessment Overview

- - + + @@ -7104,8 +7104,8 @@

Security Assessment Overview

- - + + @@ -7115,8 +7115,8 @@

Security Assessment Overview

- - + + @@ -7126,8 +7126,8 @@

Security Assessment Overview

- - + + @@ -7137,8 +7137,8 @@

Security Assessment Overview

- - + + @@ -7148,8 +7148,8 @@

Security Assessment Overview

- - + + @@ -7159,8 +7159,8 @@

Security Assessment Overview

- - + + @@ -7170,8 +7170,8 @@

Security Assessment Overview

- - + + @@ -7181,8 +7181,8 @@

Security Assessment Overview

- - + + @@ -7192,8 +7192,8 @@

Security Assessment Overview

- - + + @@ -7203,8 +7203,8 @@

Security Assessment Overview

- - + + @@ -7214,8 +7214,8 @@

Security Assessment Overview

- - + + @@ -7225,8 +7225,8 @@

Security Assessment Overview

- - + + @@ -7236,8 +7236,8 @@

Security Assessment Overview

- - + + @@ -7247,8 +7247,8 @@

Security Assessment Overview

- - + + @@ -7258,8 +7258,8 @@

Security Assessment Overview

- - + + @@ -7269,8 +7269,8 @@

Security Assessment Overview

- - + + @@ -7280,8 +7280,8 @@

Security Assessment Overview

- - + + @@ -7291,8 +7291,8 @@

Security Assessment Overview

- - + + @@ -7302,8 +7302,8 @@

Security Assessment Overview

- - + + @@ -7313,8 +7313,8 @@

Security Assessment Overview

- - + + @@ -7324,8 +7324,8 @@

Security Assessment Overview

- - + + @@ -7335,8 +7335,8 @@

Security Assessment Overview

- - + + @@ -7346,8 +7346,8 @@

Security Assessment Overview

- - + + @@ -7357,8 +7357,8 @@

Security Assessment Overview

- - + + @@ -7368,8 +7368,8 @@

Security Assessment Overview

- - + + @@ -7379,8 +7379,8 @@

Security Assessment Overview

- - + + @@ -7390,8 +7390,8 @@

Security Assessment Overview

- - + + @@ -7401,8 +7401,8 @@

Security Assessment Overview

- - + + @@ -7412,8 +7412,8 @@

Security Assessment Overview

- - + + @@ -7423,8 +7423,8 @@

Security Assessment Overview

- - + + @@ -7434,8 +7434,8 @@

Security Assessment Overview

- - + + @@ -7445,8 +7445,8 @@

Security Assessment Overview

- - + + @@ -7456,8 +7456,8 @@

Security Assessment Overview

- - + + @@ -7467,8 +7467,8 @@

Security Assessment Overview

- - + + @@ -7478,8 +7478,8 @@

Security Assessment Overview

- - + + @@ -7489,8 +7489,8 @@

Security Assessment Overview

- - + + @@ -7504,8 +7504,8 @@

Security Assessment Overview

- - + + @@ -7515,19 +7515,19 @@

Security Assessment Overview

- - + + - + - - + + @@ -7537,8 +7537,8 @@

Security Assessment Overview

- - + + @@ -7548,8 +7548,8 @@

Security Assessment Overview

- - + + @@ -7559,8 +7559,8 @@

Security Assessment Overview

- - + + @@ -7570,8 +7570,8 @@

Security Assessment Overview

- - + + @@ -7581,8 +7581,8 @@

Security Assessment Overview

- - + + @@ -7592,8 +7592,8 @@

Security Assessment Overview

- - + + @@ -7603,8 +7603,8 @@

Security Assessment Overview

- - + + @@ -7614,8 +7614,8 @@

Security Assessment Overview

- - + + @@ -7625,8 +7625,8 @@

Security Assessment Overview

- - + + @@ -7636,8 +7636,8 @@

Security Assessment Overview

- - + + @@ -7647,8 +7647,8 @@

Security Assessment Overview

- - + + @@ -7658,8 +7658,8 @@

Security Assessment Overview

- - + + @@ -7669,8 +7669,8 @@

Security Assessment Overview

- - + + @@ -7680,8 +7680,8 @@

Security Assessment Overview

- - + + @@ -7691,8 +7691,8 @@

Security Assessment Overview

- - + + @@ -7702,8 +7702,8 @@

Security Assessment Overview

- - + + @@ -7713,8 +7713,8 @@

Security Assessment Overview

- - + + @@ -7724,8 +7724,8 @@

Security Assessment Overview

- - + + @@ -7735,8 +7735,8 @@

Security Assessment Overview

- - + + @@ -7746,8 +7746,8 @@

Security Assessment Overview

- - + + @@ -7757,8 +7757,8 @@

Security Assessment Overview

- - + + @@ -7768,8 +7768,8 @@

Security Assessment Overview

- - + + @@ -7779,8 +7779,8 @@

Security Assessment Overview

- - + + @@ -7790,8 +7790,8 @@

Security Assessment Overview

- - + + @@ -7801,8 +7801,8 @@

Security Assessment Overview

- - + + @@ -7812,8 +7812,8 @@

Security Assessment Overview

- - + + @@ -7823,8 +7823,8 @@

Security Assessment Overview

- - + + @@ -7834,8 +7834,8 @@

Security Assessment Overview

- - + + @@ -7845,8 +7845,8 @@

Security Assessment Overview

- - + + @@ -7856,8 +7856,8 @@

Security Assessment Overview

- - + + @@ -7867,8 +7867,8 @@

Security Assessment Overview

- - + + @@ -7879,8 +7879,8 @@

Security Assessment Overview

- - + + @@ -7890,8 +7890,8 @@

Security Assessment Overview

- - + + @@ -7901,8 +7901,8 @@

Security Assessment Overview

- - + + @@ -7912,8 +7912,8 @@

Security Assessment Overview

- - + + @@ -7923,8 +7923,8 @@

Security Assessment Overview

- - + + @@ -7934,8 +7934,8 @@

Security Assessment Overview

- - + + @@ -7945,8 +7945,8 @@

Security Assessment Overview

- - + + @@ -7956,8 +7956,8 @@

Security Assessment Overview

- - + + @@ -7967,8 +7967,8 @@

Security Assessment Overview

- - + + @@ -7978,8 +7978,8 @@

Security Assessment Overview

- - + + @@ -7989,8 +7989,8 @@

Security Assessment Overview

- - + + @@ -8000,8 +8000,8 @@

Security Assessment Overview

- - + + @@ -8011,8 +8011,8 @@

Security Assessment Overview

- - + + @@ -8022,8 +8022,8 @@

Security Assessment Overview

- - + + @@ -8033,8 +8033,8 @@

Security Assessment Overview

- - + + @@ -8044,8 +8044,8 @@

Security Assessment Overview

- - + + @@ -8055,8 +8055,8 @@

Security Assessment Overview

- - + + @@ -8070,8 +8070,8 @@

Security Assessment Overview

- - + + @@ -8081,19 +8081,19 @@

Security Assessment Overview

- - + + - + - - + + @@ -8103,8 +8103,8 @@

Security Assessment Overview

- - + + @@ -8114,8 +8114,8 @@

Security Assessment Overview

- - + + @@ -8125,8 +8125,8 @@

Security Assessment Overview

- - + + @@ -8136,8 +8136,8 @@

Security Assessment Overview

- - + + @@ -8147,8 +8147,8 @@

Security Assessment Overview

- - + + @@ -8158,8 +8158,8 @@

Security Assessment Overview

- - + + @@ -8169,8 +8169,8 @@

Security Assessment Overview

- - + + @@ -8180,8 +8180,8 @@

Security Assessment Overview

- - + + @@ -8191,8 +8191,8 @@

Security Assessment Overview

- - + + @@ -8202,8 +8202,8 @@

Security Assessment Overview

- - + + @@ -8213,8 +8213,8 @@

Security Assessment Overview

- - + + @@ -8224,8 +8224,8 @@

Security Assessment Overview

- - + + @@ -8235,8 +8235,8 @@

Security Assessment Overview

- - + + @@ -8246,8 +8246,8 @@

Security Assessment Overview

- - + + @@ -8257,8 +8257,8 @@

Security Assessment Overview

- - + + @@ -8268,8 +8268,8 @@

Security Assessment Overview

- - + + @@ -8279,8 +8279,8 @@

Security Assessment Overview

- - + + @@ -8290,8 +8290,8 @@

Security Assessment Overview

- - + + @@ -8301,8 +8301,8 @@

Security Assessment Overview

- - + + @@ -8312,8 +8312,8 @@

Security Assessment Overview

- - + + @@ -8323,8 +8323,8 @@

Security Assessment Overview

- - + + @@ -8334,8 +8334,8 @@

Security Assessment Overview

- - + + @@ -8345,8 +8345,8 @@

Security Assessment Overview

- - + + @@ -8356,8 +8356,8 @@

Security Assessment Overview

- - + + @@ -8367,8 +8367,8 @@

Security Assessment Overview

- - + + @@ -8378,8 +8378,8 @@

Security Assessment Overview

- - + + @@ -8389,8 +8389,8 @@

Security Assessment Overview

- - + + @@ -8400,8 +8400,8 @@

Security Assessment Overview

- - + + @@ -8411,8 +8411,8 @@

Security Assessment Overview

- - + + @@ -8422,8 +8422,8 @@

Security Assessment Overview

- - + + @@ -8433,8 +8433,8 @@

Security Assessment Overview

- - + + @@ -8444,8 +8444,8 @@

Security Assessment Overview

- - + + @@ -8459,8 +8459,8 @@

Security Assessment Overview

- - + + @@ -8470,19 +8470,19 @@

Security Assessment Overview

- - + + - + - - + + @@ -8492,8 +8492,8 @@

Security Assessment Overview

- - + + @@ -8503,8 +8503,8 @@

Security Assessment Overview

- - + + @@ -8514,8 +8514,8 @@

Security Assessment Overview

- - + + @@ -8536,7 +8536,7 @@

Overall
21.3%
57 of 268 actionable checks

Risk by Account

-
193047247167
146
42 High · 101 Med · 3 Low
230266523520
14
0 High · 14 Med · 0 Low
472057511786
45
10 High · 34 Med · 1 Low
+
111111111111
146
42 High · 101 Med · 3 Low
222222222222
14
0 High · 14 Med · 0 Low
333333333333
45
10 High · 34 Med · 1 Low

Risk by Region

ap-southeast-2
0
0 High · 0 Med · 0 Low
eu-west-1
0
0 High · 0 Med · 0 Low
us-east-1
80
28 High · 48 Med · 4 Low

Findings by Service

@@ -8551,14 +8551,14 @@

Amazon Bedrock Findings
-
+
-

Account IDRegionCheck IDFindingDetailsResolutionReferenceSeverityStatus
111111111111 ap-southeast-2 AC-01 AgentCore VPC Configuration Check Informational N/A
193047247167
111111111111 ap-southeast-2 AC-04 AgentCore Observability Check Informational N/A
193047247167
111111111111 ap-southeast-2 AC-05 AgentCore Encryption Check Informational N/A
193047247167
111111111111 ap-southeast-2 AC-06 AgentCore Browser Tool Recording Check Informational N/A
193047247167
111111111111 ap-southeast-2 AC-07 AgentCore Memory Configuration Check Informational N/A
193047247167
111111111111 ap-southeast-2 AC-13 AgentCore Gateway Configuration Check Informational N/A
193047247167
111111111111 ap-southeast-2 AC-08 AgentCore VPC Endpoints Check Informational N/A
193047247167
111111111111 ap-southeast-2 AC-10 AgentCore Resource-Based Policies Check Informational N/A
193047247167
111111111111 ap-southeast-2 AC-11 AgentCore Policy Engine Encryption Check Informational N/A
193047247167
111111111111 ap-southeast-2 AC-12 AgentCore Gateway Encryption Check Informational N/A
193047247167
111111111111 ap-southeast-2 SM-01 SageMaker Internet Access Check Informational N/A
193047247167
111111111111 ap-southeast-2 SM-02 SageMaker SSO Configuration Check Medium Passed
193047247167
111111111111 ap-southeast-2 SM-03 Data Protection Check Informational N/A
193047247167
111111111111 ap-southeast-2 SM-04 GuardDuty Enabled Medium Passed
193047247167
111111111111 ap-southeast-2 SM-05 SageMaker Model Registry Issue Informational N/A
193047247167
111111111111 ap-southeast-2 SM-05 SageMaker Feature Store Issue Informational N/A
193047247167
111111111111 ap-southeast-2 SM-05 SageMaker Pipelines Issue Informational N/A
193047247167
111111111111 ap-southeast-2 SM-06 SageMaker Clarify No Clarify Usage Informational N/A
193047247167
111111111111 ap-southeast-2 SM-07 SageMaker Model Monitor No Model Monitoring Informational N/A
193047247167
111111111111 ap-southeast-2 SM-08 Model Registry Registry Not Used Informational N/A
193047247167
111111111111 ap-southeast-2 SM-09 SageMaker Notebook Root Access Check Informational N/A
193047247167
111111111111 ap-southeast-2 SM-10 SageMaker Notebook VPC Deployment Check Informational N/A
193047247167
111111111111 ap-southeast-2 SM-11 SageMaker Model Network Isolation Check Informational N/A
193047247167
111111111111 ap-southeast-2 SM-12 SageMaker Endpoint Instance Count Check Informational N/A
193047247167
111111111111 ap-southeast-2 SM-13 SageMaker Monitoring Network Isolation Check Informational N/A
193047247167
111111111111 ap-southeast-2 SM-14 SageMaker Model Repository Access Check Informational N/A
193047247167
111111111111 ap-southeast-2 SM-15 SageMaker Feature Store Encryption Check Informational N/A
193047247167
111111111111 ap-southeast-2 SM-16 SageMaker Data Quality Job Encryption Check Informational N/A
193047247167
111111111111 ap-southeast-2 SM-17 SageMaker Processing Job Encryption Check Informational N/A
193047247167
111111111111 ap-southeast-2 SM-18 SageMaker Transform Job Encryption Check Informational N/A
193047247167
111111111111 ap-southeast-2 SM-19 SageMaker Hyperparameter Tuning Job Encryption Check Informational N/A
193047247167
111111111111 ap-southeast-2 SM-20 SageMaker Compilation Job Encryption Check Informational N/A
193047247167
111111111111 ap-southeast-2 SM-21 SageMaker AutoML Job Network Isolation Check Informational N/A
193047247167
111111111111 ap-southeast-2 SM-22 Model Approval Workflow Check Informational N/A
193047247167
111111111111 ap-southeast-2 SM-23 Model Drift Detection Check Medium Passed
193047247167
111111111111 ap-southeast-2 SM-24 A/B Testing and Shadow Deployment Check Low Passed
193047247167
111111111111 ap-southeast-2 SM-25 ML Lineage Tracking - Experiments Not Used Informational N/A
193047247167
111111111111 Global AC-02 AgentCore IAM Full Access Policy High Failed
193047247167
111111111111 Global AC-02 AgentCore IAM Wildcard Permissions High Failed
193047247167
111111111111 Global AC-03 AgentCore Stale Access Medium Failed
193047247167
111111111111 Global AC-03 AgentCore Unused Permissions Medium Failed
193047247167
111111111111 Global AC-09 AgentCore Service-Linked Role Missing Medium Failed
193047247167
111111111111 us-east-1 AC-01 AgentCore Runtime VPC Configuration High Failed
193047247167
111111111111 us-east-1 AC-01 AgentCore Runtime VPC Configuration High Failed
193047247167
111111111111 us-east-1 AC-01 AgentCore Runtime VPC Configuration High Failed
193047247167
111111111111 us-east-1 AC-01 AgentCore Runtime VPC Configuration High Failed
193047247167
111111111111 us-east-1 AC-01 AgentCore Runtime VPC Configuration High Failed
193047247167
111111111111 us-east-1 AC-04 AgentCore Runtime CloudWatch Logs Medium Failed
193047247167
111111111111 us-east-1 AC-04 AgentCore Runtime X-Ray Tracing Medium Failed
193047247167
111111111111 us-east-1 AC-04 AgentCore Runtime CloudWatch Logs Medium Failed
193047247167
111111111111 us-east-1 AC-04 AgentCore Runtime X-Ray Tracing Medium Failed
193047247167
111111111111 us-east-1 AC-04 AgentCore Runtime CloudWatch Logs Medium Failed
193047247167
111111111111 us-east-1 AC-04 AgentCore Runtime X-Ray Tracing Medium Failed
193047247167
111111111111 us-east-1 AC-04 AgentCore Runtime CloudWatch Logs Medium Failed
193047247167
111111111111 us-east-1 AC-04 AgentCore Runtime X-Ray Tracing Medium Failed
193047247167
111111111111 us-east-1 AC-04 AgentCore Runtime CloudWatch Logs Medium Failed
193047247167
111111111111 us-east-1 AC-04 AgentCore Runtime X-Ray Tracing Medium Failed
193047247167
111111111111 us-east-1 AC-05 AgentCore ECR Repository AWS-Managed Keys Low Failed
193047247167
111111111111 us-east-1 AC-05 AgentCore ECR Repository AWS-Managed Keys Low Failed
193047247167
111111111111 us-east-1 AC-06 AgentCore Runtime Storage Configuration Medium Failed
193047247167
111111111111 us-east-1 AC-06 AgentCore Runtime Storage Configuration Medium Failed
193047247167
111111111111 us-east-1 AC-06 AgentCore Runtime Storage Configuration Medium Failed
193047247167
111111111111 us-east-1 AC-06 AgentCore Runtime Storage Configuration Medium Failed
193047247167
111111111111 us-east-1 AC-06 AgentCore Runtime Storage Configuration Medium Failed
193047247167
111111111111 us-east-1 AC-07 AgentCore Memory Encryption Medium Failed
193047247167
111111111111 us-east-1 AC-07 AgentCore Memory Encryption Medium Failed
193047247167
111111111111 us-east-1 AC-07 AgentCore Memory Encryption Medium Failed
193047247167
111111111111 us-east-1 AC-13 AgentCore Gateway Configuration Check Informational N/A
193047247167
111111111111 us-east-1 AC-08 AgentCore VPC Endpoints Missing High Failed
193047247167
111111111111 us-east-1 AC-10 AgentCore Resource-Based Policies Check Informational N/A
193047247167
111111111111 us-east-1 AC-11 AgentCore Policy Engine Encryption Check Informational N/A
193047247167
111111111111 us-east-1 AC-12 AgentCore Gateway Encryption Check Informational N/A
193047247167
111111111111 eu-west-1 AC-01 AgentCore VPC Configuration Check Informational N/A
193047247167
111111111111 eu-west-1 AC-04 AgentCore Observability Check Informational N/A
193047247167
111111111111 eu-west-1 AC-05 AgentCore Encryption Check Informational N/A
193047247167
111111111111 eu-west-1 AC-06 AgentCore Browser Tool Recording Check Informational N/A
193047247167
111111111111 eu-west-1 AC-07 AgentCore Memory Configuration Check Informational N/A
193047247167
111111111111 eu-west-1 AC-13 AgentCore Gateway Configuration Check Informational N/A
193047247167
111111111111 eu-west-1 AC-08 AgentCore VPC Endpoints Check Informational N/A
193047247167
111111111111 eu-west-1 AC-10 AgentCore Resource-Based Policies Check Informational N/A
193047247167
111111111111 eu-west-1 AC-11 AgentCore Policy Engine Encryption Check Informational N/A
193047247167
111111111111 eu-west-1 AC-12 AgentCore Gateway Encryption Check Informational N/A
193047247167
111111111111 Global BR-01 AmazonBedrockFullAccess role check High Failed
193047247167
111111111111 Global BR-01 AmazonBedrockFullAccess role check High Failed
193047247167
111111111111 Global BR-01 AmazonBedrockFullAccess role check High Failed
193047247167
111111111111 Global BR-03 Marketplace Subscription Access Check High Failed
193047247167
111111111111 Global BR-03 Marketplace Subscription Access Check High Failed
193047247167
111111111111 Global BR-03 Marketplace Subscription Access Check High Failed
193047247167
111111111111 Global BR-03 Marketplace Subscription Access Check High Failed
193047247167
111111111111 Global BR-03 Marketplace Subscription Access Check High Failed
193047247167
111111111111 Global BR-03 Marketplace Subscription Access Check High Failed
193047247167
111111111111 Global BR-03 Marketplace Subscription Access Check High Failed
193047247167
111111111111 Global BR-03 Marketplace Subscription Access Check High Failed
193047247167
111111111111 Global BR-03 Marketplace Subscription Access Check High Failed
193047247167
111111111111 Global BR-03 Marketplace Subscription Access Check High Failed
193047247167
111111111111 Global BR-03 Marketplace Subscription Access Check High Failed
193047247167
111111111111 Global BR-14 Stale Bedrock Access CheckRole '193047247167-us-east-1-kb-bedrock-service-role' last accessed Bedrock on 2025-12-22Role '111111111111-us-east-1-kb-bedrock-service-role' last accessed Bedrock on 2025-12-22 You can use last accessed information to refine your policies and allow access to only the services and actions that your IAM identities and policies use. This helps you to better adhere to the best practice of least privilege. Medium Failed
193047247167
111111111111 Global BR-14 Stale Bedrock Access CheckRole '193047247167-us-east-1-kb-setup-function-role' last accessed Bedrock on 2025-12-22Role '111111111111-us-east-1-kb-setup-function-role' last accessed Bedrock on 2025-12-22 You can use last accessed information to refine your policies and allow access to only the services and actions that your IAM identities and policies use. This helps you to better adhere to the best practice of least privilege. Medium Failed
193047247167
111111111111 Global BR-14 Stale Bedrock Access Check Medium Failed
193047247167
111111111111 Global BR-14 Stale Bedrock Access Check Medium Failed
193047247167
111111111111 Global BR-14 Stale Bedrock Access Check Medium Failed
193047247167
111111111111 Global BR-14 Stale Bedrock Access Check Medium Failed
193047247167
111111111111 Global BR-14 Stale Bedrock Access Check Medium Failed
193047247167
111111111111 Global BR-14 Stale Bedrock Access Check Medium Failed
193047247167
111111111111 Global BR-14 Stale Bedrock Access Check Medium Failed
193047247167
111111111111 Global BR-14 Stale Bedrock Access Check Medium Failed
193047247167
111111111111 Global BR-14 Stale Bedrock Access Check Medium Failed
193047247167
111111111111 Global BR-14 Stale Bedrock Access Check Medium Failed
193047247167
111111111111 Global BR-14 Stale Bedrock Access Check Medium Failed
193047247167
111111111111 Global BR-14 Stale Bedrock Access Check Medium Failed
193047247167
111111111111 Global BR-14 Stale Bedrock Access Check Medium Failed
193047247167
111111111111 Global BR-14 Stale Bedrock Access Check Medium Failed
193047247167
111111111111 Global BR-14 Stale Bedrock Access Check Medium Failed
193047247167
111111111111 Global BR-14 Stale Bedrock Access Check Medium Failed
193047247167
111111111111 Global BR-14 Stale Bedrock Access Check Medium Failed
193047247167
111111111111 Global BR-14 Stale Bedrock Access Check Medium Failed
193047247167
111111111111 Global BR-14 Stale Bedrock Access Check Medium Failed
193047247167
111111111111 Global BR-14 Stale Bedrock Access Check Medium Failed
193047247167
111111111111 Global BR-14 Stale Bedrock Access Check Medium Failed
193047247167
111111111111 Global BR-14 Stale Bedrock Access Check Medium Failed
193047247167
111111111111 Global BR-14 Stale Bedrock Access Check Medium Failed
193047247167
111111111111 Global BR-14 Stale Bedrock Access Check Medium Failed
193047247167
111111111111 Global BR-14 Stale Bedrock Access CheckRole 'cdk-hnb659fds-lookup-role-193047247167-us-east-1' last accessed Bedrock on neverRole 'cdk-hnb659fds-lookup-role-111111111111-us-east-1' last accessed Bedrock on never You can use last accessed information to refine your policies and allow access to only the services and actions that your IAM identities and policies use. This helps you to better adhere to the best practice of least privilege. Medium Failed
193047247167
111111111111 Global BR-14 Stale Bedrock Access CheckRole 'cdk-hnb659fds-lookup-role-193047247167-us-west-2' last accessed Bedrock on neverRole 'cdk-hnb659fds-lookup-role-111111111111-us-west-2' last accessed Bedrock on never You can use last accessed information to refine your policies and allow access to only the services and actions that your IAM identities and policies use. This helps you to better adhere to the best practice of least privilege. Medium Failed
193047247167
111111111111 Global BR-14 Stale Bedrock Access Check Medium Failed
193047247167
111111111111 Global BR-14 Stale Bedrock Access Check Medium Failed
193047247167
111111111111 Global BR-14 Stale Bedrock Access Check Medium Failed
193047247167
111111111111 Global BR-14 Stale Bedrock Access Check Medium Failed
193047247167
111111111111 Global BR-14 Stale Bedrock Access Check Medium Failed
193047247167
111111111111 Global BR-14 Stale Bedrock Access Check Medium Failed
193047247167
111111111111 Global BR-14 Stale Bedrock Access Check Medium Failed
193047247167
111111111111 Global BR-14 Stale Bedrock Access Check Medium Failed
193047247167
111111111111 Global BR-14 Stale Bedrock Access Check Medium Failed
193047247167
111111111111 Global BR-14 Stale Bedrock Access Check Medium Failed
193047247167
111111111111 Global BR-14 Stale Bedrock Access Check Medium Failed
193047247167
111111111111 Global BR-14 Stale Bedrock Access Check Medium Failed
193047247167
111111111111 Global BR-14 Stale Bedrock Access Check Medium Failed
193047247167
111111111111 Global BR-14 Stale Bedrock Access Check Medium Failed
193047247167
111111111111 Global BR-14 Stale Bedrock Access Check Medium Failed
193047247167
111111111111 Global BR-14 Stale Bedrock Access Check Medium Failed
193047247167
111111111111 Global BR-14 Stale Bedrock Access Check Medium Failed
193047247167
111111111111 Global BR-14 Stale Bedrock Access Check Medium Failed
193047247167
111111111111 Global BR-14 Stale Bedrock Access Check Medium Failed
193047247167
111111111111 Global BR-14 Stale Bedrock Access Check Medium Failed
193047247167
111111111111 Global BR-14 Stale Bedrock Access Check Medium Failed
193047247167
111111111111 Global BR-14 Stale Bedrock Access Check Medium Failed
193047247167
111111111111 Global BR-14 Stale Bedrock Access Check Medium Failed
193047247167
111111111111 Global BR-14 Stale Bedrock Access Check Medium Failed
193047247167
111111111111 Global BR-14 Stale Bedrock Access Check Medium Failed
193047247167
111111111111 Global BR-14 Stale Bedrock Access Check Medium Failed
193047247167
111111111111 Global BR-14 Stale Bedrock Access Check Medium Failed
193047247167
111111111111 Global BR-14 Stale Bedrock Access Check Medium Failed
193047247167
111111111111 Global BR-14 Stale Bedrock Access Check Medium Failed
193047247167
111111111111 Global BR-14 Stale Bedrock Access Check Medium Failed
193047247167
111111111111 Global BR-14 Stale Bedrock Access Check Medium Failed
193047247167
111111111111 Global BR-14 Stale Bedrock Access Check Medium Failed
193047247167
111111111111 Global BR-14 Stale Bedrock Access Check Medium Failed
193047247167
111111111111 Global BR-14 Stale Bedrock Access Check Medium Failed
193047247167
111111111111 us-east-1 BR-02 Amazon Bedrock private connectivity check Informational N/A
193047247167
111111111111 us-east-1 BR-04 Bedrock Model Invocation Logging Check Informational N/A
193047247167
111111111111 us-east-1 BR-05 Bedrock Guardrails Check Informational N/A
193047247167
111111111111 us-east-1 BR-06 Bedrock CloudTrail Logging Check Informational N/A
193047247167
111111111111 us-east-1 BR-07 Bedrock Prompt Management Check Informational N/A
193047247167
111111111111 us-east-1 BR-08 Bedrock Agent IAM Roles Check Informational N/A
193047247167
111111111111 us-east-1 BR-09 Bedrock Knowledge Base Encryption CheckUnable to check Bedrock Knowledge Base API: An error occurred (AccessDeniedException) when calling the ListKnowledgeBases operation: User: arn:aws:sts::193047247167:assumed-role/aiml-security-19304724716-BedrockSecurityAssessment-vv6H0eGD9ESX/aiml-security-aiml-security-193047247167-BedrockAssessment is not authorized to perform: bedrock:ListKnowledgeBases on resource: arn:aws:bedrock:us-east-1:193047247167:knowledge-base/* because no identity-based policy allows the bedrock:ListKnowledgeBases actionUnable to check Bedrock Knowledge Base API: An error occurred (AccessDeniedException) when calling the ListKnowledgeBases operation: User: arn:aws:sts::111111111111:assumed-role/aiml-security-19304724716-BedrockSecurityAssessment-vv6H0eGD9ESX/aiml-security-aiml-security-111111111111-BedrockAssessment is not authorized to perform: bedrock:ListKnowledgeBases on resource: arn:aws:bedrock:us-east-1:111111111111:knowledge-base/* because no identity-based policy allows the bedrock:ListKnowledgeBases action Verify your AWS credentials and permissions to access Bedrock Knowledge Bases, then retry the assessment. Informational N/A
193047247167
111111111111 us-east-1 BR-10 Bedrock Guardrail IAM Enforcement Check Informational N/A
193047247167
111111111111 us-east-1 BR-11 Bedrock Custom Model Encryption Check Informational N/A
193047247167
111111111111 us-east-1 BR-12 Bedrock Invocation Log Encryption Check Informational N/A
193047247167
111111111111 us-east-1 BR-13 Bedrock Flows Guardrails Check Informational N/A
193047247167
111111111111 Global SM-02 SageMaker Full Access Policy Used High Failed
193047247167
111111111111 Global SM-02 SageMaker Full Access Policy Used High Failed
193047247167
111111111111 Global SM-02 SageMaker Full Access Policy Used High Failed
193047247167
111111111111 Global SM-02 SageMaker Full Access Policy Used High Failed
193047247167
111111111111 Global SM-02 SageMaker Full Access Policy Used High Failed
193047247167
111111111111 Global SM-02 SageMaker Full Access Policy Used High Failed
193047247167
111111111111 Global SM-02 SageMaker Full Access Policy Used High Failed
193047247167
111111111111 us-east-1 SM-01 Non-VPC Only Network Access High Failed
193047247167
111111111111 us-east-1 SM-02 SSO Not Properly Configured Medium Failed
193047247167
111111111111 us-east-1 SM-03 Missing Encryption Configuration High Failed
193047247167
111111111111 us-east-1 SM-04 GuardDuty Enabled Medium Passed
193047247167
111111111111 us-east-1 SM-05 SageMaker Model Registry Issue Informational N/A
193047247167
111111111111 us-east-1 SM-05 SageMaker Feature Store Issue Informational N/A
193047247167
111111111111 us-east-1 SM-05 SageMaker Pipelines Issue Informational N/A
193047247167
111111111111 us-east-1 SM-06 SageMaker Clarify No Clarify Usage Informational N/A
193047247167
111111111111 us-east-1 SM-07 SageMaker Model Monitor No Model Monitoring Informational N/A
193047247167
111111111111 us-east-1 SM-08 Model Registry Registry Not Used Informational N/A
193047247167
111111111111 us-east-1 SM-09 SageMaker Notebook Root Access Check Informational N/A
193047247167
111111111111 us-east-1 SM-10 SageMaker Notebook VPC Deployment Check Informational N/A
193047247167
111111111111 us-east-1 SM-11 SageMaker Model Network Isolation Check Informational N/A
193047247167
111111111111 us-east-1 SM-12 SageMaker Endpoint Instance Count Check Informational N/A
193047247167
111111111111 us-east-1 SM-13 SageMaker Monitoring Network Isolation Check Informational N/A
193047247167
111111111111 us-east-1 SM-14 SageMaker Model Repository Access Check Informational N/A
193047247167
111111111111 us-east-1 SM-15 SageMaker Feature Store Encryption Check Informational N/A
193047247167
111111111111 us-east-1 SM-16 SageMaker Data Quality Job Encryption Check Informational N/A
193047247167
111111111111 us-east-1 SM-17 SageMaker Processing Job Encryption Check Informational N/A
193047247167
111111111111 us-east-1 SM-18 SageMaker Transform Job Encryption Check Informational N/A
193047247167
111111111111 us-east-1 SM-19 SageMaker Hyperparameter Tuning Job Encryption Check Informational N/A
193047247167
111111111111 us-east-1 SM-20 SageMaker Compilation Job Encryption Check Informational N/A
193047247167
111111111111 us-east-1 SM-21 SageMaker AutoML Job Network Isolation Check Informational N/A
193047247167
111111111111 us-east-1 SM-22 Model Approval Workflow Check Informational N/A
193047247167
111111111111 us-east-1 SM-23 Model Drift Detection Check Medium Passed
193047247167
111111111111 us-east-1 SM-24 A/B Testing and Shadow Deployment Check Low Passed
193047247167
111111111111 us-east-1 SM-25 ML Lineage Tracking - Experiments Not Used Informational N/A
193047247167
111111111111 ap-southeast-2 BR-02 Amazon Bedrock private connectivity check Informational N/A
193047247167
111111111111 ap-southeast-2 BR-04 Bedrock Model Invocation Logging Check Informational N/A
193047247167
111111111111 ap-southeast-2 BR-05 Bedrock Guardrails Check Informational N/A
193047247167
111111111111 ap-southeast-2 BR-06 Bedrock CloudTrail Logging Check Informational N/A
193047247167
111111111111 ap-southeast-2 BR-07 Bedrock Prompt Management Check Informational N/A
193047247167
111111111111 ap-southeast-2 BR-08 Bedrock Agent IAM Roles Check Informational N/A
193047247167
111111111111 ap-southeast-2 BR-09 Bedrock Knowledge Base Encryption CheckUnable to check Bedrock Knowledge Base API: An error occurred (AccessDeniedException) when calling the ListKnowledgeBases operation: User: arn:aws:sts::193047247167:assumed-role/aiml-security-19304724716-BedrockSecurityAssessment-vv6H0eGD9ESX/aiml-security-aiml-security-193047247167-BedrockAssessment is not authorized to perform: bedrock:ListKnowledgeBases on resource: arn:aws:bedrock:ap-southeast-2:193047247167:knowledge-base/* because no identity-based policy allows the bedrock:ListKnowledgeBases actionUnable to check Bedrock Knowledge Base API: An error occurred (AccessDeniedException) when calling the ListKnowledgeBases operation: User: arn:aws:sts::111111111111:assumed-role/aiml-security-19304724716-BedrockSecurityAssessment-vv6H0eGD9ESX/aiml-security-aiml-security-111111111111-BedrockAssessment is not authorized to perform: bedrock:ListKnowledgeBases on resource: arn:aws:bedrock:ap-southeast-2:111111111111:knowledge-base/* because no identity-based policy allows the bedrock:ListKnowledgeBases action Verify your AWS credentials and permissions to access Bedrock Knowledge Bases, then retry the assessment. Informational N/A
193047247167
111111111111 ap-southeast-2 BR-10 Bedrock Guardrail IAM Enforcement Check Informational N/A
193047247167
111111111111 ap-southeast-2 BR-11 Bedrock Custom Model Encryption Check Informational N/A
193047247167
111111111111 ap-southeast-2 BR-12 Bedrock Invocation Log Encryption Check Informational N/A
193047247167
111111111111 ap-southeast-2 BR-13 Bedrock Flows Guardrails Check Informational N/A
193047247167
111111111111 eu-west-1 BR-02 Amazon Bedrock private connectivity check Informational N/A
193047247167
111111111111 eu-west-1 BR-04 Bedrock Model Invocation Logging Check Informational N/A
193047247167
111111111111 eu-west-1 BR-05 Bedrock Guardrails Check Informational N/A
193047247167
111111111111 eu-west-1 BR-06 Bedrock CloudTrail Logging Check Informational N/A
193047247167
111111111111 eu-west-1 BR-07 Bedrock Prompt Management Check Informational N/A
193047247167
111111111111 eu-west-1 BR-08 Bedrock Agent IAM Roles Check Informational N/A
193047247167
111111111111 eu-west-1 BR-09 Bedrock Knowledge Base Encryption CheckUnable to check Bedrock Knowledge Base API: An error occurred (AccessDeniedException) when calling the ListKnowledgeBases operation: User: arn:aws:sts::193047247167:assumed-role/aiml-security-19304724716-BedrockSecurityAssessment-vv6H0eGD9ESX/aiml-security-aiml-security-193047247167-BedrockAssessment is not authorized to perform: bedrock:ListKnowledgeBases on resource: arn:aws:bedrock:eu-west-1:193047247167:knowledge-base/* because no identity-based policy allows the bedrock:ListKnowledgeBases actionUnable to check Bedrock Knowledge Base API: An error occurred (AccessDeniedException) when calling the ListKnowledgeBases operation: User: arn:aws:sts::111111111111:assumed-role/aiml-security-19304724716-BedrockSecurityAssessment-vv6H0eGD9ESX/aiml-security-aiml-security-111111111111-BedrockAssessment is not authorized to perform: bedrock:ListKnowledgeBases on resource: arn:aws:bedrock:eu-west-1:111111111111:knowledge-base/* because no identity-based policy allows the bedrock:ListKnowledgeBases action Verify your AWS credentials and permissions to access Bedrock Knowledge Bases, then retry the assessment. Informational N/A
193047247167
111111111111 eu-west-1 BR-10 Bedrock Guardrail IAM Enforcement Check Informational N/A
193047247167
111111111111 eu-west-1 BR-11 Bedrock Custom Model Encryption Check Informational N/A
193047247167
111111111111 eu-west-1 BR-12 Bedrock Invocation Log Encryption Check Informational N/A
193047247167
111111111111 eu-west-1 BR-13 Bedrock Flows Guardrails Check Informational N/A
193047247167
111111111111 eu-west-1 SM-01 SageMaker Internet Access Check Informational N/A
193047247167
111111111111 eu-west-1 SM-02 SageMaker SSO Configuration Check Medium Passed
193047247167
111111111111 eu-west-1 SM-03 Data Protection Check Informational N/A
193047247167
111111111111 eu-west-1 SM-04 GuardDuty Enabled Medium Passed
193047247167
111111111111 eu-west-1 SM-05 SageMaker Model Registry Issue Informational N/A
193047247167
111111111111 eu-west-1 SM-05 SageMaker Feature Store Issue Informational N/A
193047247167
111111111111 eu-west-1 SM-05 SageMaker Pipelines Issue Informational N/A
193047247167
111111111111 eu-west-1 SM-06 SageMaker Clarify No Clarify Usage Informational N/A
193047247167
111111111111 eu-west-1 SM-07 SageMaker Model Monitor No Model Monitoring Informational N/A
193047247167
111111111111 eu-west-1 SM-08 Model Registry Registry Not Used Informational N/A
193047247167
111111111111 eu-west-1 SM-09 SageMaker Notebook Root Access Check Informational N/A
193047247167
111111111111 eu-west-1 SM-10 SageMaker Notebook VPC Deployment Check Informational N/A
193047247167
111111111111 eu-west-1 SM-11 SageMaker Model Network Isolation Check Informational N/A
193047247167
111111111111 eu-west-1 SM-12 SageMaker Endpoint Instance Count Check Informational N/A
193047247167
111111111111 eu-west-1 SM-13 SageMaker Monitoring Network Isolation Check Informational N/A
193047247167
111111111111 eu-west-1 SM-14 SageMaker Model Repository Access Check Informational N/A
193047247167
111111111111 eu-west-1 SM-15 SageMaker Feature Store Encryption Check Informational N/A
193047247167
111111111111 eu-west-1 SM-16 SageMaker Data Quality Job Encryption Check Informational N/A
193047247167
111111111111 eu-west-1 SM-17 SageMaker Processing Job Encryption Check Informational N/A
193047247167
111111111111 eu-west-1 SM-18 SageMaker Transform Job Encryption Check Informational N/A
193047247167
111111111111 eu-west-1 SM-19 SageMaker Hyperparameter Tuning Job Encryption Check Informational N/A
193047247167
111111111111 eu-west-1 SM-20 SageMaker Compilation Job Encryption Check Informational N/A
193047247167
111111111111 eu-west-1 SM-21 SageMaker AutoML Job Network Isolation Check Informational N/A
193047247167
111111111111 eu-west-1 SM-22 Model Approval Workflow Check Informational N/A
193047247167
111111111111 eu-west-1 SM-23 Model Drift Detection Check Medium Passed
193047247167
111111111111 eu-west-1 SM-24 A/B Testing and Shadow Deployment Check Low Passed
193047247167
111111111111 eu-west-1 SM-25 ML Lineage Tracking - Experiments Not Used Informational N/A
193047247167
111111111111 us-east-1 FS-01 AWS Shield Advanced Not Enabled Low Failed
193047247167
111111111111 us-east-1 FS-01 No Regional WAF Web ACLs Found Medium Failed
193047247167
111111111111 us-east-1 FS-02 API Gateway Usage Plans Missing Throttle Medium Failed
193047247167
111111111111 us-east-1 FS-03 Bedrock Token Quotas At Default Medium N/A
193047247167
111111111111 us-east-1 FS-04 No Cost Anomaly Detection Monitors Medium Failed
193047247167
111111111111 us-east-1 FS-05 No Bedrock CloudWatch Alarms Found Medium Failed
193047247167
111111111111 us-east-1 FS-06 No AI/ML Service Budgets Configured Medium Failed
193047247167
111111111111 us-east-1 FS-07 Agent Action Boundary Check Informational N/A
193047247167
111111111111 us-east-1 FS-08 AgentCore Runtimes Missing Policy Engine High Failed
193047247167
111111111111 us-east-1 FS-09 Agent Lambda Functions Without Concurrency LimitsAgent-related Lambda functions without reserved concurrency: aiml-security-aiml-security-193047247167-FinServAssessment, resco-aiml-IAMPermissionCaching, aiml-security-aiml-security-193047247167-SagemakerAssessment, resco-aiml-CleanupBucket, aiml-security-aiml-security-193047247167-BedrockAssessment, resco-aiml-BedrockAssessment, aiml-security-aiml-security-193047247167-CleanupBucket, aiml-security-aiml-security-193047247167-AgentCoreAssessment, e2ebedrockrag-OSSInfraStack-BKBOSSInfraSetupLambda-031La8JAQXtk, e2ebedrockrag-OSSInfraSta-OSSIndexCreationProvider-g56en9UzRjII. Unlimited concurrency allows runaway agent loops to exhaust account limits.Agent-related Lambda functions without reserved concurrency: aiml-security-aiml-security-111111111111-FinServAssessment, resco-aiml-IAMPermissionCaching, aiml-security-aiml-security-111111111111-SagemakerAssessment, resco-aiml-CleanupBucket, aiml-security-aiml-security-111111111111-BedrockAssessment, resco-aiml-BedrockAssessment, aiml-security-aiml-security-111111111111-CleanupBucket, aiml-security-aiml-security-111111111111-AgentCoreAssessment, e2ebedrockrag-OSSInfraStack-BKBOSSInfraSetupLambda-031La8JAQXtk, e2ebedrockrag-OSSInfraSta-OSSIndexCreationProvider-g56en9UzRjII. Unlimited concurrency allows runaway agent loops to exhaust account limits. 1. Set reserved concurrency on agent Lambda functions. 2. Implement maximum iteration counts in agent orchestration logic. 3. Use Step Functions with MaxConcurrency and timeout states. @@ -3205,8 +3205,8 @@

Security Assessment Overview

Medium Failed
193047247167
111111111111 us-east-1 FS-10 Human-in-the-Loop Check — No Agent Workflows Found Informational N/A
193047247167
111111111111 us-east-1 FS-11 No Agent Rate Alarms Found Medium Failed
193047247167
111111111111 us-east-1 FS-12 No Bedrock-Scoped SCPs Found High Failed
193047247167
111111111111 us-east-1 FS-13 Model Provenance Tags Present Medium Passed
193047247167
111111111111 us-east-1 FS-14 Model Governance Config Rules Present Medium Passed
193047247167
111111111111 us-east-1 FS-15 No Bedrock Evaluation Jobs Found Medium Failed
193047247167
111111111111 us-east-1 FS-16 ECR Repositories Without Image Scanning4 ECR repo(s) without scan-on-push: mlexplorationrepo, cdk-hnb659fds-container-assets-193047247167-us-east-1, bedrock-agentcore-customer_support_agent, bedrock-agentcore-origami_expeditions.4 ECR repo(s) without scan-on-push: mlexplorationrepo, cdk-hnb659fds-container-assets-111111111111-us-east-1, bedrock-agentcore-customer_support_agent, bedrock-agentcore-origami_expeditions. Enable scan-on-push for all ECR repositories containing model containers. Consider enabling Enhanced Scanning (Inspector) for CVE detection. High Failed
193047247167
111111111111 us-east-1 FS-20 No SageMaker Feature Groups Found Informational N/A
193047247167
111111111111 us-east-1 FS-21 Training Data Buckets Without Versioning13 training data bucket(s) without versioning: ancbedrocklogging, bedrock-agentcore-codebuild-sources-193047247167-us-east-1, bedrock-bda-us-east-1-dda43109-6557-48bb-993d-3f97126b64b4, bedrock-bda-us-east-1-logging-00719114-debd-4487-85d1-09cbc3fc8, bedrock-kb-bucket-f736570b, bedrock-video-generation-us-east-1-h5ltpm, fsi-genai-workshop-bedrock-datasources-193047247167-us-west-2, knowledgebase-bedrock-agent-agasthik, llmevaluationpromptfoo-bedrockkb-cozhbzbrcmd2, sagemaker-studio-193047247167-huo1mvme4t.13 training data bucket(s) without versioning: ancbedrocklogging, bedrock-agentcore-codebuild-sources-111111111111-us-east-1, bedrock-bda-us-east-1-dda43109-6557-48bb-993d-3f97126b64b4, bedrock-bda-us-east-1-logging-00719114-debd-4487-85d1-09cbc3fc8, bedrock-kb-bucket-f736570b, bedrock-video-generation-us-east-1-h5ltpm, fsi-genai-workshop-bedrock-datasources-111111111111-us-west-2, knowledgebase-bedrock-agent-agasthik, llmevaluationpromptfoo-bedrockkb-cozhbzbrcmd2, sagemaker-studio-111111111111-huo1mvme4t. Enable S3 versioning on all training data buckets. Consider enabling MFA Delete for additional protection against poisoning. High Failed
193047247167
111111111111 us-east-1 FS-22 Overly Permissive Knowledge Base IAM Roles 722 role(s) with wildcard KB permissions: -- Role '193047247167-us-east-1-kb-setup-function-role' allows 'bedrock:CreateKnowledgeBase' on Resource '*' (no ARN scoping to specific Knowledge Bases) -- Role '193047247167-us-east-1-kb-setup-function-role' allows 'bedrock:CreateDataSource' on Resource '*' (no ARN scoping to specific Knowledge Bases) +- Role '111111111111-us-east-1-kb-setup-function-role' allows 'bedrock:CreateKnowledgeBase' on Resource '*' (no ARN scoping to specific Knowledge Bases) +- Role '111111111111-us-east-1-kb-setup-function-role' allows 'bedrock:CreateDataSource' on Resource '*' (no ARN scoping to specific Knowledge Bases) - Role 'Admin' allows '*' - Role 'agentcore-wildrydes_gateway_role_ab3991f6-role' allows 'bedrock:*' - Role 'AgentCoreEvalsSDK-us-east-1-d04ba7b68b' allows 'bedrock:InvokeModel' on Resource '*' (no ARN scoping to specific Knowledge Bases) @@ -3332,8 +3332,8 @@

Security Assessment Overview

High Failed
193047247167
111111111111 us-east-1 FS-24 ADVISORY: Knowledge Base Metadata Filtering — Manual Review Required Informational N/A
193047247167
111111111111 us-east-1 FS-25 OpenSearch Serverless Encryption Policies Present High Passed
193047247167
111111111111 us-east-1 FS-26 OpenSearch Serverless Collections Not VPC-Restricted High Failed
193047247167
111111111111 us-east-1 FS-27 No Guardrails — Contextual Grounding Not Applicable Informational N/A
193047247167
111111111111 us-east-1 FS-27 Automated Reasoning Policies — Access Check Low N/A
193047247167
111111111111 us-east-1 FS-28 No Guardrails — Denied Topics Not Applicable Informational N/A
193047247167
111111111111 us-east-1 FS-29 ADVISORY: Compliance Disclaimer — Manual Review Required Informational N/A
193047247167
111111111111 us-east-1 FS-30 ADVISORY: Compliance Dataset Coverage — Manual Review Required Informational N/A
193047247167
111111111111 us-east-1 FS-31 Knowledge Base Data Sources Past Review Threshold 2 data source(s) not synced in >7 days (a configurable review threshold, NOT an AWS-mandated limit): - KB 'knowledge-base-semiconductors' source 'knowledge-base-quick-start-qpvuv-data-source' last synced 702 days ago -- KB '193047247167-us-east-1-kb' source '193047247167-us-east-1-kb-datasource' last synced 180 days ago +- KB '111111111111-us-east-1-kb' source '111111111111-us-east-1-kb-datasource' last synced 180 days ago Confirm this age is acceptable for each data source's currency requirement — slow-changing reference data may legitimately sync infrequently. 1. Define the maximum acceptable data age per use case (e.g., intraday for market data, daily for product terms, weekly/monthly for regulatory guidance) and adjust the review threshold to match. 2. Configure automated sync (EventBridge Scheduler → StartIngestionJob) at that cadence — see FS-61. @@ -3447,8 +3447,8 @@

Security Assessment Overview

Medium Failed
193047247167
111111111111 us-east-1 FS-32 ADVISORY: Source Attribution — Manual Review Required Informational N/A
193047247167
111111111111 us-east-1 FS-33 KB Data Source Buckets Without VersioningKB data source S3 buckets without versioning: 193047247167-us-east-1-kb-data-bucket.KB data source S3 buckets without versioning: 111111111111-us-east-1-kb-data-bucket. Enable S3 versioning on all KB data source buckets. Enable S3 Object Integrity (checksum) for tamper detection. Medium Failed
193047247167
111111111111 us-east-1 FS-34 Legacy Foundation Models Available in Region Informational N/A
193047247167
111111111111 us-east-1 FS-35 ADVISORY: Harmful-Content Test Coverage — Manual Review Required Informational N/A
193047247167
111111111111 us-east-1 FS-36 No Guardrails — Content Filters Not Applicable Informational N/A
193047247167
111111111111 us-east-1 FS-37 ADVISORY: User Feedback Mechanism — Manual Review Required Informational N/A
193047247167
111111111111 us-east-1 FS-38 No Guardrails — Word Filters Not Applicable Informational N/A
193047247167
111111111111 us-east-1 FS-39 No SageMaker Clarify Bias Monitoring High Failed
193047247167
111111111111 us-east-1 FS-40 ADVISORY: Bias Dataset Coverage — Manual Review Required Informational N/A
193047247167
111111111111 us-east-1 FS-41 No SageMaker Clarify Explainability Monitoring High Failed
193047247167
111111111111 us-east-1 FS-42 No SageMaker Model Cards Found Medium Failed
193047247167
111111111111 us-east-1 FS-43 No CloudWatch Logs Data Protection Policies High Failed
193047247167
111111111111 us-east-1 FS-44 Amazon Macie Enabled High Passed
193047247167
111111111111 us-east-1 FS-45 No Guardrails — PII Filters Not Applicable Informational N/A
193047247167
111111111111 us-east-1 FS-46 AI/ML Buckets Without Data Classification Tags18 AI/ML bucket(s) without data-classification tags: 193047247167-us-east-1-kb-data-bucket, ancbedrocklogging, ancknowledgebase, aws-streaming-data-solut-outputaccesslogsbucket8b-1o7m0kb4bafm4, bedrock-agentcore-codebuild-sources-193047247167-us-east-1, bedrock-bda-us-east-1-dda43109-6557-48bb-993d-3f97126b64b4, bedrock-bda-us-east-1-logging-00719114-debd-4487-85d1-09cbc3fc8, bedrock-kb-bucket-f736570b, bedrock-video-generation-us-east-1-h5ltpm, create-customer-resources-kb-bucket-193047247167.18 AI/ML bucket(s) without data-classification tags: 111111111111-us-east-1-kb-data-bucket, ancbedrocklogging, ancknowledgebase, aws-streaming-data-solut-outputaccesslogsbucket8b-1o7m0kb4bafm4, bedrock-agentcore-codebuild-sources-111111111111-us-east-1, bedrock-bda-us-east-1-dda43109-6557-48bb-993d-3f97126b64b4, bedrock-bda-us-east-1-logging-00719114-debd-4487-85d1-09cbc3fc8, bedrock-kb-bucket-f736570b, bedrock-video-generation-us-east-1-h5ltpm, create-customer-resources-kb-bucket-111111111111. Tag all AI/ML data buckets with 'data-classification' key. Values: Public, Internal, Confidential, Restricted. Enforce via SCP or AWS Config rule. Medium Failed
193047247167
111111111111 us-east-1 FS-47 No Guardrails — Grounding Threshold Not Applicable Informational N/A
193047247167
111111111111 us-east-1 FS-48 Active Knowledge Bases for RAG Present Medium Passed
193047247167
111111111111 us-east-1 FS-49 ADVISORY: Hallucination Disclaimer — Manual Review Required Informational N/A
193047247167
111111111111 us-east-1 FS-50 No Guardrails With Relevance Grounding Filters Medium Failed
193047247167
111111111111 us-east-1 FS-51 No Guardrails — Prompt Attack Filters Not Applicable Informational N/A
193047247167
111111111111 us-east-1 FS-52 Bedrock Lambda Functions on Deprecated Runtimes Medium Failed
193047247167
111111111111 us-east-1 FS-53 No WAF Web ACLs — Injection Rules Not Applicable Informational N/A
193047247167
111111111111 us-east-1 FS-54 ADVISORY: Penetration Testing — Manual Review Required Informational N/A
193047247167
111111111111 us-east-1 FS-55 No Output Validation Functions Found Medium Failed
193047247167
111111111111 us-east-1 FS-56 No WAF ACLs — XSS Prevention Not Applicable Informational N/A
193047247167
111111111111 us-east-1 FS-57 ADVISORY: Output Encoding — Manual Review Required Informational N/A
193047247167
111111111111 us-east-1 FS-58 ADVISORY: Output Schema Validation — Manual Review Required Informational N/A
193047247167
111111111111 us-east-1 FS-59 No Guardrails — Topic Allowlist Not Applicable Informational N/A
193047247167
111111111111 us-east-1 FS-60 ADVISORY: Contextual Grounding for Off-Topic Prevention Informational N/A
193047247167
111111111111 us-east-1 FS-61 COULD NOT ASSESS: Knowledge Base Sync Schedule CheckThis check could not be completed (error: An error occurred (AccessDeniedException) when calling the ListSchedules operation: User: arn:aws:sts::193047247167:assumed-role/aiml-security-19304724716-FinServSecurityAssessment-G8d5dEiMJsZB/aiml-security-aiml-security-193047247167-FinServAssessment is not authorized to perform: scheduler:ListSchedules on resource: arn:aws:scheduler:us-east-1:193047247167:schedule/*/* because no identity-based policy allows the scheduler:ListSchedules action). The most common cause is a missing IAM permission for the assessment role; it may also indicate an unsupported region or an outdated botocore. This control was NOT assessed — verify the role's permissions and re-run, and assess this control manually until resolved.This check could not be completed (error: An error occurred (AccessDeniedException) when calling the ListSchedules operation: User: arn:aws:sts::111111111111:assumed-role/aiml-security-19304724716-FinServSecurityAssessment-G8d5dEiMJsZB/aiml-security-aiml-security-111111111111-FinServAssessment is not authorized to perform: scheduler:ListSchedules on resource: arn:aws:scheduler:us-east-1:111111111111:schedule/*/* because no identity-based policy allows the scheduler:ListSchedules action). The most common cause is a missing IAM permission for the assessment role; it may also indicate an unsupported region or an outdated botocore. This control was NOT assessed — verify the role's permissions and re-run, and assess this control manually until resolved. 1. Confirm the assessment role grants the actions this check requires (see the documented IAM permission set in the README). 2. Confirm the service/feature is supported in the assessed region. 3. Ensure botocore meets the version floor in requirements.txt. @@ -3827,8 +3827,8 @@

Security Assessment Overview

Low N/A
193047247167
111111111111 us-east-1 FS-62 ADVISORY: Data Currency Disclaimer — Manual Review Required Informational N/A
193047247167
111111111111 us-east-1 FS-63 Foundation Model Lifecycle Management Medium Passed
193047247167
111111111111 us-east-1 FS-65 KB Data Source Buckets Missing S3 Event Notifications The following KB data-source S3 buckets have no event notifications configured. Unauthorized document modifications will not be detected in real time: - semiconductor-demo-9999 -- 193047247167-us-east-1-kb-data-bucket 1. Enable Amazon EventBridge notifications on each KB data-source S3 bucket. 2. Create an EventBridge rule to route s3:ObjectCreated, s3:ObjectRemoved, and s3:ObjectModified events to an SNS topic or Lambda for alerting. 3. Integrate alerts into your security incident response workflow. Medium Failed
193047247167
111111111111 us-east-1 FS-66 AgentCore Runtimes Missing End-User Identity Propagation High Failed
193047247167
111111111111 us-east-1 FS-67 Agent Action-Group Lambdas May Lack Transaction Thresholds The following agent action-group Lambda functions have no environment variables whose names suggest transaction-value threshold configuration (this is a best-effort heuristic — a threshold enforced in code or in an AgentCore Policy Engine rule would not be detected here, so treat this as a prompt for manual verification rather than a definitive gap). Without explicit limits, agents could initiate unbounded financial transactions: -- aiml-security-aiml-security-193047247167-FinServAssessment -- aiml-security-aiml-security-193047247167-BedrockAssessment +- aiml-security-aiml-security-111111111111-FinServAssessment +- aiml-security-aiml-security-111111111111-BedrockAssessment - resco-aiml-BedrockAssessment -- aiml-security-aiml-security-193047247167-AgentCoreAssessment +- aiml-security-aiml-security-111111111111-AgentCoreAssessment - e2ebedrockrag-OSSInfraStack-BKBOSSInfraSetupLambda-031La8JAQXtk - e2ebedrockrag-OSSInfraSta-OSSIndexCreationProvider-g56en9UzRjII - resco-aiml-AgentCoreAssessment High Failed
193047247167
111111111111 us-east-1 FS-68 API Gateway Request Body Size Limits Not Enforced Medium Failed
193047247167
111111111111 us-east-1 FS-69 Prompt Input Validation Functions PresentFound 3 Lambda function(s) with input validation/sanitization naming patterns: resco-aiml-CleanupBucket, visa-bulletin-tracker-prod-cleanup, aiml-security-aiml-security-193047247167-CleanupBucket.Found 3 Lambda function(s) with input validation/sanitization naming patterns: resco-aiml-CleanupBucket, visa-bulletin-tracker-prod-cleanup, aiml-security-aiml-security-111111111111-CleanupBucket. Review these functions to confirm they cover: special-character stripping, format validation, size limits, and injection-sequence detection. Medium Passed
193047247167
111111111111 eu-west-1 FS-00 FinServ Regional Scope Not Applicable Informational N/A
193047247167
111111111111 ap-southeast-2 FS-00 FinServ Regional Scope Not Applicable Informational N/A
472057511786
333333333333 ap-southeast-2 AC-01 AgentCore VPC Configuration Check Informational N/A
472057511786
333333333333 ap-southeast-2 AC-04 AgentCore Observability Check Informational N/A
472057511786
333333333333 ap-southeast-2 AC-05 AgentCore Encryption Check Informational N/A
472057511786
333333333333 ap-southeast-2 AC-06 AgentCore Browser Tool Recording Check Informational N/A
472057511786
333333333333 ap-southeast-2 AC-07 AgentCore Memory Configuration Check Informational N/A
472057511786
333333333333 ap-southeast-2 AC-13 AgentCore Gateway Configuration Check Informational N/A
472057511786
333333333333 ap-southeast-2 AC-08 AgentCore VPC Endpoints Check Informational N/A
472057511786
333333333333 ap-southeast-2 AC-10 AgentCore Resource-Based Policies Check Informational N/A
472057511786
333333333333 ap-southeast-2 AC-11 AgentCore Policy Engine Encryption Check Informational N/A
472057511786
333333333333 ap-southeast-2 AC-12 AgentCore Gateway Encryption Check Informational N/A
472057511786
333333333333 eu-west-1 AC-01 AgentCore VPC Configuration Check Informational N/A
472057511786
333333333333 eu-west-1 AC-04 AgentCore Observability Check Informational N/A
472057511786
333333333333 eu-west-1 AC-05 AgentCore Encryption Check Informational N/A
472057511786
333333333333 eu-west-1 AC-06 AgentCore Browser Tool Recording Check Informational N/A
472057511786
333333333333 eu-west-1 AC-07 AgentCore Memory Configuration Check Informational N/A
472057511786
333333333333 eu-west-1 AC-13 AgentCore Gateway Configuration Check Informational N/A
472057511786
333333333333 eu-west-1 AC-08 AgentCore VPC Endpoints Check Informational N/A
472057511786
333333333333 eu-west-1 AC-10 AgentCore Resource-Based Policies Check Informational N/A
472057511786
333333333333 eu-west-1 AC-11 AgentCore Policy Engine Encryption Check Informational N/A
472057511786
333333333333 eu-west-1 AC-12 AgentCore Gateway Encryption Check Informational N/A
472057511786
333333333333 eu-west-1 SM-01 SageMaker Internet Access Check Informational N/A
472057511786
333333333333 eu-west-1 SM-02 SageMaker SSO Configuration Check Medium Passed
472057511786
333333333333 eu-west-1 SM-03 Data Protection Check Informational N/A
472057511786
333333333333 eu-west-1 SM-04 GuardDuty Enabled Medium Passed
472057511786
333333333333 eu-west-1 SM-05 SageMaker Model Registry Issue Informational N/A
472057511786
333333333333 eu-west-1 SM-05 SageMaker Feature Store Issue Informational N/A
472057511786
333333333333 eu-west-1 SM-05 SageMaker Pipelines Issue Informational N/A
472057511786
333333333333 eu-west-1 SM-06 SageMaker Clarify No Clarify Usage Informational N/A
472057511786
333333333333 eu-west-1 SM-07 SageMaker Model Monitor No Model Monitoring Informational N/A
472057511786
333333333333 eu-west-1 SM-08 Model Registry Registry Not Used Informational N/A
472057511786
333333333333 eu-west-1 SM-09 SageMaker Notebook Root Access Check Informational N/A
472057511786
333333333333 eu-west-1 SM-10 SageMaker Notebook VPC Deployment Check Informational N/A
472057511786
333333333333 eu-west-1 SM-11 SageMaker Model Network Isolation Check Informational N/A
472057511786
333333333333 eu-west-1 SM-12 SageMaker Endpoint Instance Count Check Informational N/A
472057511786
333333333333 eu-west-1 SM-13 SageMaker Monitoring Network Isolation Check Informational N/A
472057511786
333333333333 eu-west-1 SM-14 SageMaker Model Repository Access Check Informational N/A
472057511786
333333333333 eu-west-1 SM-15 SageMaker Feature Store Encryption Check Informational N/A
472057511786
333333333333 eu-west-1 SM-16 SageMaker Data Quality Job Encryption Check Informational N/A
472057511786
333333333333 eu-west-1 SM-17 SageMaker Processing Job Encryption Check Informational N/A
472057511786
333333333333 eu-west-1 SM-18 SageMaker Transform Job Encryption Check Informational N/A
472057511786
333333333333 eu-west-1 SM-19 SageMaker Hyperparameter Tuning Job Encryption Check Informational N/A
472057511786
333333333333 eu-west-1 SM-20 SageMaker Compilation Job Encryption Check Informational N/A
472057511786
333333333333 eu-west-1 SM-21 SageMaker AutoML Job Network Isolation Check Informational N/A
472057511786
333333333333 eu-west-1 SM-22 Model Approval Workflow Check Informational N/A
472057511786
333333333333 eu-west-1 SM-23 Model Drift Detection Check Medium Passed
472057511786
333333333333 eu-west-1 SM-24 A/B Testing and Shadow Deployment Check Low Passed
472057511786
333333333333 eu-west-1 SM-25 ML Lineage Tracking - Experiments Not Used Informational N/A
472057511786
333333333333 Global SM-02 SageMaker IAM Permissions Check High Passed
472057511786
333333333333 us-east-1 SM-01 SageMaker Internet Access Check Informational N/A
472057511786
333333333333 us-east-1 SM-02 SageMaker SSO Configuration Check Medium Passed
472057511786
333333333333 us-east-1 SM-03 Data Protection Check Informational N/A
472057511786
333333333333 us-east-1 SM-04 GuardDuty Enabled Medium Passed
472057511786
333333333333 us-east-1 SM-05 SageMaker Model Registry Issue Informational N/A
472057511786
333333333333 us-east-1 SM-05 SageMaker Feature Store Issue Informational N/A
472057511786
333333333333 us-east-1 SM-05 SageMaker Pipelines Issue Informational N/A
472057511786
333333333333 us-east-1 SM-06 SageMaker Clarify No Clarify Usage Informational N/A
472057511786
333333333333 us-east-1 SM-07 SageMaker Model Monitor No Model Monitoring Informational N/A
472057511786
333333333333 us-east-1 SM-08 Model Registry Registry Not Used Informational N/A
472057511786
333333333333 us-east-1 SM-09 SageMaker Notebook Root Access Check Informational N/A
472057511786
333333333333 us-east-1 SM-10 SageMaker Notebook VPC Deployment Check Informational N/A
472057511786
333333333333 us-east-1 SM-11 SageMaker Model Network Isolation Check Informational N/A
472057511786
333333333333 us-east-1 SM-12 SageMaker Endpoint Instance Count Check Informational N/A
472057511786
333333333333 us-east-1 SM-13 SageMaker Monitoring Network Isolation Check Informational N/A
472057511786
333333333333 us-east-1 SM-14 SageMaker Model Repository Access Check Informational N/A
472057511786
333333333333 us-east-1 SM-15 SageMaker Feature Store Encryption Check Informational N/A
472057511786
333333333333 us-east-1 SM-16 SageMaker Data Quality Job Encryption Check Informational N/A
472057511786
333333333333 us-east-1 SM-17 SageMaker Processing Job Encryption Check Informational N/A
472057511786
333333333333 us-east-1 SM-18 SageMaker Transform Job Encryption Check Informational N/A
472057511786
333333333333 us-east-1 SM-19 SageMaker Hyperparameter Tuning Job Encryption Check Informational N/A
472057511786
333333333333 us-east-1 SM-20 SageMaker Compilation Job Encryption Check Informational N/A
472057511786
333333333333 us-east-1 SM-21 SageMaker AutoML Job Network Isolation Check Informational N/A
472057511786
333333333333 us-east-1 SM-22 Model Approval Workflow Check Informational N/A
472057511786
333333333333 us-east-1 SM-23 Model Drift Detection Check Medium Passed
472057511786
333333333333 us-east-1 SM-24 A/B Testing and Shadow Deployment Check Low Passed
472057511786
333333333333 us-east-1 SM-25 ML Lineage Tracking - Experiments Not Used Informational N/A
472057511786
333333333333 ap-southeast-2 SM-01 SageMaker Internet Access Check Informational N/A
472057511786
333333333333 ap-southeast-2 SM-02 SageMaker SSO Configuration Check Medium Passed
472057511786
333333333333 ap-southeast-2 SM-03 Data Protection Check Informational N/A
472057511786
333333333333 ap-southeast-2 SM-04 GuardDuty Enabled Medium Passed
472057511786
333333333333 ap-southeast-2 SM-05 SageMaker Model Registry Issue Informational N/A
472057511786
333333333333 ap-southeast-2 SM-05 SageMaker Feature Store Issue Informational N/A
472057511786
333333333333 ap-southeast-2 SM-05 SageMaker Pipelines Issue Informational N/A
472057511786
333333333333 ap-southeast-2 SM-06 SageMaker Clarify No Clarify Usage Informational N/A
472057511786
333333333333 ap-southeast-2 SM-07 SageMaker Model Monitor No Model Monitoring Informational N/A
472057511786
333333333333 ap-southeast-2 SM-08 Model Registry Registry Not Used Informational N/A
472057511786
333333333333 ap-southeast-2 SM-09 SageMaker Notebook Root Access Check Informational N/A
472057511786
333333333333 ap-southeast-2 SM-10 SageMaker Notebook VPC Deployment Check Informational N/A
472057511786
333333333333 ap-southeast-2 SM-11 SageMaker Model Network Isolation Check Informational N/A
472057511786
333333333333 ap-southeast-2 SM-12 SageMaker Endpoint Instance Count Check Informational N/A
472057511786
333333333333 ap-southeast-2 SM-13 SageMaker Monitoring Network Isolation Check Informational N/A
472057511786
333333333333 ap-southeast-2 SM-14 SageMaker Model Repository Access Check Informational N/A
472057511786
333333333333 ap-southeast-2 SM-15 SageMaker Feature Store Encryption Check Informational N/A
472057511786
333333333333 ap-southeast-2 SM-16 SageMaker Data Quality Job Encryption Check Informational N/A
472057511786
333333333333 ap-southeast-2 SM-17 SageMaker Processing Job Encryption Check Informational N/A
472057511786
333333333333 ap-southeast-2 SM-18 SageMaker Transform Job Encryption Check Informational N/A
472057511786
333333333333 ap-southeast-2 SM-19 SageMaker Hyperparameter Tuning Job Encryption Check Informational N/A
472057511786
333333333333 ap-southeast-2 SM-20 SageMaker Compilation Job Encryption Check Informational N/A
472057511786
333333333333 ap-southeast-2 SM-21 SageMaker AutoML Job Network Isolation Check Informational N/A
472057511786
333333333333 ap-southeast-2 SM-22 Model Approval Workflow Check Informational N/A
472057511786
333333333333 ap-southeast-2 SM-23 Model Drift Detection Check Medium Passed
472057511786
333333333333 ap-southeast-2 SM-24 A/B Testing and Shadow Deployment Check Low Passed
472057511786
333333333333 ap-southeast-2 SM-25 ML Lineage Tracking - Experiments Not Used Informational N/A
472057511786
333333333333 Global BR-01 AmazonBedrockFullAccess role check High Passed
472057511786
333333333333 Global BR-03 Marketplace Subscription Access Check High Failed
472057511786
333333333333 Global BR-14 Stale Bedrock Access Check Medium Failed
472057511786
333333333333 Global BR-14 Stale Bedrock Access Check Medium Failed
472057511786
333333333333 Global BR-14 Stale Bedrock Access Check Medium Failed
472057511786
333333333333 Global BR-14 Stale Bedrock Access Check Medium Failed
472057511786
333333333333 Global BR-14 Stale Bedrock Access Check Medium Failed
472057511786
333333333333 Global BR-14 Stale Bedrock Access Check Medium Failed
472057511786
333333333333 Global BR-14 Stale Bedrock Access CheckRole 'cdk-hnb659fds-lookup-role-472057511786-us-east-1' last accessed Bedrock on neverRole 'cdk-hnb659fds-lookup-role-333333333333-us-east-1' last accessed Bedrock on never You can use last accessed information to refine your policies and allow access to only the services and actions that your IAM identities and policies use. This helps you to better adhere to the best practice of least privilege. Medium Failed
472057511786
333333333333 Global BR-14 Stale Bedrock Access CheckRole 'cdk-hnb659fds-lookup-role-472057511786-us-east-2' last accessed Bedrock on neverRole 'cdk-hnb659fds-lookup-role-333333333333-us-east-2' last accessed Bedrock on never You can use last accessed information to refine your policies and allow access to only the services and actions that your IAM identities and policies use. This helps you to better adhere to the best practice of least privilege. Medium Failed
472057511786
333333333333 Global BR-14 Stale Bedrock Access CheckRole 'cdk-hnb659fds-lookup-role-472057511786-us-west-2' last accessed Bedrock on neverRole 'cdk-hnb659fds-lookup-role-333333333333-us-west-2' last accessed Bedrock on never You can use last accessed information to refine your policies and allow access to only the services and actions that your IAM identities and policies use. This helps you to better adhere to the best practice of least privilege. Medium Failed
472057511786
333333333333 Global BR-14 Stale Bedrock Access Check Medium Failed
472057511786
333333333333 Global BR-14 Stale Bedrock Access Check Medium Failed
472057511786
333333333333 Global BR-14 Stale Bedrock Access Check Medium Failed
472057511786
333333333333 Global BR-14 Stale Bedrock Access Check Medium Failed
472057511786
333333333333 Global BR-14 Stale Bedrock Access Check Medium Failed
472057511786
333333333333 Global BR-14 Stale Bedrock Access Check Medium Failed
472057511786
333333333333 Global BR-14 Stale Bedrock Access Check Medium Failed
472057511786
333333333333 Global BR-14 Stale Bedrock Access Check Medium Failed
472057511786
333333333333 Global BR-14 Stale Bedrock Access Check Medium Failed
472057511786
333333333333 Global BR-14 Stale Bedrock Access Check Medium Failed
472057511786
333333333333 us-east-1 BR-02 Amazon Bedrock private connectivity check Informational N/A
472057511786
333333333333 us-east-1 BR-04 Bedrock Model Invocation Logging Check Informational N/A
472057511786
333333333333 us-east-1 BR-05 Bedrock Guardrails Check Informational N/A
472057511786
333333333333 us-east-1 BR-06 Bedrock CloudTrail Logging Check Informational N/A
472057511786
333333333333 us-east-1 BR-07 Bedrock Prompt Management Check Informational N/A
472057511786
333333333333 us-east-1 BR-08 Bedrock Agent IAM Roles Check Informational N/A
472057511786
333333333333 us-east-1 BR-09 Bedrock Knowledge Base Encryption CheckUnable to check Bedrock Knowledge Base API: An error occurred (AccessDeniedException) when calling the ListKnowledgeBases operation: User: arn:aws:sts::472057511786:assumed-role/aiml-security-mgmt-BedrockSecurityAssessmentFunctio-3SFVOekaDS6b/aiml-security-aiml-security-mgmt-BedrockAssessment is not authorized to perform: bedrock:ListKnowledgeBases on resource: arn:aws:bedrock:us-east-1:472057511786:knowledge-base/* because no identity-based policy allows the bedrock:ListKnowledgeBases actionUnable to check Bedrock Knowledge Base API: An error occurred (AccessDeniedException) when calling the ListKnowledgeBases operation: User: arn:aws:sts::333333333333:assumed-role/aiml-security-mgmt-BedrockSecurityAssessmentFunctio-3SFVOekaDS6b/aiml-security-aiml-security-mgmt-BedrockAssessment is not authorized to perform: bedrock:ListKnowledgeBases on resource: arn:aws:bedrock:us-east-1:333333333333:knowledge-base/* because no identity-based policy allows the bedrock:ListKnowledgeBases action Verify your AWS credentials and permissions to access Bedrock Knowledge Bases, then retry the assessment. Informational N/A
472057511786
333333333333 us-east-1 BR-10 Bedrock Guardrail IAM Enforcement Check Informational N/A
472057511786
333333333333 us-east-1 BR-11 Bedrock Custom Model Encryption Check Informational N/A
472057511786
333333333333 us-east-1 BR-12 Bedrock Invocation Log Encryption Check Informational N/A
472057511786
333333333333 us-east-1 BR-13 Bedrock Flows Guardrails Check Informational N/A
472057511786
333333333333 us-east-1 FS-01 AWS Shield Advanced Not Enabled Low Failed
472057511786
333333333333 us-east-1 FS-01 No Regional WAF Web ACLs Found Medium Failed
472057511786
333333333333 us-east-1 FS-02 No API Gateway Usage Plans Found Informational N/A
472057511786
333333333333 us-east-1 FS-03 Bedrock Token Quotas At Default Medium N/A
472057511786
333333333333 us-east-1 FS-04 No Cost Anomaly Detection Monitors Medium Failed
472057511786
333333333333 us-east-1 FS-05 No Bedrock CloudWatch Alarms Found Medium Failed
472057511786
333333333333 us-east-1 FS-06 No AI/ML Service Budgets Configured Medium Failed
472057511786
333333333333 us-east-1 FS-07 Agent Action Boundary Check Informational N/A
472057511786
333333333333 us-east-1 FS-08 No AgentCore Runtimes Found Informational N/A
472057511786
333333333333 us-east-1 FS-09 Agent Lambda Functions Without Concurrency Limits Medium Failed
472057511786
333333333333 us-east-1 FS-10 Human-in-the-Loop Check — No Agent Workflows Found Informational N/A
472057511786
333333333333 us-east-1 FS-11 No Agent Rate Alarms Found Medium Failed
472057511786
333333333333 us-east-1 FS-12 No Bedrock-Scoped SCPs Found High Failed
472057511786
333333333333 us-east-1 FS-13 Model Provenance Tags Present Medium Passed
472057511786
333333333333 us-east-1 FS-14 Model Governance Config Rules Present Medium Passed
472057511786
333333333333 us-east-1 FS-15 No Bedrock Evaluation Jobs Found Medium Failed
472057511786
333333333333 us-east-1 FS-16 ECR Repositories Without Image Scanning1 ECR repo(s) without scan-on-push: cdk-hnb659fds-container-assets-472057511786-us-east-1.1 ECR repo(s) without scan-on-push: cdk-hnb659fds-container-assets-333333333333-us-east-1. Enable scan-on-push for all ECR repositories containing model containers. Consider enabling Enhanced Scanning (Inspector) for CVE detection. High Failed
472057511786
333333333333 us-east-1 FS-20 No SageMaker Feature Groups Found Informational N/A
472057511786
333333333333 us-east-1 FS-21 No Training Data Buckets Identified Informational N/A
472057511786
333333333333 us-east-1 FS-22 Overly Permissive Knowledge Base IAM Roles High Failed
472057511786
333333333333 us-east-1 FS-24 ADVISORY: Knowledge Base Metadata Filtering — Manual Review Required Informational N/A
472057511786
333333333333 us-east-1 FS-25 OpenSearch Serverless Encryption Policies Present High Passed
472057511786
333333333333 us-east-1 FS-26 OpenSearch Serverless Collections Not VPC-Restricted High Failed
472057511786
333333333333 us-east-1 FS-27 No Guardrails — Contextual Grounding Not Applicable Informational N/A
472057511786
333333333333 us-east-1 FS-27 Automated Reasoning Policies — Access Check Low N/A
472057511786
333333333333 us-east-1 FS-28 No Guardrails — Denied Topics Not Applicable Informational N/A
472057511786
333333333333 us-east-1 FS-29 ADVISORY: Compliance Disclaimer — Manual Review Required Informational N/A
472057511786
333333333333 us-east-1 FS-30 ADVISORY: Compliance Dataset Coverage — Manual Review Required Informational N/A
472057511786
333333333333 us-east-1 FS-31 Knowledge Base Data Sources Past Review Threshold Medium Failed
472057511786
333333333333 us-east-1 FS-32 ADVISORY: Source Attribution — Manual Review Required Informational N/A
472057511786
333333333333 us-east-1 FS-33 KB Data Source Buckets Have Versioning Medium Passed
472057511786
333333333333 us-east-1 FS-34 Legacy Foundation Models Available in Region Informational N/A
472057511786
333333333333 us-east-1 FS-35 ADVISORY: Harmful-Content Test Coverage — Manual Review Required Informational N/A
472057511786
333333333333 us-east-1 FS-36 No Guardrails — Content Filters Not Applicable Informational N/A
472057511786
333333333333 us-east-1 FS-37 ADVISORY: User Feedback Mechanism — Manual Review Required Informational N/A
472057511786
333333333333 us-east-1 FS-38 No Guardrails — Word Filters Not Applicable Informational N/A
472057511786
333333333333 us-east-1 FS-39 No SageMaker Clarify Bias Monitoring High Failed
472057511786
333333333333 us-east-1 FS-40 ADVISORY: Bias Dataset Coverage — Manual Review Required Informational N/A
472057511786
333333333333 us-east-1 FS-41 No SageMaker Clarify Explainability Monitoring High Failed
472057511786
333333333333 us-east-1 FS-42 No SageMaker Model Cards Found Medium Failed
472057511786
333333333333 us-east-1 FS-43 No CloudWatch Logs Data Protection Policies High Failed
472057511786
333333333333 us-east-1 FS-44 Amazon Macie Not Enabled High Failed
472057511786
333333333333 us-east-1 FS-45 No Guardrails — PII Filters Not Applicable Informational N/A
472057511786
333333333333 us-east-1 FS-46 No AI/ML Data Buckets Identified Informational N/A
472057511786
333333333333 us-east-1 FS-47 No Guardrails — Grounding Threshold Not Applicable Informational N/A
472057511786
333333333333 us-east-1 FS-48 Active Knowledge Bases for RAG Present Medium Passed
472057511786
333333333333 us-east-1 FS-49 ADVISORY: Hallucination Disclaimer — Manual Review Required Informational N/A
472057511786
333333333333 us-east-1 FS-50 No Guardrails With Relevance Grounding Filters Medium Failed
472057511786
333333333333 us-east-1 FS-51 No Guardrails — Prompt Attack Filters Not Applicable Informational N/A
472057511786
333333333333 us-east-1 FS-52 Bedrock Lambda Functions on Current Runtimes Medium Passed
472057511786
333333333333 us-east-1 FS-53 No WAF Web ACLs — Injection Rules Not Applicable Informational N/A
472057511786
333333333333 us-east-1 FS-54 ADVISORY: Penetration Testing — Manual Review Required Informational N/A
472057511786
333333333333 us-east-1 FS-55 No Output Validation Functions Found Medium Failed
472057511786
333333333333 us-east-1 FS-56 No WAF ACLs — XSS Prevention Not Applicable Informational N/A
472057511786
333333333333 us-east-1 FS-57 ADVISORY: Output Encoding — Manual Review Required Informational N/A
472057511786
333333333333 us-east-1 FS-58 ADVISORY: Output Schema Validation — Manual Review Required Informational N/A
472057511786
333333333333 us-east-1 FS-59 No Guardrails — Topic Allowlist Not Applicable Informational N/A
472057511786
333333333333 us-east-1 FS-60 ADVISORY: Contextual Grounding for Off-Topic Prevention Informational N/A
472057511786
333333333333 us-east-1 FS-61 COULD NOT ASSESS: Knowledge Base Sync Schedule CheckThis check could not be completed (error: An error occurred (AccessDeniedException) when calling the ListSchedules operation: User: arn:aws:sts::472057511786:assumed-role/aiml-security-mgmt-FinServSecurityAssessmentFunctio-pwj9by1swQWa/aiml-security-aiml-security-mgmt-FinServAssessment is not authorized to perform: scheduler:ListSchedules on resource: arn:aws:scheduler:us-east-1:472057511786:schedule/*/* because no identity-based policy allows the scheduler:ListSchedules action). The most common cause is a missing IAM permission for the assessment role; it may also indicate an unsupported region or an outdated botocore. This control was NOT assessed — verify the role's permissions and re-run, and assess this control manually until resolved.This check could not be completed (error: An error occurred (AccessDeniedException) when calling the ListSchedules operation: User: arn:aws:sts::333333333333:assumed-role/aiml-security-mgmt-FinServSecurityAssessmentFunctio-pwj9by1swQWa/aiml-security-aiml-security-mgmt-FinServAssessment is not authorized to perform: scheduler:ListSchedules on resource: arn:aws:scheduler:us-east-1:333333333333:schedule/*/* because no identity-based policy allows the scheduler:ListSchedules action). The most common cause is a missing IAM permission for the assessment role; it may also indicate an unsupported region or an outdated botocore. This control was NOT assessed — verify the role's permissions and re-run, and assess this control manually until resolved. 1. Confirm the assessment role grants the actions this check requires (see the documented IAM permission set in the README). 2. Confirm the service/feature is supported in the assessed region. 3. Ensure botocore meets the version floor in requirements.txt. @@ -6181,8 +6181,8 @@

Security Assessment Overview

Low N/A
472057511786
333333333333 us-east-1 FS-62 ADVISORY: Data Currency Disclaimer — Manual Review Required Informational N/A
472057511786
333333333333 us-east-1 FS-63 Foundation Model Lifecycle Management Medium Passed
472057511786
333333333333 us-east-1 FS-65 KB Data Source Buckets Missing S3 Event Notifications Medium Failed
472057511786
333333333333 us-east-1 FS-66 No AgentCore Runtimes Found Informational N/A
472057511786
333333333333 us-east-1 FS-67 Agent Action-Group Lambdas May Lack Transaction Thresholds High Failed
472057511786
333333333333 us-east-1 FS-68 API Gateway Request Body Size Limits — Not Applicable Informational N/A
472057511786
333333333333 us-east-1 FS-69 Prompt Input Validation Functions Present Medium Passed
472057511786
333333333333 eu-west-1 FS-00 FinServ Regional Scope Not Applicable Informational N/A
472057511786
333333333333 ap-southeast-2 FS-00 FinServ Regional Scope Not Applicable Informational N/A
472057511786
333333333333 ap-southeast-2 BR-02 Amazon Bedrock private connectivity check Informational N/A
472057511786
333333333333 ap-southeast-2 BR-04 Bedrock Model Invocation Logging Check Informational N/A
472057511786
333333333333 ap-southeast-2 BR-05 Bedrock Guardrails Check Informational N/A
472057511786
333333333333 ap-southeast-2 BR-06 Bedrock CloudTrail Logging Check Informational N/A
472057511786
333333333333 ap-southeast-2 BR-07 Bedrock Prompt Management Check Informational N/A
472057511786
333333333333 ap-southeast-2 BR-08 Bedrock Agent IAM Roles Check Informational N/A
472057511786
333333333333 ap-southeast-2 BR-09 Bedrock Knowledge Base Encryption CheckUnable to check Bedrock Knowledge Base API: An error occurred (AccessDeniedException) when calling the ListKnowledgeBases operation: User: arn:aws:sts::472057511786:assumed-role/aiml-security-mgmt-BedrockSecurityAssessmentFunctio-3SFVOekaDS6b/aiml-security-aiml-security-mgmt-BedrockAssessment is not authorized to perform: bedrock:ListKnowledgeBases on resource: arn:aws:bedrock:ap-southeast-2:472057511786:knowledge-base/* because no identity-based policy allows the bedrock:ListKnowledgeBases actionUnable to check Bedrock Knowledge Base API: An error occurred (AccessDeniedException) when calling the ListKnowledgeBases operation: User: arn:aws:sts::333333333333:assumed-role/aiml-security-mgmt-BedrockSecurityAssessmentFunctio-3SFVOekaDS6b/aiml-security-aiml-security-mgmt-BedrockAssessment is not authorized to perform: bedrock:ListKnowledgeBases on resource: arn:aws:bedrock:ap-southeast-2:333333333333:knowledge-base/* because no identity-based policy allows the bedrock:ListKnowledgeBases action Verify your AWS credentials and permissions to access Bedrock Knowledge Bases, then retry the assessment. Informational N/A
472057511786
333333333333 ap-southeast-2 BR-10 Bedrock Guardrail IAM Enforcement Check Informational N/A
472057511786
333333333333 ap-southeast-2 BR-11 Bedrock Custom Model Encryption Check Informational N/A
472057511786
333333333333 ap-southeast-2 BR-12 Bedrock Invocation Log Encryption Check Informational N/A
472057511786
333333333333 ap-southeast-2 BR-13 Bedrock Flows Guardrails Check Informational N/A
472057511786
333333333333 eu-west-1 BR-02 Amazon Bedrock private connectivity check Informational N/A
472057511786
333333333333 eu-west-1 BR-04 Bedrock Model Invocation Logging Check Informational N/A
472057511786
333333333333 eu-west-1 BR-05 Bedrock Guardrails Check Informational N/A
472057511786
333333333333 eu-west-1 BR-06 Bedrock CloudTrail Logging Check Informational N/A
472057511786
333333333333 eu-west-1 BR-07 Bedrock Prompt Management Check Informational N/A
472057511786
333333333333 eu-west-1 BR-08 Bedrock Agent IAM Roles Check Informational N/A
472057511786
333333333333 eu-west-1 BR-09 Bedrock Knowledge Base Encryption CheckUnable to check Bedrock Knowledge Base API: An error occurred (AccessDeniedException) when calling the ListKnowledgeBases operation: User: arn:aws:sts::472057511786:assumed-role/aiml-security-mgmt-BedrockSecurityAssessmentFunctio-3SFVOekaDS6b/aiml-security-aiml-security-mgmt-BedrockAssessment is not authorized to perform: bedrock:ListKnowledgeBases on resource: arn:aws:bedrock:eu-west-1:472057511786:knowledge-base/* because no identity-based policy allows the bedrock:ListKnowledgeBases actionUnable to check Bedrock Knowledge Base API: An error occurred (AccessDeniedException) when calling the ListKnowledgeBases operation: User: arn:aws:sts::333333333333:assumed-role/aiml-security-mgmt-BedrockSecurityAssessmentFunctio-3SFVOekaDS6b/aiml-security-aiml-security-mgmt-BedrockAssessment is not authorized to perform: bedrock:ListKnowledgeBases on resource: arn:aws:bedrock:eu-west-1:333333333333:knowledge-base/* because no identity-based policy allows the bedrock:ListKnowledgeBases action Verify your AWS credentials and permissions to access Bedrock Knowledge Bases, then retry the assessment. Informational N/A
472057511786
333333333333 eu-west-1 BR-10 Bedrock Guardrail IAM Enforcement Check Informational N/A
472057511786
333333333333 eu-west-1 BR-11 Bedrock Custom Model Encryption Check Informational N/A
472057511786
333333333333 eu-west-1 BR-12 Bedrock Invocation Log Encryption Check Informational N/A
472057511786
333333333333 eu-west-1 BR-13 Bedrock Flows Guardrails Check Informational N/A
472057511786
333333333333 Global AC-02 AgentCore IAM Full Access Check High Passed
472057511786
333333333333 Global AC-03 AgentCore Stale Access Medium Failed
472057511786
333333333333 Global AC-03 AgentCore Unused Permissions Medium Failed
472057511786
333333333333 Global AC-09 AgentCore Service-Linked Role Missing Medium Failed
472057511786
333333333333 us-east-1 AC-01 AgentCore VPC Configuration Check Informational N/A
472057511786
333333333333 us-east-1 AC-04 AgentCore Observability Check Informational N/A
472057511786
333333333333 us-east-1 AC-05 AgentCore Encryption Check Informational N/A
472057511786
333333333333 us-east-1 AC-06 AgentCore Browser Tool Recording Check Informational N/A
472057511786
333333333333 us-east-1 AC-07 AgentCore Memory Configuration Check Informational N/A
472057511786
333333333333 us-east-1 AC-13 AgentCore Gateway Configuration Check Informational N/A
472057511786
333333333333 us-east-1 AC-08 AgentCore VPC Endpoints Check Informational N/A
472057511786
333333333333 us-east-1 AC-10 AgentCore Resource-Based Policies Check Informational N/A
472057511786
333333333333 us-east-1 AC-11 AgentCore Policy Engine Encryption Check Informational N/A
472057511786
333333333333 us-east-1 AC-12 AgentCore Gateway Encryption Check Informational N/A
230266523520
222222222222 eu-west-1 AC-01 AgentCore VPC Configuration Check Informational N/A
230266523520
222222222222 eu-west-1 AC-04 AgentCore Observability Check Informational N/A
230266523520
222222222222 eu-west-1 AC-05 AgentCore Encryption Check Informational N/A
230266523520
222222222222 eu-west-1 AC-06 AgentCore Browser Tool Recording Check Informational N/A
230266523520
222222222222 eu-west-1 AC-07 AgentCore Memory Configuration Check Informational N/A
230266523520
222222222222 eu-west-1 AC-13 AgentCore Gateway Configuration Check Informational N/A
230266523520
222222222222 eu-west-1 AC-08 AgentCore VPC Endpoints Check Informational N/A
230266523520
222222222222 eu-west-1 AC-10 AgentCore Resource-Based Policies Check Informational N/A
230266523520
222222222222 eu-west-1 AC-11 AgentCore Policy Engine Encryption Check Informational N/A
230266523520
222222222222 eu-west-1 AC-12 AgentCore Gateway Encryption Check Informational N/A
230266523520
222222222222 eu-west-1 SM-01 SageMaker Internet Access Check Informational N/A
230266523520
222222222222 eu-west-1 SM-02 SageMaker SSO Configuration Check Medium Passed
230266523520
222222222222 eu-west-1 SM-03 Data Protection Check Informational N/A
230266523520
222222222222 eu-west-1 SM-04 GuardDuty Enabled Medium Passed
230266523520
222222222222 eu-west-1 SM-05 SageMaker Model Registry Issue Informational N/A
230266523520
222222222222 eu-west-1 SM-05 SageMaker Feature Store Issue Informational N/A
230266523520
222222222222 eu-west-1 SM-05 SageMaker Pipelines Issue Informational N/A
230266523520
222222222222 eu-west-1 SM-06 SageMaker Clarify No Clarify Usage Informational N/A
230266523520
222222222222 eu-west-1 SM-07 SageMaker Model Monitor No Model Monitoring Informational N/A
230266523520
222222222222 eu-west-1 SM-08 Model Registry Registry Not Used Informational N/A
230266523520
222222222222 eu-west-1 SM-09 SageMaker Notebook Root Access Check Informational N/A
230266523520
222222222222 eu-west-1 SM-10 SageMaker Notebook VPC Deployment Check Informational N/A
230266523520
222222222222 eu-west-1 SM-11 SageMaker Model Network Isolation Check Informational N/A
230266523520
222222222222 eu-west-1 SM-12 SageMaker Endpoint Instance Count Check Informational N/A
230266523520
222222222222 eu-west-1 SM-13 SageMaker Monitoring Network Isolation Check Informational N/A
230266523520
222222222222 eu-west-1 SM-14 SageMaker Model Repository Access Check Informational N/A
230266523520
222222222222 eu-west-1 SM-15 SageMaker Feature Store Encryption Check Informational N/A
230266523520
222222222222 eu-west-1 SM-16 SageMaker Data Quality Job Encryption Check Informational N/A
230266523520
222222222222 eu-west-1 SM-17 SageMaker Processing Job Encryption Check Informational N/A
230266523520
222222222222 eu-west-1 SM-18 SageMaker Transform Job Encryption Check Informational N/A
230266523520
222222222222 eu-west-1 SM-19 SageMaker Hyperparameter Tuning Job Encryption Check Informational N/A
230266523520
222222222222 eu-west-1 SM-20 SageMaker Compilation Job Encryption Check Informational N/A
230266523520
222222222222 eu-west-1 SM-21 SageMaker AutoML Job Network Isolation Check Informational N/A
230266523520
222222222222 eu-west-1 SM-22 Model Approval Workflow Check Informational N/A
230266523520
222222222222 eu-west-1 SM-23 Model Drift Detection Check Medium Passed
230266523520
222222222222 eu-west-1 SM-24 A/B Testing and Shadow Deployment Check Low Passed
230266523520
222222222222 eu-west-1 SM-25 ML Lineage Tracking - Experiments Not Used Informational N/A
230266523520
222222222222 Global SM-02 SageMaker IAM Permissions Check High Passed
230266523520
222222222222 us-east-1 SM-01 SageMaker Internet Access Check Informational N/A
230266523520
222222222222 us-east-1 SM-02 SageMaker SSO Configuration Check Medium Passed
230266523520
222222222222 us-east-1 SM-03 Data Protection Check Informational N/A
230266523520
222222222222 us-east-1 SM-04 GuardDuty Enabled Medium Passed
230266523520
222222222222 us-east-1 SM-05 SageMaker Model Registry Issue Informational N/A
230266523520
222222222222 us-east-1 SM-05 SageMaker Feature Store Issue Informational N/A
230266523520
222222222222 us-east-1 SM-05 SageMaker Pipelines Issue Informational N/A
230266523520
222222222222 us-east-1 SM-06 SageMaker Clarify No Clarify Usage Informational N/A
230266523520
222222222222 us-east-1 SM-07 SageMaker Model Monitor No Model Monitoring Informational N/A
230266523520
222222222222 us-east-1 SM-08 Model Registry Registry Not Used Informational N/A
230266523520
222222222222 us-east-1 SM-09 SageMaker Notebook Root Access Check Informational N/A
230266523520
222222222222 us-east-1 SM-10 SageMaker Notebook VPC Deployment Check Informational N/A
230266523520
222222222222 us-east-1 SM-11 SageMaker Model Network Isolation Check Informational N/A
230266523520
222222222222 us-east-1 SM-12 SageMaker Endpoint Instance Count Check Informational N/A
230266523520
222222222222 us-east-1 SM-13 SageMaker Monitoring Network Isolation Check Informational N/A
230266523520
222222222222 us-east-1 SM-14 SageMaker Model Repository Access Check Informational N/A
230266523520
222222222222 us-east-1 SM-15 SageMaker Feature Store Encryption Check Informational N/A
230266523520
222222222222 us-east-1 SM-16 SageMaker Data Quality Job Encryption Check Informational N/A
230266523520
222222222222 us-east-1 SM-17 SageMaker Processing Job Encryption Check Informational N/A
230266523520
222222222222 us-east-1 SM-18 SageMaker Transform Job Encryption Check Informational N/A
230266523520
222222222222 us-east-1 SM-19 SageMaker Hyperparameter Tuning Job Encryption Check Informational N/A
230266523520
222222222222 us-east-1 SM-20 SageMaker Compilation Job Encryption Check Informational N/A
230266523520
222222222222 us-east-1 SM-21 SageMaker AutoML Job Network Isolation Check Informational N/A
230266523520
222222222222 us-east-1 SM-22 Model Approval Workflow Check Informational N/A
230266523520
222222222222 us-east-1 SM-23 Model Drift Detection Check Medium Passed
230266523520
222222222222 us-east-1 SM-24 A/B Testing and Shadow Deployment Check Low Passed
230266523520
222222222222 us-east-1 SM-25 ML Lineage Tracking - Experiments Not Used Informational N/A
230266523520
222222222222 us-east-1 FS-00 FinServ Regional Scope Not Applicable Informational N/A
230266523520
222222222222 eu-west-1 FS-00 FinServ Regional Scope Not Applicable Informational N/A
230266523520
222222222222 ap-southeast-2 FS-00 FinServ Regional Scope Not Applicable Informational N/A
230266523520
222222222222 ap-southeast-2 BR-02 Amazon Bedrock private connectivity check Informational N/A
230266523520
222222222222 ap-southeast-2 BR-04 Bedrock Model Invocation Logging Check Informational N/A
230266523520
222222222222 ap-southeast-2 BR-05 Bedrock Guardrails Check Informational N/A
230266523520
222222222222 ap-southeast-2 BR-06 Bedrock CloudTrail Logging Check Informational N/A
230266523520
222222222222 ap-southeast-2 BR-07 Bedrock Prompt Management Check Informational N/A
230266523520
222222222222 ap-southeast-2 BR-08 Bedrock Agent IAM Roles Check Informational N/A
230266523520
222222222222 ap-southeast-2 BR-09 Bedrock Knowledge Base Encryption CheckUnable to check Bedrock Knowledge Base API: An error occurred (AccessDeniedException) when calling the ListKnowledgeBases operation: User: arn:aws:sts::230266523520:assumed-role/aiml-security-23026652352-BedrockSecurityAssessment-UZzmVN1xrMwf/aiml-security-aiml-security-230266523520-BedrockAssessment is not authorized to perform: bedrock:ListKnowledgeBases on resource: arn:aws:bedrock:ap-southeast-2:230266523520:knowledge-base/* because no identity-based policy allows the bedrock:ListKnowledgeBases actionUnable to check Bedrock Knowledge Base API: An error occurred (AccessDeniedException) when calling the ListKnowledgeBases operation: User: arn:aws:sts::222222222222:assumed-role/aiml-security-23026652352-BedrockSecurityAssessment-UZzmVN1xrMwf/aiml-security-aiml-security-222222222222-BedrockAssessment is not authorized to perform: bedrock:ListKnowledgeBases on resource: arn:aws:bedrock:ap-southeast-2:222222222222:knowledge-base/* because no identity-based policy allows the bedrock:ListKnowledgeBases action Verify your AWS credentials and permissions to access Bedrock Knowledge Bases, then retry the assessment. Informational N/A
230266523520
222222222222 ap-southeast-2 BR-10 Bedrock Guardrail IAM Enforcement Check Informational N/A
230266523520
222222222222 ap-southeast-2 BR-11 Bedrock Custom Model Encryption Check Informational N/A
230266523520
222222222222 ap-southeast-2 BR-12 Bedrock Invocation Log Encryption Check Informational N/A
230266523520
222222222222 ap-southeast-2 BR-13 Bedrock Flows Guardrails Check Informational N/A
230266523520
222222222222 ap-southeast-2 SM-01 SageMaker Internet Access Check Informational N/A
230266523520
222222222222 ap-southeast-2 SM-02 SageMaker SSO Configuration Check Medium Passed
230266523520
222222222222 ap-southeast-2 SM-03 Data Protection Check Informational N/A
230266523520
222222222222 ap-southeast-2 SM-04 GuardDuty Enabled Medium Passed
230266523520
222222222222 ap-southeast-2 SM-05 SageMaker Model Registry Issue Informational N/A
230266523520
222222222222 ap-southeast-2 SM-05 SageMaker Feature Store Issue Informational N/A
230266523520
222222222222 ap-southeast-2 SM-05 SageMaker Pipelines Issue Informational N/A
230266523520
222222222222 ap-southeast-2 SM-06 SageMaker Clarify No Clarify Usage Informational N/A
230266523520
222222222222 ap-southeast-2 SM-07 SageMaker Model Monitor No Model Monitoring Informational N/A
230266523520
222222222222 ap-southeast-2 SM-08 Model Registry Registry Not Used Informational N/A
230266523520
222222222222 ap-southeast-2 SM-09 SageMaker Notebook Root Access Check Informational N/A
230266523520
222222222222 ap-southeast-2 SM-10 SageMaker Notebook VPC Deployment Check Informational N/A
230266523520
222222222222 ap-southeast-2 SM-11 SageMaker Model Network Isolation Check Informational N/A
230266523520
222222222222 ap-southeast-2 SM-12 SageMaker Endpoint Instance Count Check Informational N/A
230266523520
222222222222 ap-southeast-2 SM-13 SageMaker Monitoring Network Isolation Check Informational N/A
230266523520
222222222222 ap-southeast-2 SM-14 SageMaker Model Repository Access Check Informational N/A
230266523520
222222222222 ap-southeast-2 SM-15 SageMaker Feature Store Encryption Check Informational N/A
230266523520
222222222222 ap-southeast-2 SM-16 SageMaker Data Quality Job Encryption Check Informational N/A
230266523520
222222222222 ap-southeast-2 SM-17 SageMaker Processing Job Encryption Check Informational N/A
230266523520
222222222222 ap-southeast-2 SM-18 SageMaker Transform Job Encryption Check Informational N/A
230266523520
222222222222 ap-southeast-2 SM-19 SageMaker Hyperparameter Tuning Job Encryption Check Informational N/A
230266523520
222222222222 ap-southeast-2 SM-20 SageMaker Compilation Job Encryption Check Informational N/A
230266523520
222222222222 ap-southeast-2 SM-21 SageMaker AutoML Job Network Isolation Check Informational N/A
230266523520
222222222222 ap-southeast-2 SM-22 Model Approval Workflow Check Informational N/A
230266523520
222222222222 ap-southeast-2 SM-23 Model Drift Detection Check Medium Passed
230266523520
222222222222 ap-southeast-2 SM-24 A/B Testing and Shadow Deployment Check Low Passed
230266523520
222222222222 ap-southeast-2 SM-25 ML Lineage Tracking - Experiments Not Used Informational N/A
230266523520
222222222222 Global BR-01 AmazonBedrockFullAccess role check High Passed
230266523520
222222222222 Global BR-03 Marketplace Subscription Access Check Medium Passed
230266523520
222222222222 Global BR-14 Stale Bedrock Access Check Medium Failed
230266523520
222222222222 Global BR-14 Stale Bedrock Access Check Medium Failed
230266523520
222222222222 Global BR-14 Stale Bedrock Access Check Medium Failed
230266523520
222222222222 Global BR-14 Stale Bedrock Access Check Medium Failed
230266523520
222222222222 Global BR-14 Stale Bedrock Access Check Medium Failed
230266523520
222222222222 Global BR-14 Stale Bedrock Access Check Medium Failed
230266523520
222222222222 Global BR-14 Stale Bedrock Access Check Medium Failed
230266523520
222222222222 Global BR-14 Stale Bedrock Access Check Medium Failed
230266523520
222222222222 Global BR-14 Stale Bedrock Access Check Medium Failed
230266523520
222222222222 Global BR-14 Stale Bedrock Access Check Medium Failed
230266523520
222222222222 Global BR-14 Stale Bedrock Access Check Medium Failed
230266523520
222222222222 us-east-1 BR-02 Amazon Bedrock private connectivity check Informational N/A
230266523520
222222222222 us-east-1 BR-04 Bedrock Model Invocation Logging Check Informational N/A
230266523520
222222222222 us-east-1 BR-05 Bedrock Guardrails Check Informational N/A
230266523520
222222222222 us-east-1 BR-06 Bedrock CloudTrail Logging Check Informational N/A
230266523520
222222222222 us-east-1 BR-07 Bedrock Prompt Management Check Informational N/A
230266523520
222222222222 us-east-1 BR-08 Bedrock Agent IAM Roles Check Informational N/A
230266523520
222222222222 us-east-1 BR-09 Bedrock Knowledge Base Encryption CheckUnable to check Bedrock Knowledge Base API: An error occurred (AccessDeniedException) when calling the ListKnowledgeBases operation: User: arn:aws:sts::230266523520:assumed-role/aiml-security-23026652352-BedrockSecurityAssessment-UZzmVN1xrMwf/aiml-security-aiml-security-230266523520-BedrockAssessment is not authorized to perform: bedrock:ListKnowledgeBases on resource: arn:aws:bedrock:us-east-1:230266523520:knowledge-base/* because no identity-based policy allows the bedrock:ListKnowledgeBases actionUnable to check Bedrock Knowledge Base API: An error occurred (AccessDeniedException) when calling the ListKnowledgeBases operation: User: arn:aws:sts::222222222222:assumed-role/aiml-security-23026652352-BedrockSecurityAssessment-UZzmVN1xrMwf/aiml-security-aiml-security-222222222222-BedrockAssessment is not authorized to perform: bedrock:ListKnowledgeBases on resource: arn:aws:bedrock:us-east-1:222222222222:knowledge-base/* because no identity-based policy allows the bedrock:ListKnowledgeBases action Verify your AWS credentials and permissions to access Bedrock Knowledge Bases, then retry the assessment. Informational N/A
230266523520
222222222222 us-east-1 BR-10 Bedrock Guardrail IAM Enforcement Check Informational N/A
230266523520
222222222222 us-east-1 BR-11 Bedrock Custom Model Encryption Check Informational N/A
230266523520
222222222222 us-east-1 BR-12 Bedrock Invocation Log Encryption Check Informational N/A
230266523520
222222222222 us-east-1 BR-13 Bedrock Flows Guardrails Check Informational N/A
230266523520
222222222222 Global AC-02 AgentCore IAM Full Access Check High Passed
230266523520
222222222222 Global AC-03 AgentCore Stale Access Medium Failed
230266523520
222222222222 Global AC-03 AgentCore Unused Permissions Medium Failed
230266523520
222222222222 Global AC-09 AgentCore Service-Linked Role Missing Medium Failed
230266523520
222222222222 us-east-1 AC-01 AgentCore VPC Configuration Check Informational N/A
230266523520
222222222222 us-east-1 AC-04 AgentCore Observability Check Informational N/A
230266523520
222222222222 us-east-1 AC-05 AgentCore Encryption Check Informational N/A
230266523520
222222222222 us-east-1 AC-06 AgentCore Browser Tool Recording Check Informational N/A
230266523520
222222222222 us-east-1 AC-07 AgentCore Memory Configuration Check Informational N/A
230266523520
222222222222 us-east-1 AC-13 AgentCore Gateway Configuration Check Informational N/A
230266523520
222222222222 us-east-1 AC-08 AgentCore VPC Endpoints Check Informational N/A
230266523520
222222222222 us-east-1 AC-10 AgentCore Resource-Based Policies Check Informational N/A
230266523520
222222222222 us-east-1 AC-11 AgentCore Policy Engine Encryption Check Informational N/A
230266523520
222222222222 us-east-1 AC-12 AgentCore Gateway Encryption Check Informational N/A
230266523520
222222222222 ap-southeast-2 AC-01 AgentCore VPC Configuration Check Informational N/A
230266523520
222222222222 ap-southeast-2 AC-04 AgentCore Observability Check Informational N/A
230266523520
222222222222 ap-southeast-2 AC-05 AgentCore Encryption Check Informational N/A
230266523520
222222222222 ap-southeast-2 AC-06 AgentCore Browser Tool Recording Check Informational N/A
230266523520
222222222222 ap-southeast-2 AC-07 AgentCore Memory Configuration Check Informational N/A
230266523520
222222222222 ap-southeast-2 AC-13 AgentCore Gateway Configuration Check Informational N/A
230266523520
222222222222 ap-southeast-2 AC-08 AgentCore VPC Endpoints Check Informational N/A
230266523520
222222222222 ap-southeast-2 AC-10 AgentCore Resource-Based Policies Check Informational N/A
230266523520
222222222222 ap-southeast-2 AC-11 AgentCore Policy Engine Encryption Check Informational N/A
230266523520
222222222222 ap-southeast-2 AC-12 AgentCore Gateway Encryption Check Informational N/A
230266523520
222222222222 eu-west-1 BR-02 Amazon Bedrock private connectivity check Informational N/A
230266523520
222222222222 eu-west-1 BR-04 Bedrock Model Invocation Logging Check Informational N/A
230266523520
222222222222 eu-west-1 BR-05 Bedrock Guardrails Check Informational N/A
230266523520
222222222222 eu-west-1 BR-06 Bedrock CloudTrail Logging Check Informational N/A
230266523520
222222222222 eu-west-1 BR-07 Bedrock Prompt Management Check Informational N/A
230266523520
222222222222 eu-west-1 BR-08 Bedrock Agent IAM Roles Check Informational N/A
230266523520
222222222222 eu-west-1 BR-09 Bedrock Knowledge Base Encryption CheckUnable to check Bedrock Knowledge Base API: An error occurred (AccessDeniedException) when calling the ListKnowledgeBases operation: User: arn:aws:sts::230266523520:assumed-role/aiml-security-23026652352-BedrockSecurityAssessment-UZzmVN1xrMwf/aiml-security-aiml-security-230266523520-BedrockAssessment is not authorized to perform: bedrock:ListKnowledgeBases on resource: arn:aws:bedrock:eu-west-1:230266523520:knowledge-base/* because no identity-based policy allows the bedrock:ListKnowledgeBases actionUnable to check Bedrock Knowledge Base API: An error occurred (AccessDeniedException) when calling the ListKnowledgeBases operation: User: arn:aws:sts::222222222222:assumed-role/aiml-security-23026652352-BedrockSecurityAssessment-UZzmVN1xrMwf/aiml-security-aiml-security-222222222222-BedrockAssessment is not authorized to perform: bedrock:ListKnowledgeBases on resource: arn:aws:bedrock:eu-west-1:222222222222:knowledge-base/* because no identity-based policy allows the bedrock:ListKnowledgeBases action Verify your AWS credentials and permissions to access Bedrock Knowledge Bases, then retry the assessment. Informational N/A
230266523520
222222222222 eu-west-1 BR-10 Bedrock Guardrail IAM Enforcement Check Informational N/A
230266523520
222222222222 eu-west-1 BR-11 Bedrock Custom Model Encryption Check Informational N/A
230266523520
222222222222 eu-west-1 BR-12 Bedrock Invocation Log Encryption Check Informational N/A
230266523520
222222222222 eu-west-1 BR-13 Bedrock Flows Guardrails Check
- +
Account IDRegionCheck IDFindingDetailsResolutionReferenceSeverityStatus
193047247167
+ @@ -8569,8 +8569,8 @@

High

- - + + @@ -8581,8 +8581,8 @@

High

- - + + @@ -8593,8 +8593,8 @@

High

- - + + @@ -8604,8 +8604,8 @@

High

- - + + @@ -8615,8 +8615,8 @@

High

- - + + @@ -8626,8 +8626,8 @@

High

- - + + @@ -8637,8 +8637,8 @@

High

- - + + @@ -8648,8 +8648,8 @@

High

- - + + @@ -8659,8 +8659,8 @@

High

- - + + @@ -8670,8 +8670,8 @@

High

- - + + @@ -8681,8 +8681,8 @@

High

- - + + @@ -8692,8 +8692,8 @@

High

- - + + @@ -8703,8 +8703,8 @@

High

- - + + @@ -8714,30 +8714,30 @@

High

- - + + - + - - + + - + - - + + @@ -8747,8 +8747,8 @@

Medium

- - + + @@ -8758,8 +8758,8 @@

Medium

- - + + @@ -8769,8 +8769,8 @@

Medium

- - + + @@ -8780,8 +8780,8 @@

Medium

- - + + @@ -8791,8 +8791,8 @@

Medium

- - + + @@ -8802,8 +8802,8 @@

Medium

- - + + @@ -8813,8 +8813,8 @@

Medium

- - + + @@ -8824,8 +8824,8 @@

Medium

- - + + @@ -8835,8 +8835,8 @@

Medium

- - + + @@ -8846,8 +8846,8 @@

Medium

- - + + @@ -8857,8 +8857,8 @@

Medium

- - + + @@ -8868,8 +8868,8 @@

Medium

- - + + @@ -8879,8 +8879,8 @@

Medium

- - + + @@ -8890,8 +8890,8 @@

Medium

- - + + @@ -8901,8 +8901,8 @@

Medium

- - + + @@ -8912,8 +8912,8 @@

Medium

- - + + @@ -8923,8 +8923,8 @@

Medium

- - + + @@ -8934,8 +8934,8 @@

Medium

- - + + @@ -8945,8 +8945,8 @@

Medium

- - + + @@ -8956,8 +8956,8 @@

Medium

- - + + @@ -8967,8 +8967,8 @@

Medium

- - + + @@ -8978,8 +8978,8 @@

Medium

- - + + @@ -8989,8 +8989,8 @@

Medium

- - + + @@ -9000,30 +9000,30 @@

Medium

- - + + - + - - + + - + - - + + @@ -9033,8 +9033,8 @@

Medium

- - + + @@ -9044,8 +9044,8 @@

Medium

- - + + @@ -9055,8 +9055,8 @@

Medium

- - + + @@ -9066,8 +9066,8 @@

Medium

- - + + @@ -9077,8 +9077,8 @@

Medium

- - + + @@ -9088,8 +9088,8 @@

Medium

- - + + @@ -9099,8 +9099,8 @@

Medium

- - + + @@ -9110,8 +9110,8 @@

Medium

- - + + @@ -9121,8 +9121,8 @@

Medium

- - + + @@ -9132,8 +9132,8 @@

Medium

- - + + @@ -9143,8 +9143,8 @@

Medium

- - + + @@ -9154,8 +9154,8 @@

Medium

- - + + @@ -9165,8 +9165,8 @@

Medium

- - + + @@ -9176,8 +9176,8 @@

Medium

- - + + @@ -9187,8 +9187,8 @@

Medium

- - + + @@ -9198,8 +9198,8 @@

Medium

- - + + @@ -9209,8 +9209,8 @@

Medium

- - + + @@ -9220,8 +9220,8 @@

Medium

- - + + @@ -9231,8 +9231,8 @@

Medium

- - + + @@ -9242,8 +9242,8 @@

Medium

- - + + @@ -9253,8 +9253,8 @@

Medium

- - + + @@ -9264,8 +9264,8 @@

Medium

- - + + @@ -9275,8 +9275,8 @@

Medium

- - + + @@ -9286,8 +9286,8 @@

Medium

- - + + @@ -9297,8 +9297,8 @@

Medium

- - + + @@ -9308,8 +9308,8 @@

Medium

- - + + @@ -9319,8 +9319,8 @@

Medium

- - + + @@ -9330,8 +9330,8 @@

Medium

- - + + @@ -9341,8 +9341,8 @@

Medium

- - + + @@ -9352,8 +9352,8 @@

Medium

- - + + @@ -9363,8 +9363,8 @@

Medium

- - + + @@ -9374,8 +9374,8 @@

Medium

- - + + @@ -9385,8 +9385,8 @@

Medium

- - + + @@ -9396,8 +9396,8 @@

Medium

- - + + @@ -9407,8 +9407,8 @@

Informational

- - + + @@ -9418,8 +9418,8 @@

Informational

- - + + @@ -9429,8 +9429,8 @@

Informational

- - + + @@ -9440,8 +9440,8 @@

Informational

- - + + @@ -9455,8 +9455,8 @@

Informational

- - + + @@ -9466,19 +9466,19 @@

Informational

- - + + - + - - + + @@ -9488,8 +9488,8 @@

Informational

- - + + @@ -9499,8 +9499,8 @@

Informational

- - + + @@ -9510,8 +9510,8 @@

Informational

- - + + @@ -9521,8 +9521,8 @@

Informational

- - + + @@ -9532,8 +9532,8 @@

Informational

- - + + @@ -9543,8 +9543,8 @@

Informational

- - + + @@ -9554,8 +9554,8 @@

Informational

- - + + @@ -9565,8 +9565,8 @@

Informational

- - + + @@ -9580,8 +9580,8 @@

Informational

- - + + @@ -9591,19 +9591,19 @@

Informational

- - + + - + - - + + @@ -9613,8 +9613,8 @@

Informational

- - + + @@ -9624,8 +9624,8 @@

Informational

- - + + @@ -9635,8 +9635,8 @@

Informational

- - + + @@ -9646,8 +9646,8 @@

Informational

- - + + @@ -9657,8 +9657,8 @@

Informational

- - + + @@ -9668,8 +9668,8 @@

Informational

- - + + @@ -9679,8 +9679,8 @@

Informational

- - + + @@ -9690,8 +9690,8 @@

Informational

- - + + @@ -9705,8 +9705,8 @@

Informational

- - + + @@ -9716,19 +9716,19 @@

Informational

- - + + - + - - + + @@ -9738,8 +9738,8 @@

Informational

- - + + @@ -9749,8 +9749,8 @@

Informational

- - + + @@ -9760,8 +9760,8 @@

Informational

- - + + @@ -9771,8 +9771,8 @@

Informational

- - + + @@ -9783,8 +9783,8 @@

High

- - + + @@ -9794,8 +9794,8 @@

High

- - + + @@ -9805,8 +9805,8 @@

Medium

- - + + @@ -9816,8 +9816,8 @@

Medium

- - + + @@ -9827,8 +9827,8 @@

Medium

- - + + @@ -9838,8 +9838,8 @@

Medium

- - + + @@ -9849,8 +9849,8 @@

Medium

- - + + @@ -9860,41 +9860,41 @@

Medium

- - + + - + - - + + - + - - + + - + - - + + @@ -9904,8 +9904,8 @@

Medium

- - + + @@ -9915,8 +9915,8 @@

Medium

- - + + @@ -9926,8 +9926,8 @@

Medium

- - + + @@ -9937,8 +9937,8 @@

Medium

- - + + @@ -9948,8 +9948,8 @@

Medium

- - + + @@ -9959,8 +9959,8 @@

Medium

- - + + @@ -9970,8 +9970,8 @@

Medium

- - + + @@ -9981,8 +9981,8 @@

Medium

- - + + @@ -9992,8 +9992,8 @@

Medium

- - + + @@ -10003,8 +10003,8 @@

Medium

- - + + @@ -10014,8 +10014,8 @@

Informational

- - + + @@ -10025,8 +10025,8 @@

Informational

- - + + @@ -10036,8 +10036,8 @@

Informational

- - + + @@ -10047,8 +10047,8 @@

Informational

- - + + @@ -10062,8 +10062,8 @@

Informational

- - + + @@ -10073,19 +10073,19 @@

Informational

- - + + - + - - + + @@ -10095,8 +10095,8 @@

Informational

- - + + @@ -10106,8 +10106,8 @@

Informational

- - + + @@ -10117,8 +10117,8 @@

Informational

- - + + @@ -10128,8 +10128,8 @@

Informational

- - + + @@ -10139,8 +10139,8 @@

Informational

- - + + @@ -10150,8 +10150,8 @@

Informational

- - + + @@ -10161,8 +10161,8 @@

Informational

- - + + @@ -10172,8 +10172,8 @@

Informational

- - + + @@ -10187,8 +10187,8 @@

Informational

- - + + @@ -10198,19 +10198,19 @@

Informational

- - + + - + - - + + @@ -10220,8 +10220,8 @@

Informational

- - + + @@ -10231,8 +10231,8 @@

Informational

- - + + @@ -10242,8 +10242,8 @@

Informational

- - + + @@ -10253,8 +10253,8 @@

Informational

- - + + @@ -10264,8 +10264,8 @@

Informational

- - + + @@ -10275,8 +10275,8 @@

Informational

- - + + @@ -10286,8 +10286,8 @@

Informational

- - + + @@ -10297,8 +10297,8 @@

Informational

- - + + @@ -10312,8 +10312,8 @@

Informational

- - + + @@ -10323,19 +10323,19 @@

Informational

- - + + - + - - + + @@ -10345,8 +10345,8 @@

Informational

- - + + @@ -10356,8 +10356,8 @@

Informational

- - + + @@ -10367,8 +10367,8 @@

Informational

- - + + @@ -10378,8 +10378,8 @@

Informational

- - + + @@ -10389,8 +10389,8 @@

Informational

- - + + @@ -10400,8 +10400,8 @@

Informational

- - + + @@ -10411,8 +10411,8 @@

Informational

- - + + @@ -10422,8 +10422,8 @@

Informational

- - + + @@ -10437,8 +10437,8 @@

Informational

- - + + @@ -10448,19 +10448,19 @@

Informational

- - + + - + - - + + @@ -10470,8 +10470,8 @@

Informational

- - + + @@ -10481,8 +10481,8 @@

Informational

- - + + @@ -10492,8 +10492,8 @@

Informational

- - + + @@ -10503,8 +10503,8 @@

Informational

- - + + @@ -10515,8 +10515,8 @@

High

- - + + @@ -10526,8 +10526,8 @@

Medium

- - + + @@ -10537,8 +10537,8 @@

Medium

- - + + @@ -10548,8 +10548,8 @@

Medium

- - + + @@ -10559,8 +10559,8 @@

Medium

- - + + @@ -10570,8 +10570,8 @@

Medium

- - + + @@ -10581,8 +10581,8 @@

Medium

- - + + @@ -10592,8 +10592,8 @@

Medium

- - + + @@ -10603,8 +10603,8 @@

Medium

- - + + @@ -10614,8 +10614,8 @@

Medium

- - + + @@ -10625,8 +10625,8 @@

Medium

- - + + @@ -10636,8 +10636,8 @@

Medium

- - + + @@ -10647,8 +10647,8 @@

Medium

- - + + @@ -10658,8 +10658,8 @@

Informational

- - + + @@ -10669,8 +10669,8 @@

Informational

- - + + @@ -10680,8 +10680,8 @@

Informational

- - + + @@ -10691,8 +10691,8 @@

Informational

- - + + @@ -10706,8 +10706,8 @@

Informational

- - + + @@ -10717,19 +10717,19 @@

Informational

- - + + - + - - + + @@ -10739,8 +10739,8 @@

Informational

- - + + @@ -10750,8 +10750,8 @@

Informational

- - + + @@ -10761,8 +10761,8 @@

Informational

- - + + @@ -10772,8 +10772,8 @@

Informational

- - + + @@ -10783,8 +10783,8 @@

Informational

- - + + @@ -10794,8 +10794,8 @@

Informational

- - + + @@ -10805,8 +10805,8 @@

Informational

- - + + @@ -10816,8 +10816,8 @@

Informational

- - + + @@ -10831,8 +10831,8 @@

Informational

- - + + @@ -10842,19 +10842,19 @@

Informational

- - + + - + - - + + @@ -10864,8 +10864,8 @@

Informational

- - + + @@ -10875,8 +10875,8 @@

Informational

- - + + @@ -10886,8 +10886,8 @@

Informational

- - + + @@ -10902,14 +10902,14 @@

Amazon SageMaker Findings
-
+
-

Account IDRegionCheck IDFindingDetailsResolutionReferenceSeverityStatus
111111111111 Global BR-01 AmazonBedrockFullAccess role checkFailed
193047247167
111111111111 Global BR-01 AmazonBedrockFullAccess role checkFailed
193047247167
111111111111 Global BR-01 AmazonBedrockFullAccess role checkFailed
193047247167
111111111111 Global BR-03 Marketplace Subscription Access CheckFailed
193047247167
111111111111 Global BR-03 Marketplace Subscription Access CheckFailed
193047247167
111111111111 Global BR-03 Marketplace Subscription Access CheckFailed
193047247167
111111111111 Global BR-03 Marketplace Subscription Access CheckFailed
193047247167
111111111111 Global BR-03 Marketplace Subscription Access CheckFailed
193047247167
111111111111 Global BR-03 Marketplace Subscription Access CheckFailed
193047247167
111111111111 Global BR-03 Marketplace Subscription Access CheckFailed
193047247167
111111111111 Global BR-03 Marketplace Subscription Access CheckFailed
193047247167
111111111111 Global BR-03 Marketplace Subscription Access CheckFailed
193047247167
111111111111 Global BR-03 Marketplace Subscription Access CheckFailed
193047247167
111111111111 Global BR-03 Marketplace Subscription Access CheckFailed
193047247167
111111111111 Global BR-14 Stale Bedrock Access CheckRole '193047247167-us-east-1-kb-bedrock-service-role' last accessed Bedrock on 2025-12-22Role '111111111111-us-east-1-kb-bedrock-service-role' last accessed Bedrock on 2025-12-22 You can use last accessed information to refine your policies and allow access to only the services and actions that your IAM identities and policies use. This helps you to better adhere to the best practice of least privilege. Medium Failed
193047247167
111111111111 Global BR-14 Stale Bedrock Access CheckRole '193047247167-us-east-1-kb-setup-function-role' last accessed Bedrock on 2025-12-22Role '111111111111-us-east-1-kb-setup-function-role' last accessed Bedrock on 2025-12-22 You can use last accessed information to refine your policies and allow access to only the services and actions that your IAM identities and policies use. This helps you to better adhere to the best practice of least privilege. Medium Failed
193047247167
111111111111 Global BR-14 Stale Bedrock Access CheckFailed
193047247167
111111111111 Global BR-14 Stale Bedrock Access CheckFailed
193047247167
111111111111 Global BR-14 Stale Bedrock Access CheckFailed
193047247167
111111111111 Global BR-14 Stale Bedrock Access CheckFailed
193047247167
111111111111 Global BR-14 Stale Bedrock Access CheckFailed
193047247167
111111111111 Global BR-14 Stale Bedrock Access CheckFailed
193047247167
111111111111 Global BR-14 Stale Bedrock Access CheckFailed
193047247167
111111111111 Global BR-14 Stale Bedrock Access CheckFailed
193047247167
111111111111 Global BR-14 Stale Bedrock Access CheckFailed
193047247167
111111111111 Global BR-14 Stale Bedrock Access CheckFailed
193047247167
111111111111 Global BR-14 Stale Bedrock Access CheckFailed
193047247167
111111111111 Global BR-14 Stale Bedrock Access CheckFailed
193047247167
111111111111 Global BR-14 Stale Bedrock Access CheckFailed
193047247167
111111111111 Global BR-14 Stale Bedrock Access CheckFailed
193047247167
111111111111 Global BR-14 Stale Bedrock Access CheckFailed
193047247167
111111111111 Global BR-14 Stale Bedrock Access CheckFailed
193047247167
111111111111 Global BR-14 Stale Bedrock Access CheckFailed
193047247167
111111111111 Global BR-14 Stale Bedrock Access CheckFailed
193047247167
111111111111 Global BR-14 Stale Bedrock Access CheckFailed
193047247167
111111111111 Global BR-14 Stale Bedrock Access CheckFailed
193047247167
111111111111 Global BR-14 Stale Bedrock Access CheckFailed
193047247167
111111111111 Global BR-14 Stale Bedrock Access CheckFailed
193047247167
111111111111 Global BR-14 Stale Bedrock Access CheckFailed
193047247167
111111111111 Global BR-14 Stale Bedrock Access CheckFailed
193047247167
111111111111 Global BR-14 Stale Bedrock Access CheckRole 'cdk-hnb659fds-lookup-role-193047247167-us-east-1' last accessed Bedrock on neverRole 'cdk-hnb659fds-lookup-role-111111111111-us-east-1' last accessed Bedrock on never You can use last accessed information to refine your policies and allow access to only the services and actions that your IAM identities and policies use. This helps you to better adhere to the best practice of least privilege. Medium Failed
193047247167
111111111111 Global BR-14 Stale Bedrock Access CheckRole 'cdk-hnb659fds-lookup-role-193047247167-us-west-2' last accessed Bedrock on neverRole 'cdk-hnb659fds-lookup-role-111111111111-us-west-2' last accessed Bedrock on never You can use last accessed information to refine your policies and allow access to only the services and actions that your IAM identities and policies use. This helps you to better adhere to the best practice of least privilege. Medium Failed
193047247167
111111111111 Global BR-14 Stale Bedrock Access CheckFailed
193047247167
111111111111 Global BR-14 Stale Bedrock Access CheckFailed
193047247167
111111111111 Global BR-14 Stale Bedrock Access CheckFailed
193047247167
111111111111 Global BR-14 Stale Bedrock Access CheckFailed
193047247167
111111111111 Global BR-14 Stale Bedrock Access CheckFailed
193047247167
111111111111 Global BR-14 Stale Bedrock Access CheckFailed
193047247167
111111111111 Global BR-14 Stale Bedrock Access CheckFailed
193047247167
111111111111 Global BR-14 Stale Bedrock Access CheckFailed
193047247167
111111111111 Global BR-14 Stale Bedrock Access CheckFailed
193047247167
111111111111 Global BR-14 Stale Bedrock Access CheckFailed
193047247167
111111111111 Global BR-14 Stale Bedrock Access CheckFailed
193047247167
111111111111 Global BR-14 Stale Bedrock Access CheckFailed
193047247167
111111111111 Global BR-14 Stale Bedrock Access CheckFailed
193047247167
111111111111 Global BR-14 Stale Bedrock Access CheckFailed
193047247167
111111111111 Global BR-14 Stale Bedrock Access CheckFailed
193047247167
111111111111 Global BR-14 Stale Bedrock Access CheckFailed
193047247167
111111111111 Global BR-14 Stale Bedrock Access CheckFailed
193047247167
111111111111 Global BR-14 Stale Bedrock Access CheckFailed
193047247167
111111111111 Global BR-14 Stale Bedrock Access CheckFailed
193047247167
111111111111 Global BR-14 Stale Bedrock Access CheckFailed
193047247167
111111111111 Global BR-14 Stale Bedrock Access CheckFailed
193047247167
111111111111 Global BR-14 Stale Bedrock Access CheckFailed
193047247167
111111111111 Global BR-14 Stale Bedrock Access CheckFailed
193047247167
111111111111 Global BR-14 Stale Bedrock Access CheckFailed
193047247167
111111111111 Global BR-14 Stale Bedrock Access CheckFailed
193047247167
111111111111 Global BR-14 Stale Bedrock Access CheckFailed
193047247167
111111111111 Global BR-14 Stale Bedrock Access CheckFailed
193047247167
111111111111 Global BR-14 Stale Bedrock Access CheckFailed
193047247167
111111111111 Global BR-14 Stale Bedrock Access CheckFailed
193047247167
111111111111 Global BR-14 Stale Bedrock Access CheckFailed
193047247167
111111111111 Global BR-14 Stale Bedrock Access CheckFailed
193047247167
111111111111 Global BR-14 Stale Bedrock Access CheckFailed
193047247167
111111111111 Global BR-14 Stale Bedrock Access CheckFailed
193047247167
111111111111 Global BR-14 Stale Bedrock Access CheckFailed
193047247167
111111111111 us-east-1 BR-02 Amazon Bedrock private connectivity checkN/A
193047247167
111111111111 us-east-1 BR-04 Bedrock Model Invocation Logging CheckN/A
193047247167
111111111111 us-east-1 BR-05 Bedrock Guardrails CheckN/A
193047247167
111111111111 us-east-1 BR-06 Bedrock CloudTrail Logging CheckN/A
193047247167
111111111111 us-east-1 BR-07 Bedrock Prompt Management CheckN/A
193047247167
111111111111 us-east-1 BR-08 Bedrock Agent IAM Roles CheckN/A
193047247167
111111111111 us-east-1 BR-09 Bedrock Knowledge Base Encryption CheckUnable to check Bedrock Knowledge Base API: An error occurred (AccessDeniedException) when calling the ListKnowledgeBases operation: User: arn:aws:sts::193047247167:assumed-role/aiml-security-19304724716-BedrockSecurityAssessment-vv6H0eGD9ESX/aiml-security-aiml-security-193047247167-BedrockAssessment is not authorized to perform: bedrock:ListKnowledgeBases on resource: arn:aws:bedrock:us-east-1:193047247167:knowledge-base/* because no identity-based policy allows the bedrock:ListKnowledgeBases actionUnable to check Bedrock Knowledge Base API: An error occurred (AccessDeniedException) when calling the ListKnowledgeBases operation: User: arn:aws:sts::111111111111:assumed-role/aiml-security-19304724716-BedrockSecurityAssessment-vv6H0eGD9ESX/aiml-security-aiml-security-111111111111-BedrockAssessment is not authorized to perform: bedrock:ListKnowledgeBases on resource: arn:aws:bedrock:us-east-1:111111111111:knowledge-base/* because no identity-based policy allows the bedrock:ListKnowledgeBases action Verify your AWS credentials and permissions to access Bedrock Knowledge Bases, then retry the assessment. Informational N/A
193047247167
111111111111 us-east-1 BR-10 Bedrock Guardrail IAM Enforcement CheckN/A
193047247167
111111111111 us-east-1 BR-11 Bedrock Custom Model Encryption CheckN/A
193047247167
111111111111 us-east-1 BR-12 Bedrock Invocation Log Encryption CheckN/A
193047247167
111111111111 us-east-1 BR-13 Bedrock Flows Guardrails CheckN/A
193047247167
111111111111 ap-southeast-2 BR-02 Amazon Bedrock private connectivity checkN/A
193047247167
111111111111 ap-southeast-2 BR-04 Bedrock Model Invocation Logging CheckN/A
193047247167
111111111111 ap-southeast-2 BR-05 Bedrock Guardrails CheckN/A
193047247167
111111111111 ap-southeast-2 BR-06 Bedrock CloudTrail Logging CheckN/A
193047247167
111111111111 ap-southeast-2 BR-07 Bedrock Prompt Management CheckN/A
193047247167
111111111111 ap-southeast-2 BR-08 Bedrock Agent IAM Roles CheckN/A
193047247167
111111111111 ap-southeast-2 BR-09 Bedrock Knowledge Base Encryption CheckUnable to check Bedrock Knowledge Base API: An error occurred (AccessDeniedException) when calling the ListKnowledgeBases operation: User: arn:aws:sts::193047247167:assumed-role/aiml-security-19304724716-BedrockSecurityAssessment-vv6H0eGD9ESX/aiml-security-aiml-security-193047247167-BedrockAssessment is not authorized to perform: bedrock:ListKnowledgeBases on resource: arn:aws:bedrock:ap-southeast-2:193047247167:knowledge-base/* because no identity-based policy allows the bedrock:ListKnowledgeBases actionUnable to check Bedrock Knowledge Base API: An error occurred (AccessDeniedException) when calling the ListKnowledgeBases operation: User: arn:aws:sts::111111111111:assumed-role/aiml-security-19304724716-BedrockSecurityAssessment-vv6H0eGD9ESX/aiml-security-aiml-security-111111111111-BedrockAssessment is not authorized to perform: bedrock:ListKnowledgeBases on resource: arn:aws:bedrock:ap-southeast-2:111111111111:knowledge-base/* because no identity-based policy allows the bedrock:ListKnowledgeBases action Verify your AWS credentials and permissions to access Bedrock Knowledge Bases, then retry the assessment. Informational N/A
193047247167
111111111111 ap-southeast-2 BR-10 Bedrock Guardrail IAM Enforcement CheckN/A
193047247167
111111111111 ap-southeast-2 BR-11 Bedrock Custom Model Encryption CheckN/A
193047247167
111111111111 ap-southeast-2 BR-12 Bedrock Invocation Log Encryption CheckN/A
193047247167
111111111111 ap-southeast-2 BR-13 Bedrock Flows Guardrails CheckN/A
193047247167
111111111111 eu-west-1 BR-02 Amazon Bedrock private connectivity checkN/A
193047247167
111111111111 eu-west-1 BR-04 Bedrock Model Invocation Logging CheckN/A
193047247167
111111111111 eu-west-1 BR-05 Bedrock Guardrails CheckN/A
193047247167
111111111111 eu-west-1 BR-06 Bedrock CloudTrail Logging CheckN/A
193047247167
111111111111 eu-west-1 BR-07 Bedrock Prompt Management CheckN/A
193047247167
111111111111 eu-west-1 BR-08 Bedrock Agent IAM Roles CheckN/A
193047247167
111111111111 eu-west-1 BR-09 Bedrock Knowledge Base Encryption CheckUnable to check Bedrock Knowledge Base API: An error occurred (AccessDeniedException) when calling the ListKnowledgeBases operation: User: arn:aws:sts::193047247167:assumed-role/aiml-security-19304724716-BedrockSecurityAssessment-vv6H0eGD9ESX/aiml-security-aiml-security-193047247167-BedrockAssessment is not authorized to perform: bedrock:ListKnowledgeBases on resource: arn:aws:bedrock:eu-west-1:193047247167:knowledge-base/* because no identity-based policy allows the bedrock:ListKnowledgeBases actionUnable to check Bedrock Knowledge Base API: An error occurred (AccessDeniedException) when calling the ListKnowledgeBases operation: User: arn:aws:sts::111111111111:assumed-role/aiml-security-19304724716-BedrockSecurityAssessment-vv6H0eGD9ESX/aiml-security-aiml-security-111111111111-BedrockAssessment is not authorized to perform: bedrock:ListKnowledgeBases on resource: arn:aws:bedrock:eu-west-1:111111111111:knowledge-base/* because no identity-based policy allows the bedrock:ListKnowledgeBases action Verify your AWS credentials and permissions to access Bedrock Knowledge Bases, then retry the assessment. Informational N/A
193047247167
111111111111 eu-west-1 BR-10 Bedrock Guardrail IAM Enforcement CheckN/A
193047247167
111111111111 eu-west-1 BR-11 Bedrock Custom Model Encryption CheckN/A
193047247167
111111111111 eu-west-1 BR-12 Bedrock Invocation Log Encryption CheckN/A
193047247167
111111111111 eu-west-1 BR-13 Bedrock Flows Guardrails CheckN/A
472057511786
333333333333 Global BR-01 AmazonBedrockFullAccess role checkPassed
472057511786
333333333333 Global BR-03 Marketplace Subscription Access CheckFailed
472057511786
333333333333 Global BR-14 Stale Bedrock Access CheckFailed
472057511786
333333333333 Global BR-14 Stale Bedrock Access CheckFailed
472057511786
333333333333 Global BR-14 Stale Bedrock Access CheckFailed
472057511786
333333333333 Global BR-14 Stale Bedrock Access CheckFailed
472057511786
333333333333 Global BR-14 Stale Bedrock Access CheckFailed
472057511786
333333333333 Global BR-14 Stale Bedrock Access CheckFailed
472057511786
333333333333 Global BR-14 Stale Bedrock Access CheckRole 'cdk-hnb659fds-lookup-role-472057511786-us-east-1' last accessed Bedrock on neverRole 'cdk-hnb659fds-lookup-role-333333333333-us-east-1' last accessed Bedrock on never You can use last accessed information to refine your policies and allow access to only the services and actions that your IAM identities and policies use. This helps you to better adhere to the best practice of least privilege. Medium Failed
472057511786
333333333333 Global BR-14 Stale Bedrock Access CheckRole 'cdk-hnb659fds-lookup-role-472057511786-us-east-2' last accessed Bedrock on neverRole 'cdk-hnb659fds-lookup-role-333333333333-us-east-2' last accessed Bedrock on never You can use last accessed information to refine your policies and allow access to only the services and actions that your IAM identities and policies use. This helps you to better adhere to the best practice of least privilege. Medium Failed
472057511786
333333333333 Global BR-14 Stale Bedrock Access CheckRole 'cdk-hnb659fds-lookup-role-472057511786-us-west-2' last accessed Bedrock on neverRole 'cdk-hnb659fds-lookup-role-333333333333-us-west-2' last accessed Bedrock on never You can use last accessed information to refine your policies and allow access to only the services and actions that your IAM identities and policies use. This helps you to better adhere to the best practice of least privilege. Medium Failed
472057511786
333333333333 Global BR-14 Stale Bedrock Access CheckFailed
472057511786
333333333333 Global BR-14 Stale Bedrock Access CheckFailed
472057511786
333333333333 Global BR-14 Stale Bedrock Access CheckFailed
472057511786
333333333333 Global BR-14 Stale Bedrock Access CheckFailed
472057511786
333333333333 Global BR-14 Stale Bedrock Access CheckFailed
472057511786
333333333333 Global BR-14 Stale Bedrock Access CheckFailed
472057511786
333333333333 Global BR-14 Stale Bedrock Access CheckFailed
472057511786
333333333333 Global BR-14 Stale Bedrock Access CheckFailed
472057511786
333333333333 Global BR-14 Stale Bedrock Access CheckFailed
472057511786
333333333333 Global BR-14 Stale Bedrock Access CheckFailed
472057511786
333333333333 us-east-1 BR-02 Amazon Bedrock private connectivity checkN/A
472057511786
333333333333 us-east-1 BR-04 Bedrock Model Invocation Logging CheckN/A
472057511786
333333333333 us-east-1 BR-05 Bedrock Guardrails CheckN/A
472057511786
333333333333 us-east-1 BR-06 Bedrock CloudTrail Logging CheckN/A
472057511786
333333333333 us-east-1 BR-07 Bedrock Prompt Management CheckN/A
472057511786
333333333333 us-east-1 BR-08 Bedrock Agent IAM Roles CheckN/A
472057511786
333333333333 us-east-1 BR-09 Bedrock Knowledge Base Encryption CheckUnable to check Bedrock Knowledge Base API: An error occurred (AccessDeniedException) when calling the ListKnowledgeBases operation: User: arn:aws:sts::472057511786:assumed-role/aiml-security-mgmt-BedrockSecurityAssessmentFunctio-3SFVOekaDS6b/aiml-security-aiml-security-mgmt-BedrockAssessment is not authorized to perform: bedrock:ListKnowledgeBases on resource: arn:aws:bedrock:us-east-1:472057511786:knowledge-base/* because no identity-based policy allows the bedrock:ListKnowledgeBases actionUnable to check Bedrock Knowledge Base API: An error occurred (AccessDeniedException) when calling the ListKnowledgeBases operation: User: arn:aws:sts::333333333333:assumed-role/aiml-security-mgmt-BedrockSecurityAssessmentFunctio-3SFVOekaDS6b/aiml-security-aiml-security-mgmt-BedrockAssessment is not authorized to perform: bedrock:ListKnowledgeBases on resource: arn:aws:bedrock:us-east-1:333333333333:knowledge-base/* because no identity-based policy allows the bedrock:ListKnowledgeBases action Verify your AWS credentials and permissions to access Bedrock Knowledge Bases, then retry the assessment. Informational N/A
472057511786
333333333333 us-east-1 BR-10 Bedrock Guardrail IAM Enforcement CheckN/A
472057511786
333333333333 us-east-1 BR-11 Bedrock Custom Model Encryption CheckN/A
472057511786
333333333333 us-east-1 BR-12 Bedrock Invocation Log Encryption CheckN/A
472057511786
333333333333 us-east-1 BR-13 Bedrock Flows Guardrails CheckN/A
472057511786
333333333333 ap-southeast-2 BR-02 Amazon Bedrock private connectivity checkN/A
472057511786
333333333333 ap-southeast-2 BR-04 Bedrock Model Invocation Logging CheckN/A
472057511786
333333333333 ap-southeast-2 BR-05 Bedrock Guardrails CheckN/A
472057511786
333333333333 ap-southeast-2 BR-06 Bedrock CloudTrail Logging CheckN/A
472057511786
333333333333 ap-southeast-2 BR-07 Bedrock Prompt Management CheckN/A
472057511786
333333333333 ap-southeast-2 BR-08 Bedrock Agent IAM Roles CheckN/A
472057511786
333333333333 ap-southeast-2 BR-09 Bedrock Knowledge Base Encryption CheckUnable to check Bedrock Knowledge Base API: An error occurred (AccessDeniedException) when calling the ListKnowledgeBases operation: User: arn:aws:sts::472057511786:assumed-role/aiml-security-mgmt-BedrockSecurityAssessmentFunctio-3SFVOekaDS6b/aiml-security-aiml-security-mgmt-BedrockAssessment is not authorized to perform: bedrock:ListKnowledgeBases on resource: arn:aws:bedrock:ap-southeast-2:472057511786:knowledge-base/* because no identity-based policy allows the bedrock:ListKnowledgeBases actionUnable to check Bedrock Knowledge Base API: An error occurred (AccessDeniedException) when calling the ListKnowledgeBases operation: User: arn:aws:sts::333333333333:assumed-role/aiml-security-mgmt-BedrockSecurityAssessmentFunctio-3SFVOekaDS6b/aiml-security-aiml-security-mgmt-BedrockAssessment is not authorized to perform: bedrock:ListKnowledgeBases on resource: arn:aws:bedrock:ap-southeast-2:333333333333:knowledge-base/* because no identity-based policy allows the bedrock:ListKnowledgeBases action Verify your AWS credentials and permissions to access Bedrock Knowledge Bases, then retry the assessment. Informational N/A
472057511786
333333333333 ap-southeast-2 BR-10 Bedrock Guardrail IAM Enforcement CheckN/A
472057511786
333333333333 ap-southeast-2 BR-11 Bedrock Custom Model Encryption CheckN/A
472057511786
333333333333 ap-southeast-2 BR-12 Bedrock Invocation Log Encryption CheckN/A
472057511786
333333333333 ap-southeast-2 BR-13 Bedrock Flows Guardrails CheckN/A
472057511786
333333333333 eu-west-1 BR-02 Amazon Bedrock private connectivity checkN/A
472057511786
333333333333 eu-west-1 BR-04 Bedrock Model Invocation Logging CheckN/A
472057511786
333333333333 eu-west-1 BR-05 Bedrock Guardrails CheckN/A
472057511786
333333333333 eu-west-1 BR-06 Bedrock CloudTrail Logging CheckN/A
472057511786
333333333333 eu-west-1 BR-07 Bedrock Prompt Management CheckN/A
472057511786
333333333333 eu-west-1 BR-08 Bedrock Agent IAM Roles CheckN/A
472057511786
333333333333 eu-west-1 BR-09 Bedrock Knowledge Base Encryption CheckUnable to check Bedrock Knowledge Base API: An error occurred (AccessDeniedException) when calling the ListKnowledgeBases operation: User: arn:aws:sts::472057511786:assumed-role/aiml-security-mgmt-BedrockSecurityAssessmentFunctio-3SFVOekaDS6b/aiml-security-aiml-security-mgmt-BedrockAssessment is not authorized to perform: bedrock:ListKnowledgeBases on resource: arn:aws:bedrock:eu-west-1:472057511786:knowledge-base/* because no identity-based policy allows the bedrock:ListKnowledgeBases actionUnable to check Bedrock Knowledge Base API: An error occurred (AccessDeniedException) when calling the ListKnowledgeBases operation: User: arn:aws:sts::333333333333:assumed-role/aiml-security-mgmt-BedrockSecurityAssessmentFunctio-3SFVOekaDS6b/aiml-security-aiml-security-mgmt-BedrockAssessment is not authorized to perform: bedrock:ListKnowledgeBases on resource: arn:aws:bedrock:eu-west-1:333333333333:knowledge-base/* because no identity-based policy allows the bedrock:ListKnowledgeBases action Verify your AWS credentials and permissions to access Bedrock Knowledge Bases, then retry the assessment. Informational N/A
472057511786
333333333333 eu-west-1 BR-10 Bedrock Guardrail IAM Enforcement CheckN/A
472057511786
333333333333 eu-west-1 BR-11 Bedrock Custom Model Encryption CheckN/A
472057511786
333333333333 eu-west-1 BR-12 Bedrock Invocation Log Encryption CheckN/A
472057511786
333333333333 eu-west-1 BR-13 Bedrock Flows Guardrails CheckN/A
230266523520
222222222222 ap-southeast-2 BR-02 Amazon Bedrock private connectivity checkN/A
230266523520
222222222222 ap-southeast-2 BR-04 Bedrock Model Invocation Logging CheckN/A
230266523520
222222222222 ap-southeast-2 BR-05 Bedrock Guardrails CheckN/A
230266523520
222222222222 ap-southeast-2 BR-06 Bedrock CloudTrail Logging CheckN/A
230266523520
222222222222 ap-southeast-2 BR-07 Bedrock Prompt Management CheckN/A
230266523520
222222222222 ap-southeast-2 BR-08 Bedrock Agent IAM Roles CheckN/A
230266523520
222222222222 ap-southeast-2 BR-09 Bedrock Knowledge Base Encryption CheckUnable to check Bedrock Knowledge Base API: An error occurred (AccessDeniedException) when calling the ListKnowledgeBases operation: User: arn:aws:sts::230266523520:assumed-role/aiml-security-23026652352-BedrockSecurityAssessment-UZzmVN1xrMwf/aiml-security-aiml-security-230266523520-BedrockAssessment is not authorized to perform: bedrock:ListKnowledgeBases on resource: arn:aws:bedrock:ap-southeast-2:230266523520:knowledge-base/* because no identity-based policy allows the bedrock:ListKnowledgeBases actionUnable to check Bedrock Knowledge Base API: An error occurred (AccessDeniedException) when calling the ListKnowledgeBases operation: User: arn:aws:sts::222222222222:assumed-role/aiml-security-23026652352-BedrockSecurityAssessment-UZzmVN1xrMwf/aiml-security-aiml-security-222222222222-BedrockAssessment is not authorized to perform: bedrock:ListKnowledgeBases on resource: arn:aws:bedrock:ap-southeast-2:222222222222:knowledge-base/* because no identity-based policy allows the bedrock:ListKnowledgeBases action Verify your AWS credentials and permissions to access Bedrock Knowledge Bases, then retry the assessment. Informational N/A
230266523520
222222222222 ap-southeast-2 BR-10 Bedrock Guardrail IAM Enforcement CheckN/A
230266523520
222222222222 ap-southeast-2 BR-11 Bedrock Custom Model Encryption CheckN/A
230266523520
222222222222 ap-southeast-2 BR-12 Bedrock Invocation Log Encryption CheckN/A
230266523520
222222222222 ap-southeast-2 BR-13 Bedrock Flows Guardrails CheckN/A
230266523520
222222222222 Global BR-01 AmazonBedrockFullAccess role checkPassed
230266523520
222222222222 Global BR-03 Marketplace Subscription Access CheckPassed
230266523520
222222222222 Global BR-14 Stale Bedrock Access CheckFailed
230266523520
222222222222 Global BR-14 Stale Bedrock Access CheckFailed
230266523520
222222222222 Global BR-14 Stale Bedrock Access CheckFailed
230266523520
222222222222 Global BR-14 Stale Bedrock Access CheckFailed
230266523520
222222222222 Global BR-14 Stale Bedrock Access CheckFailed
230266523520
222222222222 Global BR-14 Stale Bedrock Access CheckFailed
230266523520
222222222222 Global BR-14 Stale Bedrock Access CheckFailed
230266523520
222222222222 Global BR-14 Stale Bedrock Access CheckFailed
230266523520
222222222222 Global BR-14 Stale Bedrock Access CheckFailed
230266523520
222222222222 Global BR-14 Stale Bedrock Access CheckFailed
230266523520
222222222222 Global BR-14 Stale Bedrock Access CheckFailed
230266523520
222222222222 us-east-1 BR-02 Amazon Bedrock private connectivity checkN/A
230266523520
222222222222 us-east-1 BR-04 Bedrock Model Invocation Logging CheckN/A
230266523520
222222222222 us-east-1 BR-05 Bedrock Guardrails CheckN/A
230266523520
222222222222 us-east-1 BR-06 Bedrock CloudTrail Logging CheckN/A
230266523520
222222222222 us-east-1 BR-07 Bedrock Prompt Management CheckN/A
230266523520
222222222222 us-east-1 BR-08 Bedrock Agent IAM Roles CheckN/A
230266523520
222222222222 us-east-1 BR-09 Bedrock Knowledge Base Encryption CheckUnable to check Bedrock Knowledge Base API: An error occurred (AccessDeniedException) when calling the ListKnowledgeBases operation: User: arn:aws:sts::230266523520:assumed-role/aiml-security-23026652352-BedrockSecurityAssessment-UZzmVN1xrMwf/aiml-security-aiml-security-230266523520-BedrockAssessment is not authorized to perform: bedrock:ListKnowledgeBases on resource: arn:aws:bedrock:us-east-1:230266523520:knowledge-base/* because no identity-based policy allows the bedrock:ListKnowledgeBases actionUnable to check Bedrock Knowledge Base API: An error occurred (AccessDeniedException) when calling the ListKnowledgeBases operation: User: arn:aws:sts::222222222222:assumed-role/aiml-security-23026652352-BedrockSecurityAssessment-UZzmVN1xrMwf/aiml-security-aiml-security-222222222222-BedrockAssessment is not authorized to perform: bedrock:ListKnowledgeBases on resource: arn:aws:bedrock:us-east-1:222222222222:knowledge-base/* because no identity-based policy allows the bedrock:ListKnowledgeBases action Verify your AWS credentials and permissions to access Bedrock Knowledge Bases, then retry the assessment. Informational N/A
230266523520
222222222222 us-east-1 BR-10 Bedrock Guardrail IAM Enforcement CheckN/A
230266523520
222222222222 us-east-1 BR-11 Bedrock Custom Model Encryption CheckN/A
230266523520
222222222222 us-east-1 BR-12 Bedrock Invocation Log Encryption CheckN/A
230266523520
222222222222 us-east-1 BR-13 Bedrock Flows Guardrails CheckN/A
230266523520
222222222222 eu-west-1 BR-02 Amazon Bedrock private connectivity checkN/A
230266523520
222222222222 eu-west-1 BR-04 Bedrock Model Invocation Logging CheckN/A
230266523520
222222222222 eu-west-1 BR-05 Bedrock Guardrails CheckN/A
230266523520
222222222222 eu-west-1 BR-06 Bedrock CloudTrail Logging CheckN/A
230266523520
222222222222 eu-west-1 BR-07 Bedrock Prompt Management CheckN/A
230266523520
222222222222 eu-west-1 BR-08 Bedrock Agent IAM Roles CheckN/A
230266523520
222222222222 eu-west-1 BR-09 Bedrock Knowledge Base Encryption CheckUnable to check Bedrock Knowledge Base API: An error occurred (AccessDeniedException) when calling the ListKnowledgeBases operation: User: arn:aws:sts::230266523520:assumed-role/aiml-security-23026652352-BedrockSecurityAssessment-UZzmVN1xrMwf/aiml-security-aiml-security-230266523520-BedrockAssessment is not authorized to perform: bedrock:ListKnowledgeBases on resource: arn:aws:bedrock:eu-west-1:230266523520:knowledge-base/* because no identity-based policy allows the bedrock:ListKnowledgeBases actionUnable to check Bedrock Knowledge Base API: An error occurred (AccessDeniedException) when calling the ListKnowledgeBases operation: User: arn:aws:sts::222222222222:assumed-role/aiml-security-23026652352-BedrockSecurityAssessment-UZzmVN1xrMwf/aiml-security-aiml-security-222222222222-BedrockAssessment is not authorized to perform: bedrock:ListKnowledgeBases on resource: arn:aws:bedrock:eu-west-1:222222222222:knowledge-base/* because no identity-based policy allows the bedrock:ListKnowledgeBases action Verify your AWS credentials and permissions to access Bedrock Knowledge Bases, then retry the assessment. Informational N/A
230266523520
222222222222 eu-west-1 BR-10 Bedrock Guardrail IAM Enforcement CheckN/A
230266523520
222222222222 eu-west-1 BR-11 Bedrock Custom Model Encryption CheckN/A
230266523520
222222222222 eu-west-1 BR-12 Bedrock Invocation Log Encryption CheckN/A
230266523520
222222222222 eu-west-1 BR-13 Bedrock Flows Guardrails Check
- +
Account IDRegionCheck IDFindingDetailsResolutionReferenceSeverityStatus
193047247167
+ @@ -10919,8 +10919,8 @@

Informational

- - + + @@ -10930,8 +10930,8 @@

Medium

- - + + @@ -10941,8 +10941,8 @@

Informational

- - + + @@ -10952,8 +10952,8 @@

Medium

- - + + @@ -10963,8 +10963,8 @@

Informational

- - + + @@ -10974,8 +10974,8 @@

Informational

- - + + @@ -10985,8 +10985,8 @@

Informational

- - + + @@ -10996,8 +10996,8 @@

Informational

- - + + @@ -11007,8 +11007,8 @@

Informational

- - + + @@ -11018,8 +11018,8 @@

Informational

- - + + @@ -11029,8 +11029,8 @@

Informational

- - + + @@ -11040,8 +11040,8 @@

Informational

- - + + @@ -11051,8 +11051,8 @@

Informational

- - + + @@ -11062,8 +11062,8 @@

Informational

- - + + @@ -11073,8 +11073,8 @@

Informational

- - + + @@ -11084,8 +11084,8 @@

Informational

- - + + @@ -11095,8 +11095,8 @@

Informational

- - + + @@ -11106,8 +11106,8 @@

Informational

- - + + @@ -11117,8 +11117,8 @@

Informational

- - + + @@ -11128,8 +11128,8 @@

Informational

- - + + @@ -11139,8 +11139,8 @@

Informational

- - + + @@ -11150,8 +11150,8 @@

Informational

- - + + @@ -11161,8 +11161,8 @@

Informational

- - + + @@ -11172,8 +11172,8 @@

Informational

- - + + @@ -11183,8 +11183,8 @@

Medium

- - + + @@ -11194,8 +11194,8 @@

Low

- - + + @@ -11205,8 +11205,8 @@

Informational

- - + + @@ -11216,8 +11216,8 @@

High

- - + + @@ -11227,8 +11227,8 @@

High

- - + + @@ -11238,8 +11238,8 @@

High

- - + + @@ -11249,8 +11249,8 @@

High

- - + + @@ -11260,8 +11260,8 @@

High

- - + + @@ -11271,8 +11271,8 @@

High

- - + + @@ -11282,8 +11282,8 @@

High

- - + + @@ -11293,8 +11293,8 @@

High

- - + + @@ -11304,8 +11304,8 @@

Medium

- - + + @@ -11315,8 +11315,8 @@

High

- - + + @@ -11326,8 +11326,8 @@

Medium

- - + + @@ -11337,8 +11337,8 @@

Informational

- - + + @@ -11348,8 +11348,8 @@

Informational

- - + + @@ -11359,8 +11359,8 @@

Informational

- - + + @@ -11370,8 +11370,8 @@

Informational

- - + + @@ -11381,8 +11381,8 @@

Informational

- - + + @@ -11392,8 +11392,8 @@

Informational

- - + + @@ -11403,8 +11403,8 @@

Informational

- - + + @@ -11414,8 +11414,8 @@

Informational

- - + + @@ -11425,8 +11425,8 @@

Informational

- - + + @@ -11436,8 +11436,8 @@

Informational

- - + + @@ -11447,8 +11447,8 @@

Informational

- - + + @@ -11458,8 +11458,8 @@

Informational

- - + + @@ -11469,8 +11469,8 @@

Informational

- - + + @@ -11480,8 +11480,8 @@

Informational

- - + + @@ -11491,8 +11491,8 @@

Informational

- - + + @@ -11502,8 +11502,8 @@

Informational

- - + + @@ -11513,8 +11513,8 @@

Informational

- - + + @@ -11524,8 +11524,8 @@

Informational

- - + + @@ -11535,8 +11535,8 @@

Informational

- - + + @@ -11546,8 +11546,8 @@

Informational

- - + + @@ -11557,8 +11557,8 @@

Medium

- - + + @@ -11568,8 +11568,8 @@

Low

- - + + @@ -11579,8 +11579,8 @@

Informational

- - + + @@ -11590,8 +11590,8 @@

Informational

- - + + @@ -11601,8 +11601,8 @@

Medium

- - + + @@ -11612,8 +11612,8 @@

Informational

- - + + @@ -11623,8 +11623,8 @@

Medium

- - + + @@ -11634,8 +11634,8 @@

Informational

- - + + @@ -11645,8 +11645,8 @@

Informational

- - + + @@ -11656,8 +11656,8 @@

Informational

- - + + @@ -11667,8 +11667,8 @@

Informational

- - + + @@ -11678,8 +11678,8 @@

Informational

- - + + @@ -11689,8 +11689,8 @@

Informational

- - + + @@ -11700,8 +11700,8 @@

Informational

- - + + @@ -11711,8 +11711,8 @@

Informational

- - + + @@ -11722,8 +11722,8 @@

Informational

- - + + @@ -11733,8 +11733,8 @@

Informational

- - + + @@ -11744,8 +11744,8 @@

Informational

- - + + @@ -11755,8 +11755,8 @@

Informational

- - + + @@ -11766,8 +11766,8 @@

Informational

- - + + @@ -11777,8 +11777,8 @@

Informational

- - + + @@ -11788,8 +11788,8 @@

Informational

- - + + @@ -11799,8 +11799,8 @@

Informational

- - + + @@ -11810,8 +11810,8 @@

Informational

- - + + @@ -11821,8 +11821,8 @@

Informational

- - + + @@ -11832,8 +11832,8 @@

Informational

- - + + @@ -11843,8 +11843,8 @@

Informational

- - + + @@ -11854,8 +11854,8 @@

Medium

- - + + @@ -11865,8 +11865,8 @@

Low

- - + + @@ -11876,8 +11876,8 @@

Informational

- - + + @@ -11887,8 +11887,8 @@

Informational

- - + + @@ -11898,8 +11898,8 @@

Medium

- - + + @@ -11909,8 +11909,8 @@

Informational

- - + + @@ -11920,8 +11920,8 @@

Medium

- - + + @@ -11931,8 +11931,8 @@

Informational

- - + + @@ -11942,8 +11942,8 @@

Informational

- - + + @@ -11953,8 +11953,8 @@

Informational

- - + + @@ -11964,8 +11964,8 @@

Informational

- - + + @@ -11975,8 +11975,8 @@

Informational

- - + + @@ -11986,8 +11986,8 @@

Informational

- - + + @@ -11997,8 +11997,8 @@

Informational

- - + + @@ -12008,8 +12008,8 @@

Informational

- - + + @@ -12019,8 +12019,8 @@

Informational

- - + + @@ -12030,8 +12030,8 @@

Informational

- - + + @@ -12041,8 +12041,8 @@

Informational

- - + + @@ -12052,8 +12052,8 @@

Informational

- - + + @@ -12063,8 +12063,8 @@

Informational

- - + + @@ -12074,8 +12074,8 @@

Informational

- - + + @@ -12085,8 +12085,8 @@

Informational

- - + + @@ -12096,8 +12096,8 @@

Informational

- - + + @@ -12107,8 +12107,8 @@

Informational

- - + + @@ -12118,8 +12118,8 @@

Informational

- - + + @@ -12129,8 +12129,8 @@

Informational

- - + + @@ -12140,8 +12140,8 @@

Informational

- - + + @@ -12151,8 +12151,8 @@

Medium

- - + + @@ -12162,8 +12162,8 @@

Low

- - + + @@ -12173,8 +12173,8 @@

Informational

- - + + @@ -12184,8 +12184,8 @@

High

- - + + @@ -12195,8 +12195,8 @@

Informational

- - + + @@ -12206,8 +12206,8 @@

Medium

- - + + @@ -12217,8 +12217,8 @@

Informational

- - + + @@ -12228,8 +12228,8 @@

Medium

- - + + @@ -12239,8 +12239,8 @@

Informational

- - + + @@ -12250,8 +12250,8 @@

Informational

- - + + @@ -12261,8 +12261,8 @@

Informational

- - + + @@ -12272,8 +12272,8 @@

Informational

- - + + @@ -12283,8 +12283,8 @@

Informational

- - + + @@ -12294,8 +12294,8 @@

Informational

- - + + @@ -12305,8 +12305,8 @@

Informational

- - + + @@ -12316,8 +12316,8 @@

Informational

- - + + @@ -12327,8 +12327,8 @@

Informational

- - + + @@ -12338,8 +12338,8 @@

Informational

- - + + @@ -12349,8 +12349,8 @@

Informational

- - + + @@ -12360,8 +12360,8 @@

Informational

- - + + @@ -12371,8 +12371,8 @@

Informational

- - + + @@ -12382,8 +12382,8 @@

Informational

- - + + @@ -12393,8 +12393,8 @@

Informational

- - + + @@ -12404,8 +12404,8 @@

Informational

- - + + @@ -12415,8 +12415,8 @@

Informational

- - + + @@ -12426,8 +12426,8 @@

Informational

- - + + @@ -12437,8 +12437,8 @@

Informational

- - + + @@ -12448,8 +12448,8 @@

Informational

- - + + @@ -12459,8 +12459,8 @@

Medium

- - + + @@ -12470,8 +12470,8 @@

Low

- - + + @@ -12481,8 +12481,8 @@

Informational

- - + + @@ -12492,8 +12492,8 @@

Informational

- - + + @@ -12503,8 +12503,8 @@

Medium

- - + + @@ -12514,8 +12514,8 @@

Informational

- - + + @@ -12525,8 +12525,8 @@

Medium

- - + + @@ -12536,8 +12536,8 @@

Informational

- - + + @@ -12547,8 +12547,8 @@

Informational

- - + + @@ -12558,8 +12558,8 @@

Informational

- - + + @@ -12569,8 +12569,8 @@

Informational

- - + + @@ -12580,8 +12580,8 @@

Informational

- - + + @@ -12591,8 +12591,8 @@

Informational

- - + + @@ -12602,8 +12602,8 @@

Informational

- - + + @@ -12613,8 +12613,8 @@

Informational

- - + + @@ -12624,8 +12624,8 @@

Informational

- - + + @@ -12635,8 +12635,8 @@

Informational

- - + + @@ -12646,8 +12646,8 @@

Informational

- - + + @@ -12657,8 +12657,8 @@

Informational

- - + + @@ -12668,8 +12668,8 @@

Informational

- - + + @@ -12679,8 +12679,8 @@

Informational

- - + + @@ -12690,8 +12690,8 @@

Informational

- - + + @@ -12701,8 +12701,8 @@

Informational

- - + + @@ -12712,8 +12712,8 @@

Informational

- - + + @@ -12723,8 +12723,8 @@

Informational

- - + + @@ -12734,8 +12734,8 @@

Informational

- - + + @@ -12745,8 +12745,8 @@

Informational

- - + + @@ -12756,8 +12756,8 @@

Medium

- - + + @@ -12767,8 +12767,8 @@

Low

- - + + @@ -12778,8 +12778,8 @@

Informational

- - + + @@ -12789,8 +12789,8 @@

Informational

- - + + @@ -12800,8 +12800,8 @@

Medium

- - + + @@ -12811,8 +12811,8 @@

Informational

- - + + @@ -12822,8 +12822,8 @@

Medium

- - + + @@ -12833,8 +12833,8 @@

Informational

- - + + @@ -12844,8 +12844,8 @@

Informational

- - + + @@ -12855,8 +12855,8 @@

Informational

- - + + @@ -12866,8 +12866,8 @@

Informational

- - + + @@ -12877,8 +12877,8 @@

Informational

- - + + @@ -12888,8 +12888,8 @@

Informational

- - + + @@ -12899,8 +12899,8 @@

Informational

- - + + @@ -12910,8 +12910,8 @@

Informational

- - + + @@ -12921,8 +12921,8 @@

Informational

- - + + @@ -12932,8 +12932,8 @@

Informational

- - + + @@ -12943,8 +12943,8 @@

Informational

- - + + @@ -12954,8 +12954,8 @@

Informational

- - + + @@ -12965,8 +12965,8 @@

Informational

- - + + @@ -12976,8 +12976,8 @@

Informational

- - + + @@ -12987,8 +12987,8 @@

Informational

- - + + @@ -12998,8 +12998,8 @@

Informational

- - + + @@ -13009,8 +13009,8 @@

Informational

- - + + @@ -13020,8 +13020,8 @@

Informational

- - + + @@ -13031,8 +13031,8 @@

Informational

- - + + @@ -13042,8 +13042,8 @@

Informational

- - + + @@ -13053,8 +13053,8 @@

Medium

- - + + @@ -13064,8 +13064,8 @@

Low

- - + + @@ -13075,8 +13075,8 @@

Informational

- - + + @@ -13086,8 +13086,8 @@

High

- - + + @@ -13097,8 +13097,8 @@

Informational

- - + + @@ -13108,8 +13108,8 @@

Medium

- - + + @@ -13119,8 +13119,8 @@

Informational

- - + + @@ -13130,8 +13130,8 @@

Medium

- - + + @@ -13141,8 +13141,8 @@

Informational

- - + + @@ -13152,8 +13152,8 @@

Informational

- - + + @@ -13163,8 +13163,8 @@

Informational

- - + + @@ -13174,8 +13174,8 @@

Informational

- - + + @@ -13185,8 +13185,8 @@

Informational

- - + + @@ -13196,8 +13196,8 @@

Informational

- - + + @@ -13207,8 +13207,8 @@

Informational

- - + + @@ -13218,8 +13218,8 @@

Informational

- - + + @@ -13229,8 +13229,8 @@

Informational

- - + + @@ -13240,8 +13240,8 @@

Informational

- - + + @@ -13251,8 +13251,8 @@

Informational

- - + + @@ -13262,8 +13262,8 @@

Informational

- - + + @@ -13273,8 +13273,8 @@

Informational

- - + + @@ -13284,8 +13284,8 @@

Informational

- - + + @@ -13295,8 +13295,8 @@

Informational

- - + + @@ -13306,8 +13306,8 @@

Informational

- - + + @@ -13317,8 +13317,8 @@

Informational

- - + + @@ -13328,8 +13328,8 @@

Informational

- - + + @@ -13339,8 +13339,8 @@

Informational

- - + + @@ -13350,8 +13350,8 @@

Informational

- - + + @@ -13361,8 +13361,8 @@

Medium

- - + + @@ -13372,8 +13372,8 @@

Low

- - + + @@ -13383,8 +13383,8 @@

Informational

- - + + @@ -13394,8 +13394,8 @@

Informational

- - + + @@ -13405,8 +13405,8 @@

Medium

- - + + @@ -13416,8 +13416,8 @@

Informational

- - + + @@ -13427,8 +13427,8 @@

Medium

- - + + @@ -13438,8 +13438,8 @@

Informational

- - + + @@ -13449,8 +13449,8 @@

Informational

- - + + @@ -13460,8 +13460,8 @@

Informational

- - + + @@ -13471,8 +13471,8 @@

Informational

- - + + @@ -13482,8 +13482,8 @@

Informational

- - + + @@ -13493,8 +13493,8 @@

Informational

- - + + @@ -13504,8 +13504,8 @@

Informational

- - + + @@ -13515,8 +13515,8 @@

Informational

- - + + @@ -13526,8 +13526,8 @@

Informational

- - + + @@ -13537,8 +13537,8 @@

Informational

- - + + @@ -13548,8 +13548,8 @@

Informational

- - + + @@ -13559,8 +13559,8 @@

Informational

- - + + @@ -13570,8 +13570,8 @@

Informational

- - + + @@ -13581,8 +13581,8 @@

Informational

- - + + @@ -13592,8 +13592,8 @@

Informational

- - + + @@ -13603,8 +13603,8 @@

Informational

- - + + @@ -13614,8 +13614,8 @@

Informational

- - + + @@ -13625,8 +13625,8 @@

Informational

- - + + @@ -13636,8 +13636,8 @@

Informational

- - + + @@ -13647,8 +13647,8 @@

Informational

- - + + @@ -13658,8 +13658,8 @@

Medium

- - + + @@ -13669,8 +13669,8 @@

Low

- - + + @@ -13685,14 +13685,14 @@

Amazon Bedrock AgentCore Findings
-
+
-

Account IDRegionCheck IDFindingDetailsResolutionReferenceSeverityStatus
111111111111 ap-southeast-2 SM-01 SageMaker Internet Access CheckN/A
193047247167
111111111111 ap-southeast-2 SM-02 SageMaker SSO Configuration CheckPassed
193047247167
111111111111 ap-southeast-2 SM-03 Data Protection CheckN/A
193047247167
111111111111 ap-southeast-2 SM-04 GuardDuty EnabledPassed
193047247167
111111111111 ap-southeast-2 SM-05 SageMaker Model Registry IssueN/A
193047247167
111111111111 ap-southeast-2 SM-05 SageMaker Feature Store IssueN/A
193047247167
111111111111 ap-southeast-2 SM-05 SageMaker Pipelines IssueN/A
193047247167
111111111111 ap-southeast-2 SM-06 SageMaker Clarify No Clarify UsageN/A
193047247167
111111111111 ap-southeast-2 SM-07 SageMaker Model Monitor No Model MonitoringN/A
193047247167
111111111111 ap-southeast-2 SM-08 Model Registry Registry Not UsedN/A
193047247167
111111111111 ap-southeast-2 SM-09 SageMaker Notebook Root Access CheckN/A
193047247167
111111111111 ap-southeast-2 SM-10 SageMaker Notebook VPC Deployment CheckN/A
193047247167
111111111111 ap-southeast-2 SM-11 SageMaker Model Network Isolation CheckN/A
193047247167
111111111111 ap-southeast-2 SM-12 SageMaker Endpoint Instance Count CheckN/A
193047247167
111111111111 ap-southeast-2 SM-13 SageMaker Monitoring Network Isolation CheckN/A
193047247167
111111111111 ap-southeast-2 SM-14 SageMaker Model Repository Access CheckN/A
193047247167
111111111111 ap-southeast-2 SM-15 SageMaker Feature Store Encryption CheckN/A
193047247167
111111111111 ap-southeast-2 SM-16 SageMaker Data Quality Job Encryption CheckN/A
193047247167
111111111111 ap-southeast-2 SM-17 SageMaker Processing Job Encryption CheckN/A
193047247167
111111111111 ap-southeast-2 SM-18 SageMaker Transform Job Encryption CheckN/A
193047247167
111111111111 ap-southeast-2 SM-19 SageMaker Hyperparameter Tuning Job Encryption CheckN/A
193047247167
111111111111 ap-southeast-2 SM-20 SageMaker Compilation Job Encryption CheckN/A
193047247167
111111111111 ap-southeast-2 SM-21 SageMaker AutoML Job Network Isolation CheckN/A
193047247167
111111111111 ap-southeast-2 SM-22 Model Approval Workflow CheckN/A
193047247167
111111111111 ap-southeast-2 SM-23 Model Drift Detection CheckPassed
193047247167
111111111111 ap-southeast-2 SM-24 A/B Testing and Shadow Deployment CheckPassed
193047247167
111111111111 ap-southeast-2 SM-25 ML Lineage Tracking - Experiments Not UsedN/A
193047247167
111111111111 Global SM-02 SageMaker Full Access Policy UsedFailed
193047247167
111111111111 Global SM-02 SageMaker Full Access Policy UsedFailed
193047247167
111111111111 Global SM-02 SageMaker Full Access Policy UsedFailed
193047247167
111111111111 Global SM-02 SageMaker Full Access Policy UsedFailed
193047247167
111111111111 Global SM-02 SageMaker Full Access Policy UsedFailed
193047247167
111111111111 Global SM-02 SageMaker Full Access Policy UsedFailed
193047247167
111111111111 Global SM-02 SageMaker Full Access Policy UsedFailed
193047247167
111111111111 us-east-1 SM-01 Non-VPC Only Network AccessFailed
193047247167
111111111111 us-east-1 SM-02 SSO Not Properly ConfiguredFailed
193047247167
111111111111 us-east-1 SM-03 Missing Encryption ConfigurationFailed
193047247167
111111111111 us-east-1 SM-04 GuardDuty EnabledPassed
193047247167
111111111111 us-east-1 SM-05 SageMaker Model Registry IssueN/A
193047247167
111111111111 us-east-1 SM-05 SageMaker Feature Store IssueN/A
193047247167
111111111111 us-east-1 SM-05 SageMaker Pipelines IssueN/A
193047247167
111111111111 us-east-1 SM-06 SageMaker Clarify No Clarify UsageN/A
193047247167
111111111111 us-east-1 SM-07 SageMaker Model Monitor No Model MonitoringN/A
193047247167
111111111111 us-east-1 SM-08 Model Registry Registry Not UsedN/A
193047247167
111111111111 us-east-1 SM-09 SageMaker Notebook Root Access CheckN/A
193047247167
111111111111 us-east-1 SM-10 SageMaker Notebook VPC Deployment CheckN/A
193047247167
111111111111 us-east-1 SM-11 SageMaker Model Network Isolation CheckN/A
193047247167
111111111111 us-east-1 SM-12 SageMaker Endpoint Instance Count CheckN/A
193047247167
111111111111 us-east-1 SM-13 SageMaker Monitoring Network Isolation CheckN/A
193047247167
111111111111 us-east-1 SM-14 SageMaker Model Repository Access CheckN/A
193047247167
111111111111 us-east-1 SM-15 SageMaker Feature Store Encryption CheckN/A
193047247167
111111111111 us-east-1 SM-16 SageMaker Data Quality Job Encryption CheckN/A
193047247167
111111111111 us-east-1 SM-17 SageMaker Processing Job Encryption CheckN/A
193047247167
111111111111 us-east-1 SM-18 SageMaker Transform Job Encryption CheckN/A
193047247167
111111111111 us-east-1 SM-19 SageMaker Hyperparameter Tuning Job Encryption CheckN/A
193047247167
111111111111 us-east-1 SM-20 SageMaker Compilation Job Encryption CheckN/A
193047247167
111111111111 us-east-1 SM-21 SageMaker AutoML Job Network Isolation CheckN/A
193047247167
111111111111 us-east-1 SM-22 Model Approval Workflow CheckN/A
193047247167
111111111111 us-east-1 SM-23 Model Drift Detection CheckPassed
193047247167
111111111111 us-east-1 SM-24 A/B Testing and Shadow Deployment CheckPassed
193047247167
111111111111 us-east-1 SM-25 ML Lineage Tracking - Experiments Not UsedN/A
193047247167
111111111111 eu-west-1 SM-01 SageMaker Internet Access CheckN/A
193047247167
111111111111 eu-west-1 SM-02 SageMaker SSO Configuration CheckPassed
193047247167
111111111111 eu-west-1 SM-03 Data Protection CheckN/A
193047247167
111111111111 eu-west-1 SM-04 GuardDuty EnabledPassed
193047247167
111111111111 eu-west-1 SM-05 SageMaker Model Registry IssueN/A
193047247167
111111111111 eu-west-1 SM-05 SageMaker Feature Store IssueN/A
193047247167
111111111111 eu-west-1 SM-05 SageMaker Pipelines IssueN/A
193047247167
111111111111 eu-west-1 SM-06 SageMaker Clarify No Clarify UsageN/A
193047247167
111111111111 eu-west-1 SM-07 SageMaker Model Monitor No Model MonitoringN/A
193047247167
111111111111 eu-west-1 SM-08 Model Registry Registry Not UsedN/A
193047247167
111111111111 eu-west-1 SM-09 SageMaker Notebook Root Access CheckN/A
193047247167
111111111111 eu-west-1 SM-10 SageMaker Notebook VPC Deployment CheckN/A
193047247167
111111111111 eu-west-1 SM-11 SageMaker Model Network Isolation CheckN/A
193047247167
111111111111 eu-west-1 SM-12 SageMaker Endpoint Instance Count CheckN/A
193047247167
111111111111 eu-west-1 SM-13 SageMaker Monitoring Network Isolation CheckN/A
193047247167
111111111111 eu-west-1 SM-14 SageMaker Model Repository Access CheckN/A
193047247167
111111111111 eu-west-1 SM-15 SageMaker Feature Store Encryption CheckN/A
193047247167
111111111111 eu-west-1 SM-16 SageMaker Data Quality Job Encryption CheckN/A
193047247167
111111111111 eu-west-1 SM-17 SageMaker Processing Job Encryption CheckN/A
193047247167
111111111111 eu-west-1 SM-18 SageMaker Transform Job Encryption CheckN/A
193047247167
111111111111 eu-west-1 SM-19 SageMaker Hyperparameter Tuning Job Encryption CheckN/A
193047247167
111111111111 eu-west-1 SM-20 SageMaker Compilation Job Encryption CheckN/A
193047247167
111111111111 eu-west-1 SM-21 SageMaker AutoML Job Network Isolation CheckN/A
193047247167
111111111111 eu-west-1 SM-22 Model Approval Workflow CheckN/A
193047247167
111111111111 eu-west-1 SM-23 Model Drift Detection CheckPassed
193047247167
111111111111 eu-west-1 SM-24 A/B Testing and Shadow Deployment CheckPassed
193047247167
111111111111 eu-west-1 SM-25 ML Lineage Tracking - Experiments Not UsedN/A
472057511786
333333333333 eu-west-1 SM-01 SageMaker Internet Access CheckN/A
472057511786
333333333333 eu-west-1 SM-02 SageMaker SSO Configuration CheckPassed
472057511786
333333333333 eu-west-1 SM-03 Data Protection CheckN/A
472057511786
333333333333 eu-west-1 SM-04 GuardDuty EnabledPassed
472057511786
333333333333 eu-west-1 SM-05 SageMaker Model Registry IssueN/A
472057511786
333333333333 eu-west-1 SM-05 SageMaker Feature Store IssueN/A
472057511786
333333333333 eu-west-1 SM-05 SageMaker Pipelines IssueN/A
472057511786
333333333333 eu-west-1 SM-06 SageMaker Clarify No Clarify UsageN/A
472057511786
333333333333 eu-west-1 SM-07 SageMaker Model Monitor No Model MonitoringN/A
472057511786
333333333333 eu-west-1 SM-08 Model Registry Registry Not UsedN/A
472057511786
333333333333 eu-west-1 SM-09 SageMaker Notebook Root Access CheckN/A
472057511786
333333333333 eu-west-1 SM-10 SageMaker Notebook VPC Deployment CheckN/A
472057511786
333333333333 eu-west-1 SM-11 SageMaker Model Network Isolation CheckN/A
472057511786
333333333333 eu-west-1 SM-12 SageMaker Endpoint Instance Count CheckN/A
472057511786
333333333333 eu-west-1 SM-13 SageMaker Monitoring Network Isolation CheckN/A
472057511786
333333333333 eu-west-1 SM-14 SageMaker Model Repository Access CheckN/A
472057511786
333333333333 eu-west-1 SM-15 SageMaker Feature Store Encryption CheckN/A
472057511786
333333333333 eu-west-1 SM-16 SageMaker Data Quality Job Encryption CheckN/A
472057511786
333333333333 eu-west-1 SM-17 SageMaker Processing Job Encryption CheckN/A
472057511786
333333333333 eu-west-1 SM-18 SageMaker Transform Job Encryption CheckN/A
472057511786
333333333333 eu-west-1 SM-19 SageMaker Hyperparameter Tuning Job Encryption CheckN/A
472057511786
333333333333 eu-west-1 SM-20 SageMaker Compilation Job Encryption CheckN/A
472057511786
333333333333 eu-west-1 SM-21 SageMaker AutoML Job Network Isolation CheckN/A
472057511786
333333333333 eu-west-1 SM-22 Model Approval Workflow CheckN/A
472057511786
333333333333 eu-west-1 SM-23 Model Drift Detection CheckPassed
472057511786
333333333333 eu-west-1 SM-24 A/B Testing and Shadow Deployment CheckPassed
472057511786
333333333333 eu-west-1 SM-25 ML Lineage Tracking - Experiments Not UsedN/A
472057511786
333333333333 Global SM-02 SageMaker IAM Permissions CheckPassed
472057511786
333333333333 us-east-1 SM-01 SageMaker Internet Access CheckN/A
472057511786
333333333333 us-east-1 SM-02 SageMaker SSO Configuration CheckPassed
472057511786
333333333333 us-east-1 SM-03 Data Protection CheckN/A
472057511786
333333333333 us-east-1 SM-04 GuardDuty EnabledPassed
472057511786
333333333333 us-east-1 SM-05 SageMaker Model Registry IssueN/A
472057511786
333333333333 us-east-1 SM-05 SageMaker Feature Store IssueN/A
472057511786
333333333333 us-east-1 SM-05 SageMaker Pipelines IssueN/A
472057511786
333333333333 us-east-1 SM-06 SageMaker Clarify No Clarify UsageN/A
472057511786
333333333333 us-east-1 SM-07 SageMaker Model Monitor No Model MonitoringN/A
472057511786
333333333333 us-east-1 SM-08 Model Registry Registry Not UsedN/A
472057511786
333333333333 us-east-1 SM-09 SageMaker Notebook Root Access CheckN/A
472057511786
333333333333 us-east-1 SM-10 SageMaker Notebook VPC Deployment CheckN/A
472057511786
333333333333 us-east-1 SM-11 SageMaker Model Network Isolation CheckN/A
472057511786
333333333333 us-east-1 SM-12 SageMaker Endpoint Instance Count CheckN/A
472057511786
333333333333 us-east-1 SM-13 SageMaker Monitoring Network Isolation CheckN/A
472057511786
333333333333 us-east-1 SM-14 SageMaker Model Repository Access CheckN/A
472057511786
333333333333 us-east-1 SM-15 SageMaker Feature Store Encryption CheckN/A
472057511786
333333333333 us-east-1 SM-16 SageMaker Data Quality Job Encryption CheckN/A
472057511786
333333333333 us-east-1 SM-17 SageMaker Processing Job Encryption CheckN/A
472057511786
333333333333 us-east-1 SM-18 SageMaker Transform Job Encryption CheckN/A
472057511786
333333333333 us-east-1 SM-19 SageMaker Hyperparameter Tuning Job Encryption CheckN/A
472057511786
333333333333 us-east-1 SM-20 SageMaker Compilation Job Encryption CheckN/A
472057511786
333333333333 us-east-1 SM-21 SageMaker AutoML Job Network Isolation CheckN/A
472057511786
333333333333 us-east-1 SM-22 Model Approval Workflow CheckN/A
472057511786
333333333333 us-east-1 SM-23 Model Drift Detection CheckPassed
472057511786
333333333333 us-east-1 SM-24 A/B Testing and Shadow Deployment CheckPassed
472057511786
333333333333 us-east-1 SM-25 ML Lineage Tracking - Experiments Not UsedN/A
472057511786
333333333333 ap-southeast-2 SM-01 SageMaker Internet Access CheckN/A
472057511786
333333333333 ap-southeast-2 SM-02 SageMaker SSO Configuration CheckPassed
472057511786
333333333333 ap-southeast-2 SM-03 Data Protection CheckN/A
472057511786
333333333333 ap-southeast-2 SM-04 GuardDuty EnabledPassed
472057511786
333333333333 ap-southeast-2 SM-05 SageMaker Model Registry IssueN/A
472057511786
333333333333 ap-southeast-2 SM-05 SageMaker Feature Store IssueN/A
472057511786
333333333333 ap-southeast-2 SM-05 SageMaker Pipelines IssueN/A
472057511786
333333333333 ap-southeast-2 SM-06 SageMaker Clarify No Clarify UsageN/A
472057511786
333333333333 ap-southeast-2 SM-07 SageMaker Model Monitor No Model MonitoringN/A
472057511786
333333333333 ap-southeast-2 SM-08 Model Registry Registry Not UsedN/A
472057511786
333333333333 ap-southeast-2 SM-09 SageMaker Notebook Root Access CheckN/A
472057511786
333333333333 ap-southeast-2 SM-10 SageMaker Notebook VPC Deployment CheckN/A
472057511786
333333333333 ap-southeast-2 SM-11 SageMaker Model Network Isolation CheckN/A
472057511786
333333333333 ap-southeast-2 SM-12 SageMaker Endpoint Instance Count CheckN/A
472057511786
333333333333 ap-southeast-2 SM-13 SageMaker Monitoring Network Isolation CheckN/A
472057511786
333333333333 ap-southeast-2 SM-14 SageMaker Model Repository Access CheckN/A
472057511786
333333333333 ap-southeast-2 SM-15 SageMaker Feature Store Encryption CheckN/A
472057511786
333333333333 ap-southeast-2 SM-16 SageMaker Data Quality Job Encryption CheckN/A
472057511786
333333333333 ap-southeast-2 SM-17 SageMaker Processing Job Encryption CheckN/A
472057511786
333333333333 ap-southeast-2 SM-18 SageMaker Transform Job Encryption CheckN/A
472057511786
333333333333 ap-southeast-2 SM-19 SageMaker Hyperparameter Tuning Job Encryption CheckN/A
472057511786
333333333333 ap-southeast-2 SM-20 SageMaker Compilation Job Encryption CheckN/A
472057511786
333333333333 ap-southeast-2 SM-21 SageMaker AutoML Job Network Isolation CheckN/A
472057511786
333333333333 ap-southeast-2 SM-22 Model Approval Workflow CheckN/A
472057511786
333333333333 ap-southeast-2 SM-23 Model Drift Detection CheckPassed
472057511786
333333333333 ap-southeast-2 SM-24 A/B Testing and Shadow Deployment CheckPassed
472057511786
333333333333 ap-southeast-2 SM-25 ML Lineage Tracking - Experiments Not UsedN/A
230266523520
222222222222 eu-west-1 SM-01 SageMaker Internet Access CheckN/A
230266523520
222222222222 eu-west-1 SM-02 SageMaker SSO Configuration CheckPassed
230266523520
222222222222 eu-west-1 SM-03 Data Protection CheckN/A
230266523520
222222222222 eu-west-1 SM-04 GuardDuty EnabledPassed
230266523520
222222222222 eu-west-1 SM-05 SageMaker Model Registry IssueN/A
230266523520
222222222222 eu-west-1 SM-05 SageMaker Feature Store IssueN/A
230266523520
222222222222 eu-west-1 SM-05 SageMaker Pipelines IssueN/A
230266523520
222222222222 eu-west-1 SM-06 SageMaker Clarify No Clarify UsageN/A
230266523520
222222222222 eu-west-1 SM-07 SageMaker Model Monitor No Model MonitoringN/A
230266523520
222222222222 eu-west-1 SM-08 Model Registry Registry Not UsedN/A
230266523520
222222222222 eu-west-1 SM-09 SageMaker Notebook Root Access CheckN/A
230266523520
222222222222 eu-west-1 SM-10 SageMaker Notebook VPC Deployment CheckN/A
230266523520
222222222222 eu-west-1 SM-11 SageMaker Model Network Isolation CheckN/A
230266523520
222222222222 eu-west-1 SM-12 SageMaker Endpoint Instance Count CheckN/A
230266523520
222222222222 eu-west-1 SM-13 SageMaker Monitoring Network Isolation CheckN/A
230266523520
222222222222 eu-west-1 SM-14 SageMaker Model Repository Access CheckN/A
230266523520
222222222222 eu-west-1 SM-15 SageMaker Feature Store Encryption CheckN/A
230266523520
222222222222 eu-west-1 SM-16 SageMaker Data Quality Job Encryption CheckN/A
230266523520
222222222222 eu-west-1 SM-17 SageMaker Processing Job Encryption CheckN/A
230266523520
222222222222 eu-west-1 SM-18 SageMaker Transform Job Encryption CheckN/A
230266523520
222222222222 eu-west-1 SM-19 SageMaker Hyperparameter Tuning Job Encryption CheckN/A
230266523520
222222222222 eu-west-1 SM-20 SageMaker Compilation Job Encryption CheckN/A
230266523520
222222222222 eu-west-1 SM-21 SageMaker AutoML Job Network Isolation CheckN/A
230266523520
222222222222 eu-west-1 SM-22 Model Approval Workflow CheckN/A
230266523520
222222222222 eu-west-1 SM-23 Model Drift Detection CheckPassed
230266523520
222222222222 eu-west-1 SM-24 A/B Testing and Shadow Deployment CheckPassed
230266523520
222222222222 eu-west-1 SM-25 ML Lineage Tracking - Experiments Not UsedN/A
230266523520
222222222222 Global SM-02 SageMaker IAM Permissions CheckPassed
230266523520
222222222222 us-east-1 SM-01 SageMaker Internet Access CheckN/A
230266523520
222222222222 us-east-1 SM-02 SageMaker SSO Configuration CheckPassed
230266523520
222222222222 us-east-1 SM-03 Data Protection CheckN/A
230266523520
222222222222 us-east-1 SM-04 GuardDuty EnabledPassed
230266523520
222222222222 us-east-1 SM-05 SageMaker Model Registry IssueN/A
230266523520
222222222222 us-east-1 SM-05 SageMaker Feature Store IssueN/A
230266523520
222222222222 us-east-1 SM-05 SageMaker Pipelines IssueN/A
230266523520
222222222222 us-east-1 SM-06 SageMaker Clarify No Clarify UsageN/A
230266523520
222222222222 us-east-1 SM-07 SageMaker Model Monitor No Model MonitoringN/A
230266523520
222222222222 us-east-1 SM-08 Model Registry Registry Not UsedN/A
230266523520
222222222222 us-east-1 SM-09 SageMaker Notebook Root Access CheckN/A
230266523520
222222222222 us-east-1 SM-10 SageMaker Notebook VPC Deployment CheckN/A
230266523520
222222222222 us-east-1 SM-11 SageMaker Model Network Isolation CheckN/A
230266523520
222222222222 us-east-1 SM-12 SageMaker Endpoint Instance Count CheckN/A
230266523520
222222222222 us-east-1 SM-13 SageMaker Monitoring Network Isolation CheckN/A
230266523520
222222222222 us-east-1 SM-14 SageMaker Model Repository Access CheckN/A
230266523520
222222222222 us-east-1 SM-15 SageMaker Feature Store Encryption CheckN/A
230266523520
222222222222 us-east-1 SM-16 SageMaker Data Quality Job Encryption CheckN/A
230266523520
222222222222 us-east-1 SM-17 SageMaker Processing Job Encryption CheckN/A
230266523520
222222222222 us-east-1 SM-18 SageMaker Transform Job Encryption CheckN/A
230266523520
222222222222 us-east-1 SM-19 SageMaker Hyperparameter Tuning Job Encryption CheckN/A
230266523520
222222222222 us-east-1 SM-20 SageMaker Compilation Job Encryption CheckN/A
230266523520
222222222222 us-east-1 SM-21 SageMaker AutoML Job Network Isolation CheckN/A
230266523520
222222222222 us-east-1 SM-22 Model Approval Workflow CheckN/A
230266523520
222222222222 us-east-1 SM-23 Model Drift Detection CheckPassed
230266523520
222222222222 us-east-1 SM-24 A/B Testing and Shadow Deployment CheckPassed
230266523520
222222222222 us-east-1 SM-25 ML Lineage Tracking - Experiments Not UsedN/A
230266523520
222222222222 ap-southeast-2 SM-01 SageMaker Internet Access CheckN/A
230266523520
222222222222 ap-southeast-2 SM-02 SageMaker SSO Configuration CheckPassed
230266523520
222222222222 ap-southeast-2 SM-03 Data Protection CheckN/A
230266523520
222222222222 ap-southeast-2 SM-04 GuardDuty EnabledPassed
230266523520
222222222222 ap-southeast-2 SM-05 SageMaker Model Registry IssueN/A
230266523520
222222222222 ap-southeast-2 SM-05 SageMaker Feature Store IssueN/A
230266523520
222222222222 ap-southeast-2 SM-05 SageMaker Pipelines IssueN/A
230266523520
222222222222 ap-southeast-2 SM-06 SageMaker Clarify No Clarify UsageN/A
230266523520
222222222222 ap-southeast-2 SM-07 SageMaker Model Monitor No Model MonitoringN/A
230266523520
222222222222 ap-southeast-2 SM-08 Model Registry Registry Not UsedN/A
230266523520
222222222222 ap-southeast-2 SM-09 SageMaker Notebook Root Access CheckN/A
230266523520
222222222222 ap-southeast-2 SM-10 SageMaker Notebook VPC Deployment CheckN/A
230266523520
222222222222 ap-southeast-2 SM-11 SageMaker Model Network Isolation CheckN/A
230266523520
222222222222 ap-southeast-2 SM-12 SageMaker Endpoint Instance Count CheckN/A
230266523520
222222222222 ap-southeast-2 SM-13 SageMaker Monitoring Network Isolation CheckN/A
230266523520
222222222222 ap-southeast-2 SM-14 SageMaker Model Repository Access CheckN/A
230266523520
222222222222 ap-southeast-2 SM-15 SageMaker Feature Store Encryption CheckN/A
230266523520
222222222222 ap-southeast-2 SM-16 SageMaker Data Quality Job Encryption CheckN/A
230266523520
222222222222 ap-southeast-2 SM-17 SageMaker Processing Job Encryption CheckN/A
230266523520
222222222222 ap-southeast-2 SM-18 SageMaker Transform Job Encryption CheckN/A
230266523520
222222222222 ap-southeast-2 SM-19 SageMaker Hyperparameter Tuning Job Encryption CheckN/A
230266523520
222222222222 ap-southeast-2 SM-20 SageMaker Compilation Job Encryption CheckN/A
230266523520
222222222222 ap-southeast-2 SM-21 SageMaker AutoML Job Network Isolation CheckN/A
230266523520
222222222222 ap-southeast-2 SM-22 Model Approval Workflow CheckN/A
230266523520
222222222222 ap-southeast-2 SM-23 Model Drift Detection CheckPassed
230266523520
222222222222 ap-southeast-2 SM-24 A/B Testing and Shadow Deployment CheckPassed
230266523520
222222222222 ap-southeast-2 SM-25 ML Lineage Tracking - Experiments Not Used
- +
Account IDRegionCheck IDFindingDetailsResolutionReferenceSeverityStatus
193047247167
+ @@ -13702,8 +13702,8 @@

Informational

- - + + @@ -13713,8 +13713,8 @@

Informational

- - + + @@ -13724,8 +13724,8 @@

Informational

- - + + @@ -13735,8 +13735,8 @@

Informational

- - + + @@ -13746,8 +13746,8 @@

Informational

- - + + @@ -13757,8 +13757,8 @@

Informational

- - + + @@ -13768,8 +13768,8 @@

Informational

- - + + @@ -13779,8 +13779,8 @@

Informational

- - + + @@ -13790,8 +13790,8 @@

Informational

- - + + @@ -13801,8 +13801,8 @@

Informational

- - + + @@ -13812,8 +13812,8 @@

High

- - + + @@ -13823,8 +13823,8 @@

High

- - + + @@ -13834,8 +13834,8 @@

Medium

- - + + @@ -13845,8 +13845,8 @@

Medium

- - + + @@ -13856,8 +13856,8 @@

Medium

- - + + @@ -13867,8 +13867,8 @@

High

- - + + @@ -13878,8 +13878,8 @@

High

- - + + @@ -13889,8 +13889,8 @@

High

- - + + @@ -13900,8 +13900,8 @@

High

- - + + @@ -13911,8 +13911,8 @@

High

- - + + @@ -13922,8 +13922,8 @@

Medium

- - + + @@ -13933,8 +13933,8 @@

Medium

- - + + @@ -13944,8 +13944,8 @@

Medium

- - + + @@ -13955,8 +13955,8 @@

Medium

- - + + @@ -13966,8 +13966,8 @@

Medium

- - + + @@ -13977,8 +13977,8 @@

Medium

- - + + @@ -13988,8 +13988,8 @@

Medium

- - + + @@ -13999,8 +13999,8 @@

Medium

- - + + @@ -14010,8 +14010,8 @@

Medium

- - + + @@ -14021,8 +14021,8 @@

Medium

- - + + @@ -14032,8 +14032,8 @@

Low

- - + + @@ -14043,8 +14043,8 @@

Low

- - + + @@ -14054,8 +14054,8 @@

Medium

- - + + @@ -14065,8 +14065,8 @@

Medium

- - + + @@ -14076,8 +14076,8 @@

Medium

- - + + @@ -14087,8 +14087,8 @@

Medium

- - + + @@ -14098,8 +14098,8 @@

Medium

- - + + @@ -14109,8 +14109,8 @@

Medium

- - + + @@ -14120,8 +14120,8 @@

Medium

- - + + @@ -14131,8 +14131,8 @@

Medium

- - + + @@ -14142,8 +14142,8 @@

Informational

- - + + @@ -14157,8 +14157,8 @@

High

- - + + @@ -14168,8 +14168,8 @@

Informational

- - + + @@ -14179,8 +14179,8 @@

Informational

- - + + @@ -14190,8 +14190,8 @@

Informational

- - + + @@ -14201,8 +14201,8 @@

Informational

- - + + @@ -14212,8 +14212,8 @@

Informational

- - + + @@ -14223,8 +14223,8 @@

Informational

- - + + @@ -14234,8 +14234,8 @@

Informational

- - + + @@ -14245,8 +14245,8 @@

Informational

- - + + @@ -14256,8 +14256,8 @@

Informational

- - + + @@ -14267,8 +14267,8 @@

Informational

- - + + @@ -14278,8 +14278,8 @@

Informational

- - + + @@ -14289,8 +14289,8 @@

Informational

- - + + @@ -14300,8 +14300,8 @@

Informational

- - + + @@ -14311,8 +14311,8 @@

Informational

- - + + @@ -14322,8 +14322,8 @@

Informational

- - + + @@ -14333,8 +14333,8 @@

Informational

- - + + @@ -14344,8 +14344,8 @@

Informational

- - + + @@ -14355,8 +14355,8 @@

Informational

- - + + @@ -14366,8 +14366,8 @@

Informational

- - + + @@ -14377,8 +14377,8 @@

Informational

- - + + @@ -14388,8 +14388,8 @@

Informational

- - + + @@ -14399,8 +14399,8 @@

Informational

- - + + @@ -14410,8 +14410,8 @@

Informational

- - + + @@ -14421,8 +14421,8 @@

Informational

- - + + @@ -14432,8 +14432,8 @@

Informational

- - + + @@ -14443,8 +14443,8 @@

Informational

- - + + @@ -14454,8 +14454,8 @@

Informational

- - + + @@ -14465,8 +14465,8 @@

Informational

- - + + @@ -14476,8 +14476,8 @@

Informational

- - + + @@ -14487,8 +14487,8 @@

Informational

- - + + @@ -14498,8 +14498,8 @@

Informational

- - + + @@ -14509,8 +14509,8 @@

Informational

- - + + @@ -14520,8 +14520,8 @@

Informational

- - + + @@ -14531,8 +14531,8 @@

High

- - + + @@ -14542,8 +14542,8 @@

Medium

- - + + @@ -14553,8 +14553,8 @@

Medium

- - + + @@ -14564,8 +14564,8 @@

Medium

- - + + @@ -14575,8 +14575,8 @@

Informational

- - + + @@ -14586,8 +14586,8 @@

Informational

- - + + @@ -14597,8 +14597,8 @@

Informational

- - + + @@ -14608,8 +14608,8 @@

Informational

- - + + @@ -14619,8 +14619,8 @@

Informational

- - + + @@ -14630,8 +14630,8 @@

Informational

- - + + @@ -14641,8 +14641,8 @@

Informational

- - + + @@ -14652,8 +14652,8 @@

Informational

- - + + @@ -14663,8 +14663,8 @@

Informational

- - + + @@ -14674,8 +14674,8 @@

Informational

- - + + @@ -14685,8 +14685,8 @@

Informational

- - + + @@ -14696,8 +14696,8 @@

Informational

- - + + @@ -14707,8 +14707,8 @@

Informational

- - + + @@ -14718,8 +14718,8 @@

Informational

- - + + @@ -14729,8 +14729,8 @@

Informational

- - + + @@ -14740,8 +14740,8 @@

Informational

- - + + @@ -14751,8 +14751,8 @@

Informational

- - + + @@ -14762,8 +14762,8 @@

Informational

- - + + @@ -14773,8 +14773,8 @@

Informational

- - + + @@ -14784,8 +14784,8 @@

Informational

- - + + @@ -14795,8 +14795,8 @@

High

- - + + @@ -14806,8 +14806,8 @@

Medium

- - + + @@ -14817,8 +14817,8 @@

Medium

- - + + @@ -14828,8 +14828,8 @@

Medium

- - + + @@ -14839,8 +14839,8 @@

Informational

- - + + @@ -14850,8 +14850,8 @@

Informational

- - + + @@ -14861,8 +14861,8 @@

Informational

- - + + @@ -14872,8 +14872,8 @@

Informational

- - + + @@ -14883,8 +14883,8 @@

Informational

- - + + @@ -14894,8 +14894,8 @@

Informational

- - + + @@ -14905,8 +14905,8 @@

Informational

- - + + @@ -14916,8 +14916,8 @@

Informational

- - + + @@ -14927,8 +14927,8 @@

Informational

- - + + @@ -14938,8 +14938,8 @@

Informational

- - + + @@ -14949,8 +14949,8 @@

Informational

- - + + @@ -14960,8 +14960,8 @@

Informational

- - + + @@ -14971,8 +14971,8 @@

Informational

- - + + @@ -14982,8 +14982,8 @@

Informational

- - + + @@ -14993,8 +14993,8 @@

Informational

- - + + @@ -15004,8 +15004,8 @@

Informational

- - + + @@ -15015,8 +15015,8 @@

Informational

- - + + @@ -15026,8 +15026,8 @@

Informational

- - + + @@ -15037,8 +15037,8 @@

Informational

- - + + @@ -15049,8 +15049,8 @@

N/A

Account IDRegionCheck IDFindingDetailsResolutionReferenceSeverityStatus
111111111111 ap-southeast-2 AC-01 AgentCore VPC Configuration CheckN/A
193047247167
111111111111 ap-southeast-2 AC-04 AgentCore Observability CheckN/A
193047247167
111111111111 ap-southeast-2 AC-05 AgentCore Encryption CheckN/A
193047247167
111111111111 ap-southeast-2 AC-06 AgentCore Browser Tool Recording CheckN/A
193047247167
111111111111 ap-southeast-2 AC-07 AgentCore Memory Configuration CheckN/A
193047247167
111111111111 ap-southeast-2 AC-13 AgentCore Gateway Configuration CheckN/A
193047247167
111111111111 ap-southeast-2 AC-08 AgentCore VPC Endpoints CheckN/A
193047247167
111111111111 ap-southeast-2 AC-10 AgentCore Resource-Based Policies CheckN/A
193047247167
111111111111 ap-southeast-2 AC-11 AgentCore Policy Engine Encryption CheckN/A
193047247167
111111111111 ap-southeast-2 AC-12 AgentCore Gateway Encryption CheckN/A
193047247167
111111111111 Global AC-02 AgentCore IAM Full Access PolicyFailed
193047247167
111111111111 Global AC-02 AgentCore IAM Wildcard PermissionsFailed
193047247167
111111111111 Global AC-03 AgentCore Stale AccessFailed
193047247167
111111111111 Global AC-03 AgentCore Unused PermissionsFailed
193047247167
111111111111 Global AC-09 AgentCore Service-Linked Role MissingFailed
193047247167
111111111111 us-east-1 AC-01 AgentCore Runtime VPC ConfigurationFailed
193047247167
111111111111 us-east-1 AC-01 AgentCore Runtime VPC ConfigurationFailed
193047247167
111111111111 us-east-1 AC-01 AgentCore Runtime VPC ConfigurationFailed
193047247167
111111111111 us-east-1 AC-01 AgentCore Runtime VPC ConfigurationFailed
193047247167
111111111111 us-east-1 AC-01 AgentCore Runtime VPC ConfigurationFailed
193047247167
111111111111 us-east-1 AC-04 AgentCore Runtime CloudWatch LogsFailed
193047247167
111111111111 us-east-1 AC-04 AgentCore Runtime X-Ray TracingFailed
193047247167
111111111111 us-east-1 AC-04 AgentCore Runtime CloudWatch LogsFailed
193047247167
111111111111 us-east-1 AC-04 AgentCore Runtime X-Ray TracingFailed
193047247167
111111111111 us-east-1 AC-04 AgentCore Runtime CloudWatch LogsFailed
193047247167
111111111111 us-east-1 AC-04 AgentCore Runtime X-Ray TracingFailed
193047247167
111111111111 us-east-1 AC-04 AgentCore Runtime CloudWatch LogsFailed
193047247167
111111111111 us-east-1 AC-04 AgentCore Runtime X-Ray TracingFailed
193047247167
111111111111 us-east-1 AC-04 AgentCore Runtime CloudWatch LogsFailed
193047247167
111111111111 us-east-1 AC-04 AgentCore Runtime X-Ray TracingFailed
193047247167
111111111111 us-east-1 AC-05 AgentCore ECR Repository AWS-Managed KeysFailed
193047247167
111111111111 us-east-1 AC-05 AgentCore ECR Repository AWS-Managed KeysFailed
193047247167
111111111111 us-east-1 AC-06 AgentCore Runtime Storage ConfigurationFailed
193047247167
111111111111 us-east-1 AC-06 AgentCore Runtime Storage ConfigurationFailed
193047247167
111111111111 us-east-1 AC-06 AgentCore Runtime Storage ConfigurationFailed
193047247167
111111111111 us-east-1 AC-06 AgentCore Runtime Storage ConfigurationFailed
193047247167
111111111111 us-east-1 AC-06 AgentCore Runtime Storage ConfigurationFailed
193047247167
111111111111 us-east-1 AC-07 AgentCore Memory EncryptionFailed
193047247167
111111111111 us-east-1 AC-07 AgentCore Memory EncryptionFailed
193047247167
111111111111 us-east-1 AC-07 AgentCore Memory EncryptionFailed
193047247167
111111111111 us-east-1 AC-13 AgentCore Gateway Configuration CheckN/A
193047247167
111111111111 us-east-1 AC-08 AgentCore VPC Endpoints MissingFailed
193047247167
111111111111 us-east-1 AC-10 AgentCore Resource-Based Policies CheckN/A
193047247167
111111111111 us-east-1 AC-11 AgentCore Policy Engine Encryption CheckN/A
193047247167
111111111111 us-east-1 AC-12 AgentCore Gateway Encryption CheckN/A
193047247167
111111111111 eu-west-1 AC-01 AgentCore VPC Configuration CheckN/A
193047247167
111111111111 eu-west-1 AC-04 AgentCore Observability CheckN/A
193047247167
111111111111 eu-west-1 AC-05 AgentCore Encryption CheckN/A
193047247167
111111111111 eu-west-1 AC-06 AgentCore Browser Tool Recording CheckN/A
193047247167
111111111111 eu-west-1 AC-07 AgentCore Memory Configuration CheckN/A
193047247167
111111111111 eu-west-1 AC-13 AgentCore Gateway Configuration CheckN/A
193047247167
111111111111 eu-west-1 AC-08 AgentCore VPC Endpoints CheckN/A
193047247167
111111111111 eu-west-1 AC-10 AgentCore Resource-Based Policies CheckN/A
193047247167
111111111111 eu-west-1 AC-11 AgentCore Policy Engine Encryption CheckN/A
193047247167
111111111111 eu-west-1 AC-12 AgentCore Gateway Encryption CheckN/A
472057511786
333333333333 ap-southeast-2 AC-01 AgentCore VPC Configuration CheckN/A
472057511786
333333333333 ap-southeast-2 AC-04 AgentCore Observability CheckN/A
472057511786
333333333333 ap-southeast-2 AC-05 AgentCore Encryption CheckN/A
472057511786
333333333333 ap-southeast-2 AC-06 AgentCore Browser Tool Recording CheckN/A
472057511786
333333333333 ap-southeast-2 AC-07 AgentCore Memory Configuration CheckN/A
472057511786
333333333333 ap-southeast-2 AC-13 AgentCore Gateway Configuration CheckN/A
472057511786
333333333333 ap-southeast-2 AC-08 AgentCore VPC Endpoints CheckN/A
472057511786
333333333333 ap-southeast-2 AC-10 AgentCore Resource-Based Policies CheckN/A
472057511786
333333333333 ap-southeast-2 AC-11 AgentCore Policy Engine Encryption CheckN/A
472057511786
333333333333 ap-southeast-2 AC-12 AgentCore Gateway Encryption CheckN/A
472057511786
333333333333 eu-west-1 AC-01 AgentCore VPC Configuration CheckN/A
472057511786
333333333333 eu-west-1 AC-04 AgentCore Observability CheckN/A
472057511786
333333333333 eu-west-1 AC-05 AgentCore Encryption CheckN/A
472057511786
333333333333 eu-west-1 AC-06 AgentCore Browser Tool Recording CheckN/A
472057511786
333333333333 eu-west-1 AC-07 AgentCore Memory Configuration CheckN/A
472057511786
333333333333 eu-west-1 AC-13 AgentCore Gateway Configuration CheckN/A
472057511786
333333333333 eu-west-1 AC-08 AgentCore VPC Endpoints CheckN/A
472057511786
333333333333 eu-west-1 AC-10 AgentCore Resource-Based Policies CheckN/A
472057511786
333333333333 eu-west-1 AC-11 AgentCore Policy Engine Encryption CheckN/A
472057511786
333333333333 eu-west-1 AC-12 AgentCore Gateway Encryption CheckN/A
472057511786
333333333333 Global AC-02 AgentCore IAM Full Access CheckPassed
472057511786
333333333333 Global AC-03 AgentCore Stale AccessFailed
472057511786
333333333333 Global AC-03 AgentCore Unused PermissionsFailed
472057511786
333333333333 Global AC-09 AgentCore Service-Linked Role MissingFailed
472057511786
333333333333 us-east-1 AC-01 AgentCore VPC Configuration CheckN/A
472057511786
333333333333 us-east-1 AC-04 AgentCore Observability CheckN/A
472057511786
333333333333 us-east-1 AC-05 AgentCore Encryption CheckN/A
472057511786
333333333333 us-east-1 AC-06 AgentCore Browser Tool Recording CheckN/A
472057511786
333333333333 us-east-1 AC-07 AgentCore Memory Configuration CheckN/A
472057511786
333333333333 us-east-1 AC-13 AgentCore Gateway Configuration CheckN/A
472057511786
333333333333 us-east-1 AC-08 AgentCore VPC Endpoints CheckN/A
472057511786
333333333333 us-east-1 AC-10 AgentCore Resource-Based Policies CheckN/A
472057511786
333333333333 us-east-1 AC-11 AgentCore Policy Engine Encryption CheckN/A
472057511786
333333333333 us-east-1 AC-12 AgentCore Gateway Encryption CheckN/A
230266523520
222222222222 eu-west-1 AC-01 AgentCore VPC Configuration CheckN/A
230266523520
222222222222 eu-west-1 AC-04 AgentCore Observability CheckN/A
230266523520
222222222222 eu-west-1 AC-05 AgentCore Encryption CheckN/A
230266523520
222222222222 eu-west-1 AC-06 AgentCore Browser Tool Recording CheckN/A
230266523520
222222222222 eu-west-1 AC-07 AgentCore Memory Configuration CheckN/A
230266523520
222222222222 eu-west-1 AC-13 AgentCore Gateway Configuration CheckN/A
230266523520
222222222222 eu-west-1 AC-08 AgentCore VPC Endpoints CheckN/A
230266523520
222222222222 eu-west-1 AC-10 AgentCore Resource-Based Policies CheckN/A
230266523520
222222222222 eu-west-1 AC-11 AgentCore Policy Engine Encryption CheckN/A
230266523520
222222222222 eu-west-1 AC-12 AgentCore Gateway Encryption CheckN/A
230266523520
222222222222 Global AC-02 AgentCore IAM Full Access CheckPassed
230266523520
222222222222 Global AC-03 AgentCore Stale AccessFailed
230266523520
222222222222 Global AC-03 AgentCore Unused PermissionsFailed
230266523520
222222222222 Global AC-09 AgentCore Service-Linked Role MissingFailed
230266523520
222222222222 us-east-1 AC-01 AgentCore VPC Configuration CheckN/A
230266523520
222222222222 us-east-1 AC-04 AgentCore Observability CheckN/A
230266523520
222222222222 us-east-1 AC-05 AgentCore Encryption CheckN/A
230266523520
222222222222 us-east-1 AC-06 AgentCore Browser Tool Recording CheckN/A
230266523520
222222222222 us-east-1 AC-07 AgentCore Memory Configuration CheckN/A
230266523520
222222222222 us-east-1 AC-13 AgentCore Gateway Configuration CheckN/A
230266523520
222222222222 us-east-1 AC-08 AgentCore VPC Endpoints CheckN/A
230266523520
222222222222 us-east-1 AC-10 AgentCore Resource-Based Policies CheckN/A
230266523520
222222222222 us-east-1 AC-11 AgentCore Policy Engine Encryption CheckN/A
230266523520
222222222222 us-east-1 AC-12 AgentCore Gateway Encryption CheckN/A
230266523520
222222222222 ap-southeast-2 AC-01 AgentCore VPC Configuration CheckN/A
230266523520
222222222222 ap-southeast-2 AC-04 AgentCore Observability CheckN/A
230266523520
222222222222 ap-southeast-2 AC-05 AgentCore Encryption CheckN/A
230266523520
222222222222 ap-southeast-2 AC-06 AgentCore Browser Tool Recording CheckN/A
230266523520
222222222222 ap-southeast-2 AC-07 AgentCore Memory Configuration CheckN/A
230266523520
222222222222 ap-southeast-2 AC-13 AgentCore Gateway Configuration CheckN/A
230266523520
222222222222 ap-southeast-2 AC-08 AgentCore VPC Endpoints CheckN/A
230266523520
222222222222 ap-southeast-2 AC-10 AgentCore Resource-Based Policies CheckN/A
230266523520
222222222222 ap-southeast-2 AC-11 AgentCore Policy Engine Encryption CheckN/A
230266523520
222222222222 ap-southeast-2 AC-12 AgentCore Gateway Encryption Check
-
Financial Services GenAI Risk Findings
- +
Financial Services GenAI Risk Findings
Scope: this assessment records findings against each resolved CloudFormation TargetRegions entry. These checks are based on the AWS User Guide to Governance, Risk, and Compliance for Responsible AI Adoption. Severities follow a documented Likelihood × Impact methodology (see docs).
Account IDRegionCheck IDFindingDetailsResolutionReferenceSeverityStatus
193047247167
+ @@ -15063,8 +15063,8 @@

Low

- - + + @@ -15076,8 +15076,8 @@

Medium

- - + + @@ -15087,8 +15087,8 @@

Medium

- - + + @@ -15101,8 +15101,8 @@

Medium

- - + + @@ -15114,8 +15114,8 @@

Medium

- - + + @@ -15128,8 +15128,8 @@

Medium

- - + + @@ -15141,8 +15141,8 @@

Medium

- - + + @@ -15152,8 +15152,8 @@

Informational

- - + + @@ -15163,12 +15163,12 @@

High

- - + + - + - - + + @@ -15188,8 +15188,8 @@

Informational

- - + + @@ -15202,8 +15202,8 @@

Medium

- - + + @@ -15215,8 +15215,8 @@

High

- - + + @@ -15226,8 +15226,8 @@

Medium

- - + + @@ -15237,8 +15237,8 @@

Medium

- - + + @@ -15250,19 +15250,19 @@

Medium

- - + + - + - - + + @@ -15272,25 +15272,25 @@

Informational

- - + + - + - - + + - - + + @@ -15317,8 +15317,8 @@

Informational

- - + + @@ -15328,8 +15328,8 @@

High

- - + + @@ -15339,8 +15339,8 @@

High

- - + + @@ -15350,8 +15350,8 @@

Informational

- - + + @@ -15364,8 +15364,8 @@

Low

- - + + @@ -15375,8 +15375,8 @@

Informational

- - + + @@ -15389,8 +15389,8 @@

Informational

- - + + @@ -15403,14 +15403,14 @@

Informational

- - + + - - + + @@ -15433,19 +15433,19 @@

Informational

- - + + - + - - + + @@ -15458,8 +15458,8 @@

Informational

- - + + @@ -15472,8 +15472,8 @@

Informational

- - + + @@ -15483,8 +15483,8 @@

Informational

- - + + @@ -15497,8 +15497,8 @@

Informational

- - + + @@ -15508,8 +15508,8 @@

Informational

- - + + @@ -15522,8 +15522,8 @@

High

- - + + @@ -15536,8 +15536,8 @@

Informational

- - + + @@ -15550,8 +15550,8 @@

High

- - + + @@ -15564,8 +15564,8 @@

Medium

- - + + @@ -15578,8 +15578,8 @@

High

- - + + @@ -15589,8 +15589,8 @@

High

- - + + @@ -15600,19 +15600,19 @@

Informational

- - + + - + - - + + @@ -15622,8 +15622,8 @@

Informational

- - + + @@ -15633,8 +15633,8 @@

Medium

- - + + @@ -15646,8 +15646,8 @@

Informational

- - + + @@ -15657,8 +15657,8 @@

Medium

- - + + @@ -15668,8 +15668,8 @@

Informational

- - + + @@ -15682,8 +15682,8 @@

Medium

- - + + @@ -15693,8 +15693,8 @@

Informational

- - + + @@ -15708,8 +15708,8 @@

Informational

- - + + @@ -15722,8 +15722,8 @@

Medium

- - + + @@ -15733,8 +15733,8 @@

Informational

- - + + @@ -15747,8 +15747,8 @@

Informational

- - + + @@ -15761,8 +15761,8 @@

Informational

- - + + @@ -15772,8 +15772,8 @@

Informational

- - + + @@ -15785,12 +15785,12 @@

Informational

- - + + - + - - + + @@ -15812,8 +15812,8 @@

Informational

- - + + @@ -15823,14 +15823,14 @@

Medium

- - + + +- 111111111111-us-east-1-kb-data-bucket @@ -15838,8 +15838,8 @@

Medium

- - + + @@ -15857,16 +15857,16 @@

High

- - + + @@ -15878,8 +15878,8 @@

High

- - + + @@ -15892,19 +15892,19 @@

Medium

- - + + - + - - + + @@ -15914,8 +15914,8 @@

Informational

- - + + @@ -15925,8 +15925,8 @@

Informational

- - + + @@ -15939,8 +15939,8 @@

Low

- - + + @@ -15952,8 +15952,8 @@

Medium

- - + + @@ -15963,8 +15963,8 @@

Informational

- - + + @@ -15977,8 +15977,8 @@

Medium

- - + + @@ -15990,8 +15990,8 @@

Medium

- - + + @@ -16004,8 +16004,8 @@

Medium

- - + + @@ -16017,8 +16017,8 @@

Medium

- - + + @@ -16028,8 +16028,8 @@

Informational

- - + + @@ -16039,8 +16039,8 @@

Informational

- - + + @@ -16053,8 +16053,8 @@

Medium

- - + + @@ -16064,8 +16064,8 @@

Informational

- - + + @@ -16078,8 +16078,8 @@

Medium

- - + + @@ -16091,8 +16091,8 @@

High

- - + + @@ -16102,8 +16102,8 @@

Medium

- - + + @@ -16113,8 +16113,8 @@

Medium

- - + + @@ -16126,19 +16126,19 @@

Medium

- - + + - + - - + + @@ -16148,8 +16148,8 @@

Informational

- - + + @@ -16159,8 +16159,8 @@

Informational

- - + + @@ -16180,8 +16180,8 @@

High

- - + + @@ -16193,8 +16193,8 @@

Informational

- - + + @@ -16204,8 +16204,8 @@

High

- - + + @@ -16215,8 +16215,8 @@

High

- - + + @@ -16226,8 +16226,8 @@

Informational

- - + + @@ -16240,8 +16240,8 @@

Low

- - + + @@ -16251,8 +16251,8 @@

Informational

- - + + @@ -16265,8 +16265,8 @@

Informational

- - + + @@ -16279,8 +16279,8 @@

Informational

- - + + @@ -16294,8 +16294,8 @@

Medium

- - + + @@ -16308,8 +16308,8 @@

Informational

- - + + @@ -16319,8 +16319,8 @@

Medium

- - + + @@ -16333,8 +16333,8 @@

Informational

- - + + @@ -16347,8 +16347,8 @@

Informational

- - + + @@ -16358,8 +16358,8 @@

Informational

- - + + @@ -16372,8 +16372,8 @@

Informational

- - + + @@ -16383,8 +16383,8 @@

Informational

- - + + @@ -16397,8 +16397,8 @@

High

- - + + @@ -16411,8 +16411,8 @@

Informational

- - + + @@ -16425,8 +16425,8 @@

High

- - + + @@ -16439,8 +16439,8 @@

Medium

- - + + @@ -16453,8 +16453,8 @@

High

- - + + @@ -16467,8 +16467,8 @@

High

- - + + @@ -16478,8 +16478,8 @@

Informational

- - + + @@ -16489,8 +16489,8 @@

Informational

- - + + @@ -16500,8 +16500,8 @@

Informational

- - + + @@ -16511,8 +16511,8 @@

Medium

- - + + @@ -16524,8 +16524,8 @@

Informational

- - + + @@ -16535,8 +16535,8 @@

Medium

- - + + @@ -16546,8 +16546,8 @@

Informational

- - + + @@ -16557,8 +16557,8 @@

Medium

- - + + @@ -16568,8 +16568,8 @@

Informational

- - + + @@ -16583,8 +16583,8 @@

Informational

- - + + @@ -16597,8 +16597,8 @@

Medium

- - + + @@ -16608,8 +16608,8 @@

Informational

- - + + @@ -16622,8 +16622,8 @@

Informational

- - + + @@ -16636,8 +16636,8 @@

Informational

- - + + @@ -16647,8 +16647,8 @@

Informational

- - + + @@ -16660,12 +16660,12 @@

Informational

- - + + - + - - + + @@ -16687,8 +16687,8 @@

Informational

- - + + @@ -16698,8 +16698,8 @@

Medium

- - + + @@ -16712,8 +16712,8 @@

Medium

- - + + @@ -16723,8 +16723,8 @@

Informational

- - + + @@ -16742,8 +16742,8 @@

High

- - + + @@ -16753,8 +16753,8 @@

Informational

- - + + @@ -16764,8 +16764,8 @@

Medium

- - + + @@ -16775,8 +16775,8 @@

Informational

- - + + @@ -16786,8 +16786,8 @@

Informational

- - + + @@ -16797,8 +16797,8 @@

Informational

- - + + @@ -16808,8 +16808,8 @@

Informational

- - + + diff --git a/sample-reports/security_assessment_single_account.html b/sample-reports/security_assessment_single_account.html index 8a65452..4a3fbfd 100644 --- a/sample-reports/security_assessment_single_account.html +++ b/sample-reports/security_assessment_single_account.html @@ -214,7 +214,7 @@

By Service

@@ -224,7 +224,7 @@

By Service

Security Assessment Overview

2026-06-20T01:48:33.485086 - Account: 007564470903 · 3 Regions + Account: 111111111111 · 3 Regions
@@ -274,8 +274,8 @@

Security Assessment Overview

-
Account IDRegionCheck IDFindingDetailsResolutionReferenceSeverityStatus
111111111111 us-east-1 FS-01 AWS Shield Advanced Not EnabledFailed
193047247167
111111111111 us-east-1 FS-01 No Regional WAF Web ACLs FoundFailed
193047247167
111111111111 us-east-1 FS-02 API Gateway Usage Plans Missing ThrottleFailed
193047247167
111111111111 us-east-1 FS-03 Bedrock Token Quotas At DefaultN/A
193047247167
111111111111 us-east-1 FS-04 No Cost Anomaly Detection MonitorsFailed
193047247167
111111111111 us-east-1 FS-05 No Bedrock CloudWatch Alarms FoundFailed
193047247167
111111111111 us-east-1 FS-06 No AI/ML Service Budgets ConfiguredFailed
193047247167
111111111111 us-east-1 FS-07 Agent Action Boundary CheckN/A
193047247167
111111111111 us-east-1 FS-08 AgentCore Runtimes Missing Policy EngineFailed
193047247167
111111111111 us-east-1 FS-09 Agent Lambda Functions Without Concurrency LimitsAgent-related Lambda functions without reserved concurrency: aiml-security-aiml-security-193047247167-FinServAssessment, resco-aiml-IAMPermissionCaching, aiml-security-aiml-security-193047247167-SagemakerAssessment, resco-aiml-CleanupBucket, aiml-security-aiml-security-193047247167-BedrockAssessment, resco-aiml-BedrockAssessment, aiml-security-aiml-security-193047247167-CleanupBucket, aiml-security-aiml-security-193047247167-AgentCoreAssessment, e2ebedrockrag-OSSInfraStack-BKBOSSInfraSetupLambda-031La8JAQXtk, e2ebedrockrag-OSSInfraSta-OSSIndexCreationProvider-g56en9UzRjII. Unlimited concurrency allows runaway agent loops to exhaust account limits.Agent-related Lambda functions without reserved concurrency: aiml-security-aiml-security-111111111111-FinServAssessment, resco-aiml-IAMPermissionCaching, aiml-security-aiml-security-111111111111-SagemakerAssessment, resco-aiml-CleanupBucket, aiml-security-aiml-security-111111111111-BedrockAssessment, resco-aiml-BedrockAssessment, aiml-security-aiml-security-111111111111-CleanupBucket, aiml-security-aiml-security-111111111111-AgentCoreAssessment, e2ebedrockrag-OSSInfraStack-BKBOSSInfraSetupLambda-031La8JAQXtk, e2ebedrockrag-OSSInfraSta-OSSIndexCreationProvider-g56en9UzRjII. Unlimited concurrency allows runaway agent loops to exhaust account limits. 1. Set reserved concurrency on agent Lambda functions. 2. Implement maximum iteration counts in agent orchestration logic. 3. Use Step Functions with MaxConcurrency and timeout states. @@ -15177,8 +15177,8 @@

Medium

Failed
193047247167
111111111111 us-east-1 FS-10 Human-in-the-Loop Check — No Agent Workflows FoundN/A
193047247167
111111111111 us-east-1 FS-11 No Agent Rate Alarms FoundFailed
193047247167
111111111111 us-east-1 FS-12 No Bedrock-Scoped SCPs FoundFailed
193047247167
111111111111 us-east-1 FS-13 Model Provenance Tags PresentPassed
193047247167
111111111111 us-east-1 FS-14 Model Governance Config Rules PresentPassed
193047247167
111111111111 us-east-1 FS-15 No Bedrock Evaluation Jobs FoundFailed
193047247167
111111111111 us-east-1 FS-16 ECR Repositories Without Image Scanning4 ECR repo(s) without scan-on-push: mlexplorationrepo, cdk-hnb659fds-container-assets-193047247167-us-east-1, bedrock-agentcore-customer_support_agent, bedrock-agentcore-origami_expeditions.4 ECR repo(s) without scan-on-push: mlexplorationrepo, cdk-hnb659fds-container-assets-111111111111-us-east-1, bedrock-agentcore-customer_support_agent, bedrock-agentcore-origami_expeditions. Enable scan-on-push for all ECR repositories containing model containers. Consider enabling Enhanced Scanning (Inspector) for CVE detection. High Failed
193047247167
111111111111 us-east-1 FS-20 No SageMaker Feature Groups FoundN/A
193047247167
111111111111 us-east-1 FS-21 Training Data Buckets Without Versioning13 training data bucket(s) without versioning: ancbedrocklogging, bedrock-agentcore-codebuild-sources-193047247167-us-east-1, bedrock-bda-us-east-1-dda43109-6557-48bb-993d-3f97126b64b4, bedrock-bda-us-east-1-logging-00719114-debd-4487-85d1-09cbc3fc8, bedrock-kb-bucket-f736570b, bedrock-video-generation-us-east-1-h5ltpm, fsi-genai-workshop-bedrock-datasources-193047247167-us-west-2, knowledgebase-bedrock-agent-agasthik, llmevaluationpromptfoo-bedrockkb-cozhbzbrcmd2, sagemaker-studio-193047247167-huo1mvme4t.13 training data bucket(s) without versioning: ancbedrocklogging, bedrock-agentcore-codebuild-sources-111111111111-us-east-1, bedrock-bda-us-east-1-dda43109-6557-48bb-993d-3f97126b64b4, bedrock-bda-us-east-1-logging-00719114-debd-4487-85d1-09cbc3fc8, bedrock-kb-bucket-f736570b, bedrock-video-generation-us-east-1-h5ltpm, fsi-genai-workshop-bedrock-datasources-111111111111-us-west-2, knowledgebase-bedrock-agent-agasthik, llmevaluationpromptfoo-bedrockkb-cozhbzbrcmd2, sagemaker-studio-111111111111-huo1mvme4t. Enable S3 versioning on all training data buckets. Consider enabling MFA Delete for additional protection against poisoning. High Failed
193047247167
111111111111 us-east-1 FS-22 Overly Permissive Knowledge Base IAM Roles 722 role(s) with wildcard KB permissions: -- Role '193047247167-us-east-1-kb-setup-function-role' allows 'bedrock:CreateKnowledgeBase' on Resource '*' (no ARN scoping to specific Knowledge Bases) -- Role '193047247167-us-east-1-kb-setup-function-role' allows 'bedrock:CreateDataSource' on Resource '*' (no ARN scoping to specific Knowledge Bases) +- Role '111111111111-us-east-1-kb-setup-function-role' allows 'bedrock:CreateKnowledgeBase' on Resource '*' (no ARN scoping to specific Knowledge Bases) +- Role '111111111111-us-east-1-kb-setup-function-role' allows 'bedrock:CreateDataSource' on Resource '*' (no ARN scoping to specific Knowledge Bases) - Role 'Admin' allows '*' - Role 'agentcore-wildrydes_gateway_role_ab3991f6-role' allows 'bedrock:*' - Role 'AgentCoreEvalsSDK-us-east-1-d04ba7b68b' allows 'bedrock:InvokeModel' on Resource '*' (no ARN scoping to specific Knowledge Bases) @@ -15304,8 +15304,8 @@

High

Failed
193047247167
111111111111 us-east-1 FS-24 ADVISORY: Knowledge Base Metadata Filtering — Manual Review RequiredN/A
193047247167
111111111111 us-east-1 FS-25 OpenSearch Serverless Encryption Policies PresentPassed
193047247167
111111111111 us-east-1 FS-26 OpenSearch Serverless Collections Not VPC-RestrictedFailed
193047247167
111111111111 us-east-1 FS-27 No Guardrails — Contextual Grounding Not ApplicableN/A
193047247167
111111111111 us-east-1 FS-27 Automated Reasoning Policies — Access CheckN/A
193047247167
111111111111 us-east-1 FS-28 No Guardrails — Denied Topics Not ApplicableN/A
193047247167
111111111111 us-east-1 FS-29 ADVISORY: Compliance Disclaimer — Manual Review RequiredN/A
193047247167
111111111111 us-east-1 FS-30 ADVISORY: Compliance Dataset Coverage — Manual Review RequiredN/A
193047247167
111111111111 us-east-1 FS-31 Knowledge Base Data Sources Past Review Threshold 2 data source(s) not synced in >7 days (a configurable review threshold, NOT an AWS-mandated limit): - KB 'knowledge-base-semiconductors' source 'knowledge-base-quick-start-qpvuv-data-source' last synced 702 days ago -- KB '193047247167-us-east-1-kb' source '193047247167-us-east-1-kb-datasource' last synced 180 days ago +- KB '111111111111-us-east-1-kb' source '111111111111-us-east-1-kb-datasource' last synced 180 days ago Confirm this age is acceptable for each data source's currency requirement — slow-changing reference data may legitimately sync infrequently. 1. Define the maximum acceptable data age per use case (e.g., intraday for market data, daily for product terms, weekly/monthly for regulatory guidance) and adjust the review threshold to match. 2. Configure automated sync (EventBridge Scheduler → StartIngestionJob) at that cadence — see FS-61. @@ -15419,8 +15419,8 @@

Medium

Failed
193047247167
111111111111 us-east-1 FS-32 ADVISORY: Source Attribution — Manual Review RequiredN/A
193047247167
111111111111 us-east-1 FS-33 KB Data Source Buckets Without VersioningKB data source S3 buckets without versioning: 193047247167-us-east-1-kb-data-bucket.KB data source S3 buckets without versioning: 111111111111-us-east-1-kb-data-bucket. Enable S3 versioning on all KB data source buckets. Enable S3 Object Integrity (checksum) for tamper detection. Medium Failed
193047247167
111111111111 us-east-1 FS-34 Legacy Foundation Models Available in RegionN/A
193047247167
111111111111 us-east-1 FS-35 ADVISORY: Harmful-Content Test Coverage — Manual Review RequiredN/A
193047247167
111111111111 us-east-1 FS-36 No Guardrails — Content Filters Not ApplicableN/A
193047247167
111111111111 us-east-1 FS-37 ADVISORY: User Feedback Mechanism — Manual Review RequiredN/A
193047247167
111111111111 us-east-1 FS-38 No Guardrails — Word Filters Not ApplicableN/A
193047247167
111111111111 us-east-1 FS-39 No SageMaker Clarify Bias MonitoringFailed
193047247167
111111111111 us-east-1 FS-40 ADVISORY: Bias Dataset Coverage — Manual Review RequiredN/A
193047247167
111111111111 us-east-1 FS-41 No SageMaker Clarify Explainability MonitoringFailed
193047247167
111111111111 us-east-1 FS-42 No SageMaker Model Cards FoundFailed
193047247167
111111111111 us-east-1 FS-43 No CloudWatch Logs Data Protection PoliciesFailed
193047247167
111111111111 us-east-1 FS-44 Amazon Macie EnabledPassed
193047247167
111111111111 us-east-1 FS-45 No Guardrails — PII Filters Not ApplicableN/A
193047247167
111111111111 us-east-1 FS-46 AI/ML Buckets Without Data Classification Tags18 AI/ML bucket(s) without data-classification tags: 193047247167-us-east-1-kb-data-bucket, ancbedrocklogging, ancknowledgebase, aws-streaming-data-solut-outputaccesslogsbucket8b-1o7m0kb4bafm4, bedrock-agentcore-codebuild-sources-193047247167-us-east-1, bedrock-bda-us-east-1-dda43109-6557-48bb-993d-3f97126b64b4, bedrock-bda-us-east-1-logging-00719114-debd-4487-85d1-09cbc3fc8, bedrock-kb-bucket-f736570b, bedrock-video-generation-us-east-1-h5ltpm, create-customer-resources-kb-bucket-193047247167.18 AI/ML bucket(s) without data-classification tags: 111111111111-us-east-1-kb-data-bucket, ancbedrocklogging, ancknowledgebase, aws-streaming-data-solut-outputaccesslogsbucket8b-1o7m0kb4bafm4, bedrock-agentcore-codebuild-sources-111111111111-us-east-1, bedrock-bda-us-east-1-dda43109-6557-48bb-993d-3f97126b64b4, bedrock-bda-us-east-1-logging-00719114-debd-4487-85d1-09cbc3fc8, bedrock-kb-bucket-f736570b, bedrock-video-generation-us-east-1-h5ltpm, create-customer-resources-kb-bucket-111111111111. Tag all AI/ML data buckets with 'data-classification' key. Values: Public, Internal, Confidential, Restricted. Enforce via SCP or AWS Config rule. Medium Failed
193047247167
111111111111 us-east-1 FS-47 No Guardrails — Grounding Threshold Not ApplicableN/A
193047247167
111111111111 us-east-1 FS-48 Active Knowledge Bases for RAG PresentPassed
193047247167
111111111111 us-east-1 FS-49 ADVISORY: Hallucination Disclaimer — Manual Review RequiredN/A
193047247167
111111111111 us-east-1 FS-50 No Guardrails With Relevance Grounding FiltersFailed
193047247167
111111111111 us-east-1 FS-51 No Guardrails — Prompt Attack Filters Not ApplicableN/A
193047247167
111111111111 us-east-1 FS-52 Bedrock Lambda Functions on Deprecated RuntimesFailed
193047247167
111111111111 us-east-1 FS-53 No WAF Web ACLs — Injection Rules Not ApplicableN/A
193047247167
111111111111 us-east-1 FS-54 ADVISORY: Penetration Testing — Manual Review RequiredN/A
193047247167
111111111111 us-east-1 FS-55 No Output Validation Functions FoundFailed
193047247167
111111111111 us-east-1 FS-56 No WAF ACLs — XSS Prevention Not ApplicableN/A
193047247167
111111111111 us-east-1 FS-57 ADVISORY: Output Encoding — Manual Review RequiredN/A
193047247167
111111111111 us-east-1 FS-58 ADVISORY: Output Schema Validation — Manual Review RequiredN/A
193047247167
111111111111 us-east-1 FS-59 No Guardrails — Topic Allowlist Not ApplicableN/A
193047247167
111111111111 us-east-1 FS-60 ADVISORY: Contextual Grounding for Off-Topic PreventionN/A
193047247167
111111111111 us-east-1 FS-61 COULD NOT ASSESS: Knowledge Base Sync Schedule CheckThis check could not be completed (error: An error occurred (AccessDeniedException) when calling the ListSchedules operation: User: arn:aws:sts::193047247167:assumed-role/aiml-security-19304724716-FinServSecurityAssessment-G8d5dEiMJsZB/aiml-security-aiml-security-193047247167-FinServAssessment is not authorized to perform: scheduler:ListSchedules on resource: arn:aws:scheduler:us-east-1:193047247167:schedule/*/* because no identity-based policy allows the scheduler:ListSchedules action). The most common cause is a missing IAM permission for the assessment role; it may also indicate an unsupported region or an outdated botocore. This control was NOT assessed — verify the role's permissions and re-run, and assess this control manually until resolved.This check could not be completed (error: An error occurred (AccessDeniedException) when calling the ListSchedules operation: User: arn:aws:sts::111111111111:assumed-role/aiml-security-19304724716-FinServSecurityAssessment-G8d5dEiMJsZB/aiml-security-aiml-security-111111111111-FinServAssessment is not authorized to perform: scheduler:ListSchedules on resource: arn:aws:scheduler:us-east-1:111111111111:schedule/*/* because no identity-based policy allows the scheduler:ListSchedules action). The most common cause is a missing IAM permission for the assessment role; it may also indicate an unsupported region or an outdated botocore. This control was NOT assessed — verify the role's permissions and re-run, and assess this control manually until resolved. 1. Confirm the assessment role grants the actions this check requires (see the documented IAM permission set in the README). 2. Confirm the service/feature is supported in the assessed region. 3. Ensure botocore meets the version floor in requirements.txt. @@ -15799,8 +15799,8 @@

Low

N/A
193047247167
111111111111 us-east-1 FS-62 ADVISORY: Data Currency Disclaimer — Manual Review RequiredN/A
193047247167
111111111111 us-east-1 FS-63 Foundation Model Lifecycle ManagementPassed
193047247167
111111111111 us-east-1 FS-65 KB Data Source Buckets Missing S3 Event Notifications The following KB data-source S3 buckets have no event notifications configured. Unauthorized document modifications will not be detected in real time: - semiconductor-demo-9999 -- 193047247167-us-east-1-kb-data-bucket 1. Enable Amazon EventBridge notifications on each KB data-source S3 bucket. 2. Create an EventBridge rule to route s3:ObjectCreated, s3:ObjectRemoved, and s3:ObjectModified events to an SNS topic or Lambda for alerting. 3. Integrate alerts into your security incident response workflow.Failed
193047247167
111111111111 us-east-1 FS-66 AgentCore Runtimes Missing End-User Identity PropagationFailed
193047247167
111111111111 us-east-1 FS-67 Agent Action-Group Lambdas May Lack Transaction Thresholds The following agent action-group Lambda functions have no environment variables whose names suggest transaction-value threshold configuration (this is a best-effort heuristic — a threshold enforced in code or in an AgentCore Policy Engine rule would not be detected here, so treat this as a prompt for manual verification rather than a definitive gap). Without explicit limits, agents could initiate unbounded financial transactions: -- aiml-security-aiml-security-193047247167-FinServAssessment -- aiml-security-aiml-security-193047247167-BedrockAssessment +- aiml-security-aiml-security-111111111111-FinServAssessment +- aiml-security-aiml-security-111111111111-BedrockAssessment - resco-aiml-BedrockAssessment -- aiml-security-aiml-security-193047247167-AgentCoreAssessment +- aiml-security-aiml-security-111111111111-AgentCoreAssessment - e2ebedrockrag-OSSInfraStack-BKBOSSInfraSetupLambda-031La8JAQXtk - e2ebedrockrag-OSSInfraSta-OSSIndexCreationProvider-g56en9UzRjII - resco-aiml-AgentCoreAssessmentFailed
193047247167
111111111111 us-east-1 FS-68 API Gateway Request Body Size Limits Not EnforcedFailed
193047247167
111111111111 us-east-1 FS-69 Prompt Input Validation Functions PresentFound 3 Lambda function(s) with input validation/sanitization naming patterns: resco-aiml-CleanupBucket, visa-bulletin-tracker-prod-cleanup, aiml-security-aiml-security-193047247167-CleanupBucket.Found 3 Lambda function(s) with input validation/sanitization naming patterns: resco-aiml-CleanupBucket, visa-bulletin-tracker-prod-cleanup, aiml-security-aiml-security-111111111111-CleanupBucket. Review these functions to confirm they cover: special-character stripping, format validation, size limits, and injection-sequence detection. Medium Passed
193047247167
111111111111 eu-west-1 FS-00 FinServ Regional Scope Not ApplicableN/A
193047247167
111111111111 ap-southeast-2 FS-00 FinServ Regional Scope Not ApplicableN/A
472057511786
333333333333 us-east-1 FS-01 AWS Shield Advanced Not EnabledFailed
472057511786
333333333333 us-east-1 FS-01 No Regional WAF Web ACLs FoundFailed
472057511786
333333333333 us-east-1 FS-02 No API Gateway Usage Plans FoundN/A
472057511786
333333333333 us-east-1 FS-03 Bedrock Token Quotas At DefaultN/A
472057511786
333333333333 us-east-1 FS-04 No Cost Anomaly Detection MonitorsFailed
472057511786
333333333333 us-east-1 FS-05 No Bedrock CloudWatch Alarms FoundFailed
472057511786
333333333333 us-east-1 FS-06 No AI/ML Service Budgets ConfiguredFailed
472057511786
333333333333 us-east-1 FS-07 Agent Action Boundary CheckN/A
472057511786
333333333333 us-east-1 FS-08 No AgentCore Runtimes FoundN/A
472057511786
333333333333 us-east-1 FS-09 Agent Lambda Functions Without Concurrency LimitsFailed
472057511786
333333333333 us-east-1 FS-10 Human-in-the-Loop Check — No Agent Workflows FoundN/A
472057511786
333333333333 us-east-1 FS-11 No Agent Rate Alarms FoundFailed
472057511786
333333333333 us-east-1 FS-12 No Bedrock-Scoped SCPs FoundFailed
472057511786
333333333333 us-east-1 FS-13 Model Provenance Tags PresentPassed
472057511786
333333333333 us-east-1 FS-14 Model Governance Config Rules PresentPassed
472057511786
333333333333 us-east-1 FS-15 No Bedrock Evaluation Jobs FoundFailed
472057511786
333333333333 us-east-1 FS-16 ECR Repositories Without Image Scanning1 ECR repo(s) without scan-on-push: cdk-hnb659fds-container-assets-472057511786-us-east-1.1 ECR repo(s) without scan-on-push: cdk-hnb659fds-container-assets-333333333333-us-east-1. Enable scan-on-push for all ECR repositories containing model containers. Consider enabling Enhanced Scanning (Inspector) for CVE detection. High Failed
472057511786
333333333333 us-east-1 FS-20 No SageMaker Feature Groups FoundN/A
472057511786
333333333333 us-east-1 FS-21 No Training Data Buckets IdentifiedN/A
472057511786
333333333333 us-east-1 FS-22 Overly Permissive Knowledge Base IAM RolesFailed
472057511786
333333333333 us-east-1 FS-24 ADVISORY: Knowledge Base Metadata Filtering — Manual Review RequiredN/A
472057511786
333333333333 us-east-1 FS-25 OpenSearch Serverless Encryption Policies PresentPassed
472057511786
333333333333 us-east-1 FS-26 OpenSearch Serverless Collections Not VPC-RestrictedFailed
472057511786
333333333333 us-east-1 FS-27 No Guardrails — Contextual Grounding Not ApplicableN/A
472057511786
333333333333 us-east-1 FS-27 Automated Reasoning Policies — Access CheckN/A
472057511786
333333333333 us-east-1 FS-28 No Guardrails — Denied Topics Not ApplicableN/A
472057511786
333333333333 us-east-1 FS-29 ADVISORY: Compliance Disclaimer — Manual Review RequiredN/A
472057511786
333333333333 us-east-1 FS-30 ADVISORY: Compliance Dataset Coverage — Manual Review RequiredN/A
472057511786
333333333333 us-east-1 FS-31 Knowledge Base Data Sources Past Review ThresholdFailed
472057511786
333333333333 us-east-1 FS-32 ADVISORY: Source Attribution — Manual Review RequiredN/A
472057511786
333333333333 us-east-1 FS-33 KB Data Source Buckets Have VersioningPassed
472057511786
333333333333 us-east-1 FS-34 Legacy Foundation Models Available in RegionN/A
472057511786
333333333333 us-east-1 FS-35 ADVISORY: Harmful-Content Test Coverage — Manual Review RequiredN/A
472057511786
333333333333 us-east-1 FS-36 No Guardrails — Content Filters Not ApplicableN/A
472057511786
333333333333 us-east-1 FS-37 ADVISORY: User Feedback Mechanism — Manual Review RequiredN/A
472057511786
333333333333 us-east-1 FS-38 No Guardrails — Word Filters Not ApplicableN/A
472057511786
333333333333 us-east-1 FS-39 No SageMaker Clarify Bias MonitoringFailed
472057511786
333333333333 us-east-1 FS-40 ADVISORY: Bias Dataset Coverage — Manual Review RequiredN/A
472057511786
333333333333 us-east-1 FS-41 No SageMaker Clarify Explainability MonitoringFailed
472057511786
333333333333 us-east-1 FS-42 No SageMaker Model Cards FoundFailed
472057511786
333333333333 us-east-1 FS-43 No CloudWatch Logs Data Protection PoliciesFailed
472057511786
333333333333 us-east-1 FS-44 Amazon Macie Not EnabledFailed
472057511786
333333333333 us-east-1 FS-45 No Guardrails — PII Filters Not ApplicableN/A
472057511786
333333333333 us-east-1 FS-46 No AI/ML Data Buckets IdentifiedN/A
472057511786
333333333333 us-east-1 FS-47 No Guardrails — Grounding Threshold Not ApplicableN/A
472057511786
333333333333 us-east-1 FS-48 Active Knowledge Bases for RAG PresentPassed
472057511786
333333333333 us-east-1 FS-49 ADVISORY: Hallucination Disclaimer — Manual Review RequiredN/A
472057511786
333333333333 us-east-1 FS-50 No Guardrails With Relevance Grounding FiltersFailed
472057511786
333333333333 us-east-1 FS-51 No Guardrails — Prompt Attack Filters Not ApplicableN/A
472057511786
333333333333 us-east-1 FS-52 Bedrock Lambda Functions on Current RuntimesPassed
472057511786
333333333333 us-east-1 FS-53 No WAF Web ACLs — Injection Rules Not ApplicableN/A
472057511786
333333333333 us-east-1 FS-54 ADVISORY: Penetration Testing — Manual Review RequiredN/A
472057511786
333333333333 us-east-1 FS-55 No Output Validation Functions FoundFailed
472057511786
333333333333 us-east-1 FS-56 No WAF ACLs — XSS Prevention Not ApplicableN/A
472057511786
333333333333 us-east-1 FS-57 ADVISORY: Output Encoding — Manual Review RequiredN/A
472057511786
333333333333 us-east-1 FS-58 ADVISORY: Output Schema Validation — Manual Review RequiredN/A
472057511786
333333333333 us-east-1 FS-59 No Guardrails — Topic Allowlist Not ApplicableN/A
472057511786
333333333333 us-east-1 FS-60 ADVISORY: Contextual Grounding for Off-Topic PreventionN/A
472057511786
333333333333 us-east-1 FS-61 COULD NOT ASSESS: Knowledge Base Sync Schedule CheckThis check could not be completed (error: An error occurred (AccessDeniedException) when calling the ListSchedules operation: User: arn:aws:sts::472057511786:assumed-role/aiml-security-mgmt-FinServSecurityAssessmentFunctio-pwj9by1swQWa/aiml-security-aiml-security-mgmt-FinServAssessment is not authorized to perform: scheduler:ListSchedules on resource: arn:aws:scheduler:us-east-1:472057511786:schedule/*/* because no identity-based policy allows the scheduler:ListSchedules action). The most common cause is a missing IAM permission for the assessment role; it may also indicate an unsupported region or an outdated botocore. This control was NOT assessed — verify the role's permissions and re-run, and assess this control manually until resolved.This check could not be completed (error: An error occurred (AccessDeniedException) when calling the ListSchedules operation: User: arn:aws:sts::333333333333:assumed-role/aiml-security-mgmt-FinServSecurityAssessmentFunctio-pwj9by1swQWa/aiml-security-aiml-security-mgmt-FinServAssessment is not authorized to perform: scheduler:ListSchedules on resource: arn:aws:scheduler:us-east-1:333333333333:schedule/*/* because no identity-based policy allows the scheduler:ListSchedules action). The most common cause is a missing IAM permission for the assessment role; it may also indicate an unsupported region or an outdated botocore. This control was NOT assessed — verify the role's permissions and re-run, and assess this control manually until resolved. 1. Confirm the assessment role grants the actions this check requires (see the documented IAM permission set in the README). 2. Confirm the service/feature is supported in the assessed region. 3. Ensure botocore meets the version floor in requirements.txt. @@ -16674,8 +16674,8 @@

Low

N/A
472057511786
333333333333 us-east-1 FS-62 ADVISORY: Data Currency Disclaimer — Manual Review RequiredN/A
472057511786
333333333333 us-east-1 FS-63 Foundation Model Lifecycle ManagementPassed
472057511786
333333333333 us-east-1 FS-65 KB Data Source Buckets Missing S3 Event NotificationsFailed
472057511786
333333333333 us-east-1 FS-66 No AgentCore Runtimes FoundN/A
472057511786
333333333333 us-east-1 FS-67 Agent Action-Group Lambdas May Lack Transaction ThresholdsFailed
472057511786
333333333333 us-east-1 FS-68 API Gateway Request Body Size Limits — Not ApplicableN/A
472057511786
333333333333 us-east-1 FS-69 Prompt Input Validation Functions PresentPassed
472057511786
333333333333 eu-west-1 FS-00 FinServ Regional Scope Not ApplicableN/A
472057511786
333333333333 ap-southeast-2 FS-00 FinServ Regional Scope Not ApplicableN/A
230266523520
222222222222 us-east-1 FS-00 FinServ Regional Scope Not ApplicableN/A
230266523520
222222222222 eu-west-1 FS-00 FinServ Regional Scope Not ApplicableN/A
230266523520
222222222222 ap-southeast-2 FS-00 FinServ Regional Scope Not Applicable
- +
Account IDRegionCheck IDFindingDetailsResolutionReferenceSeverityStatus
007564470903
+ @@ -285,8 +285,8 @@

Security Assessment Overview

- - + + @@ -296,8 +296,8 @@

Security Assessment Overview

- - + + @@ -307,8 +307,8 @@

Security Assessment Overview

- - + + @@ -318,8 +318,8 @@

Security Assessment Overview

- - + + @@ -333,8 +333,8 @@

Security Assessment Overview

- - + + @@ -344,19 +344,19 @@

Security Assessment Overview

- - + + - + - - + + @@ -366,8 +366,8 @@

Security Assessment Overview

- - + + @@ -377,8 +377,8 @@

Security Assessment Overview

- - + + @@ -388,8 +388,8 @@

Security Assessment Overview

- - + + @@ -399,8 +399,8 @@

Security Assessment Overview

- - + + @@ -410,8 +410,8 @@

Security Assessment Overview

- - + + @@ -421,8 +421,8 @@

Security Assessment Overview

- - + + @@ -432,8 +432,8 @@

Security Assessment Overview

- - + + @@ -443,8 +443,8 @@

Security Assessment Overview

- - + + @@ -458,8 +458,8 @@

Security Assessment Overview

- - + + @@ -469,19 +469,19 @@

Security Assessment Overview

- - + + - + - - + + @@ -491,8 +491,8 @@

Security Assessment Overview

- - + + @@ -502,8 +502,8 @@

Security Assessment Overview

- - + + @@ -513,8 +513,8 @@

Security Assessment Overview

- - + + @@ -524,8 +524,8 @@

Security Assessment Overview

- - + + @@ -536,8 +536,8 @@

Security Assessment Overview

- - + + @@ -547,8 +547,8 @@

Security Assessment Overview

- - + + @@ -558,8 +558,8 @@

Security Assessment Overview

- - + + @@ -569,8 +569,8 @@

Security Assessment Overview

- - + + @@ -580,8 +580,8 @@

Security Assessment Overview

- - + + @@ -591,8 +591,8 @@

Security Assessment Overview

- - + + @@ -602,8 +602,8 @@

Security Assessment Overview

- - + + @@ -613,8 +613,8 @@

Security Assessment Overview

- - + + @@ -624,8 +624,8 @@

Security Assessment Overview

- - + + @@ -635,19 +635,19 @@

Security Assessment Overview

- - + + - + - - + + @@ -657,8 +657,8 @@

Security Assessment Overview

- - + + @@ -668,8 +668,8 @@

Security Assessment Overview

- - + + @@ -679,8 +679,8 @@

Security Assessment Overview

- - + + @@ -690,8 +690,8 @@

Security Assessment Overview

- - + + @@ -701,8 +701,8 @@

Security Assessment Overview

- - + + @@ -712,8 +712,8 @@

Security Assessment Overview

- - + + @@ -723,8 +723,8 @@

Security Assessment Overview

- - + + @@ -734,8 +734,8 @@

Security Assessment Overview

- - + + @@ -745,8 +745,8 @@

Security Assessment Overview

- - + + @@ -756,8 +756,8 @@

Security Assessment Overview

- - + + @@ -767,8 +767,8 @@

Security Assessment Overview

- - + + @@ -778,8 +778,8 @@

Security Assessment Overview

- - + + @@ -789,8 +789,8 @@

Security Assessment Overview

- - + + @@ -800,8 +800,8 @@

Security Assessment Overview

- - + + @@ -815,8 +815,8 @@

Security Assessment Overview

- - + + @@ -826,19 +826,19 @@

Security Assessment Overview

- - + + - + - - + + @@ -848,8 +848,8 @@

Security Assessment Overview

- - + + @@ -859,8 +859,8 @@

Security Assessment Overview

- - + + @@ -870,8 +870,8 @@

Security Assessment Overview

- - + + @@ -881,8 +881,8 @@

Security Assessment Overview

- - + + @@ -892,8 +892,8 @@

Security Assessment Overview

- - + + @@ -903,8 +903,8 @@

Security Assessment Overview

- - + + @@ -914,8 +914,8 @@

Security Assessment Overview

- - + + @@ -925,8 +925,8 @@

Security Assessment Overview

- - + + @@ -936,8 +936,8 @@

Security Assessment Overview

- - + + @@ -947,8 +947,8 @@

Security Assessment Overview

- - + + @@ -958,8 +958,8 @@

Security Assessment Overview

- - + + @@ -969,8 +969,8 @@

Security Assessment Overview

- - + + @@ -980,8 +980,8 @@

Security Assessment Overview

- - + + @@ -991,8 +991,8 @@

Security Assessment Overview

- - + + @@ -1002,8 +1002,8 @@

Security Assessment Overview

- - + + @@ -1013,8 +1013,8 @@

Security Assessment Overview

- - + + @@ -1024,8 +1024,8 @@

Security Assessment Overview

- - + + @@ -1035,8 +1035,8 @@

Security Assessment Overview

- - + + @@ -1046,8 +1046,8 @@

Security Assessment Overview

- - + + @@ -1057,8 +1057,8 @@

Security Assessment Overview

- - + + @@ -1068,8 +1068,8 @@

Security Assessment Overview

- - + + @@ -1079,8 +1079,8 @@

Security Assessment Overview

- - + + @@ -1090,8 +1090,8 @@

Security Assessment Overview

- - + + @@ -1101,8 +1101,8 @@

Security Assessment Overview

- - + + @@ -1112,8 +1112,8 @@

Security Assessment Overview

- - + + @@ -1123,8 +1123,8 @@

Security Assessment Overview

- - + + @@ -1134,8 +1134,8 @@

Security Assessment Overview

- - + + @@ -1145,8 +1145,8 @@

Security Assessment Overview

- - + + @@ -1156,8 +1156,8 @@

Security Assessment Overview

- - + + @@ -1167,8 +1167,8 @@

Security Assessment Overview

- - + + @@ -1178,8 +1178,8 @@

Security Assessment Overview

- - + + @@ -1189,8 +1189,8 @@

Security Assessment Overview

- - + + @@ -1200,8 +1200,8 @@

Security Assessment Overview

- - + + @@ -1211,8 +1211,8 @@

Security Assessment Overview

- - + + @@ -1222,8 +1222,8 @@

Security Assessment Overview

- - + + @@ -1233,8 +1233,8 @@

Security Assessment Overview

- - + + @@ -1244,8 +1244,8 @@

Security Assessment Overview

- - + + @@ -1255,8 +1255,8 @@

Security Assessment Overview

- - + + @@ -1266,8 +1266,8 @@

Security Assessment Overview

- - + + @@ -1277,8 +1277,8 @@

Security Assessment Overview

- - + + @@ -1288,8 +1288,8 @@

Security Assessment Overview

- - + + @@ -1299,8 +1299,8 @@

Security Assessment Overview

- - + + @@ -1310,8 +1310,8 @@

Security Assessment Overview

- - + + @@ -1321,8 +1321,8 @@

Security Assessment Overview

- - + + @@ -1332,8 +1332,8 @@

Security Assessment Overview

- - + + @@ -1343,8 +1343,8 @@

Security Assessment Overview

- - + + @@ -1354,8 +1354,8 @@

Security Assessment Overview

- - + + @@ -1365,8 +1365,8 @@

Security Assessment Overview

- - + + @@ -1376,8 +1376,8 @@

Security Assessment Overview

- - + + @@ -1387,8 +1387,8 @@

Security Assessment Overview

- - + + @@ -1398,8 +1398,8 @@

Security Assessment Overview

- - + + @@ -1409,8 +1409,8 @@

Security Assessment Overview

- - + + @@ -1420,8 +1420,8 @@

Security Assessment Overview

- - + + @@ -1431,8 +1431,8 @@

Security Assessment Overview

- - + + @@ -1442,8 +1442,8 @@

Security Assessment Overview

- - + + @@ -1453,8 +1453,8 @@

Security Assessment Overview

- - + + @@ -1464,8 +1464,8 @@

Security Assessment Overview

- - + + @@ -1475,8 +1475,8 @@

Security Assessment Overview

- - + + @@ -1486,8 +1486,8 @@

Security Assessment Overview

- - + + @@ -1497,8 +1497,8 @@

Security Assessment Overview

- - + + @@ -1508,8 +1508,8 @@

Security Assessment Overview

- - + + @@ -1519,8 +1519,8 @@

Security Assessment Overview

- - + + @@ -1530,8 +1530,8 @@

Security Assessment Overview

- - + + @@ -1541,8 +1541,8 @@

Security Assessment Overview

- - + + @@ -1552,8 +1552,8 @@

Security Assessment Overview

- - + + @@ -1563,8 +1563,8 @@

Security Assessment Overview

- - + + @@ -1574,8 +1574,8 @@

Security Assessment Overview

- - + + @@ -1585,8 +1585,8 @@

Security Assessment Overview

- - + + @@ -1596,8 +1596,8 @@

Security Assessment Overview

- - + + @@ -1607,8 +1607,8 @@

Security Assessment Overview

- - + + @@ -1618,8 +1618,8 @@

Security Assessment Overview

- - + + @@ -1629,8 +1629,8 @@

Security Assessment Overview

- - + + @@ -1640,8 +1640,8 @@

Security Assessment Overview

- - + + @@ -1651,8 +1651,8 @@

Security Assessment Overview

- - + + @@ -1662,8 +1662,8 @@

Security Assessment Overview

- - + + @@ -1673,8 +1673,8 @@

Security Assessment Overview

- - + + @@ -1684,8 +1684,8 @@

Security Assessment Overview

- - + + @@ -1695,8 +1695,8 @@

Security Assessment Overview

- - + + @@ -1706,8 +1706,8 @@

Security Assessment Overview

- - + + @@ -1717,8 +1717,8 @@

Security Assessment Overview

- - + + @@ -1728,8 +1728,8 @@

Security Assessment Overview

- - + + @@ -1739,8 +1739,8 @@

Security Assessment Overview

- - + + @@ -1750,8 +1750,8 @@

Security Assessment Overview

- - + + @@ -1761,8 +1761,8 @@

Security Assessment Overview

- - + + @@ -1772,8 +1772,8 @@

Security Assessment Overview

- - + + @@ -1783,8 +1783,8 @@

Security Assessment Overview

- - + + @@ -1794,8 +1794,8 @@

Security Assessment Overview

- - + + @@ -1805,8 +1805,8 @@

Security Assessment Overview

- - + + @@ -1816,8 +1816,8 @@

Security Assessment Overview

- - + + @@ -1827,8 +1827,8 @@

Security Assessment Overview

- - + + @@ -1838,8 +1838,8 @@

Security Assessment Overview

- - + + @@ -1849,8 +1849,8 @@

Security Assessment Overview

- - + + @@ -1860,8 +1860,8 @@

Security Assessment Overview

- - + + @@ -1871,8 +1871,8 @@

Security Assessment Overview

- - + + @@ -1882,8 +1882,8 @@

Security Assessment Overview

- - + + @@ -1893,8 +1893,8 @@

Security Assessment Overview

- - + + @@ -1904,8 +1904,8 @@

Security Assessment Overview

- - + + @@ -1915,8 +1915,8 @@

Security Assessment Overview

- - + + @@ -1926,8 +1926,8 @@

Security Assessment Overview

- - + + @@ -1937,8 +1937,8 @@

Security Assessment Overview

- - + + @@ -1948,8 +1948,8 @@

Security Assessment Overview

- - + + @@ -1959,8 +1959,8 @@

Security Assessment Overview

- - + + @@ -1970,8 +1970,8 @@

Security Assessment Overview

- - + + @@ -1981,8 +1981,8 @@

Security Assessment Overview

- - + + @@ -1992,8 +1992,8 @@

Security Assessment Overview

- - + + @@ -2003,8 +2003,8 @@

Security Assessment Overview

- - + + @@ -2014,8 +2014,8 @@

Security Assessment Overview

- - + + @@ -2025,8 +2025,8 @@

Security Assessment Overview

- - + + @@ -2036,8 +2036,8 @@

Security Assessment Overview

- - + + @@ -2047,8 +2047,8 @@

Security Assessment Overview

- - + + @@ -2058,8 +2058,8 @@

Security Assessment Overview

- - + + @@ -2069,8 +2069,8 @@

Security Assessment Overview

- - + + @@ -2080,8 +2080,8 @@

Security Assessment Overview

- - + + @@ -2091,8 +2091,8 @@

Security Assessment Overview

- - + + @@ -2102,8 +2102,8 @@

Security Assessment Overview

- - + + @@ -2113,8 +2113,8 @@

Security Assessment Overview

- - + + @@ -2124,8 +2124,8 @@

Security Assessment Overview

- - + + @@ -2135,8 +2135,8 @@

Security Assessment Overview

- - + + @@ -2146,8 +2146,8 @@

Security Assessment Overview

- - + + @@ -2157,8 +2157,8 @@

Security Assessment Overview

- - + + @@ -2168,8 +2168,8 @@

Security Assessment Overview

- - + + @@ -2210,8 +2210,8 @@

-

Account IDRegionCheck IDFindingDetailsResolutionReferenceSeverityStatus
111111111111 ap-southeast-2 BR-02 Amazon Bedrock private connectivity check Informational N/A
007564470903
111111111111 ap-southeast-2 BR-04 Bedrock Model Invocation Logging Check Informational N/A
007564470903
111111111111 ap-southeast-2 BR-05 Bedrock Guardrails Check Informational N/A
007564470903
111111111111 ap-southeast-2 BR-06 Bedrock CloudTrail Logging Check Informational N/A
007564470903
111111111111 ap-southeast-2 BR-07 Bedrock Prompt Management Check Informational N/A
007564470903
111111111111 ap-southeast-2 BR-08 Bedrock Agent IAM Roles Check Informational N/A
007564470903
111111111111 ap-southeast-2 BR-09 Bedrock Knowledge Base Encryption CheckUnable to check Bedrock Knowledge Base API: An error occurred (AccessDeniedException) when calling the ListKnowledgeBases operation: User: arn:aws:sts::007564470903:assumed-role/aiml-sec-007564470903-BedrockSecurityAssessmentFunc-188U9EAkRKkw/aiml-security-aiml-sec-007564470903-BedrockAssessment is not authorized to perform: bedrock:ListKnowledgeBases on resource: arn:aws:bedrock:ap-southeast-2:007564470903:knowledge-base/* because no identity-based policy allows the bedrock:ListKnowledgeBases actionUnable to check Bedrock Knowledge Base API: An error occurred (AccessDeniedException) when calling the ListKnowledgeBases operation: User: arn:aws:sts::111111111111:assumed-role/aiml-sec-111111111111-BedrockSecurityAssessmentFunc-188U9EAkRKkw/aiml-security-aiml-sec-111111111111-BedrockAssessment is not authorized to perform: bedrock:ListKnowledgeBases on resource: arn:aws:bedrock:ap-southeast-2:111111111111:knowledge-base/* because no identity-based policy allows the bedrock:ListKnowledgeBases action Verify your AWS credentials and permissions to access Bedrock Knowledge Bases, then retry the assessment. Informational N/A
007564470903
111111111111 ap-southeast-2 BR-10 Bedrock Guardrail IAM Enforcement Check Informational N/A
007564470903
111111111111 ap-southeast-2 BR-11 Bedrock Custom Model Encryption Check Informational N/A
007564470903
111111111111 ap-southeast-2 BR-12 Bedrock Invocation Log Encryption Check Informational N/A
007564470903
111111111111 ap-southeast-2 BR-13 Bedrock Flows Guardrails Check Informational N/A
007564470903
111111111111 eu-west-1 BR-02 Amazon Bedrock private connectivity check Informational N/A
007564470903
111111111111 eu-west-1 BR-04 Bedrock Model Invocation Logging Check Informational N/A
007564470903
111111111111 eu-west-1 BR-05 Bedrock Guardrails Check Informational N/A
007564470903
111111111111 eu-west-1 BR-06 Bedrock CloudTrail Logging Check Informational N/A
007564470903
111111111111 eu-west-1 BR-07 Bedrock Prompt Management Check Informational N/A
007564470903
111111111111 eu-west-1 BR-08 Bedrock Agent IAM Roles Check Informational N/A
007564470903
111111111111 eu-west-1 BR-09 Bedrock Knowledge Base Encryption CheckUnable to check Bedrock Knowledge Base API: An error occurred (AccessDeniedException) when calling the ListKnowledgeBases operation: User: arn:aws:sts::007564470903:assumed-role/aiml-sec-007564470903-BedrockSecurityAssessmentFunc-188U9EAkRKkw/aiml-security-aiml-sec-007564470903-BedrockAssessment is not authorized to perform: bedrock:ListKnowledgeBases on resource: arn:aws:bedrock:eu-west-1:007564470903:knowledge-base/* because no identity-based policy allows the bedrock:ListKnowledgeBases actionUnable to check Bedrock Knowledge Base API: An error occurred (AccessDeniedException) when calling the ListKnowledgeBases operation: User: arn:aws:sts::111111111111:assumed-role/aiml-sec-111111111111-BedrockSecurityAssessmentFunc-188U9EAkRKkw/aiml-security-aiml-sec-111111111111-BedrockAssessment is not authorized to perform: bedrock:ListKnowledgeBases on resource: arn:aws:bedrock:eu-west-1:111111111111:knowledge-base/* because no identity-based policy allows the bedrock:ListKnowledgeBases action Verify your AWS credentials and permissions to access Bedrock Knowledge Bases, then retry the assessment. Informational N/A
007564470903
111111111111 eu-west-1 BR-10 Bedrock Guardrail IAM Enforcement Check Informational N/A
007564470903
111111111111 eu-west-1 BR-11 Bedrock Custom Model Encryption Check Informational N/A
007564470903
111111111111 eu-west-1 BR-12 Bedrock Invocation Log Encryption Check Informational N/A
007564470903
111111111111 eu-west-1 BR-13 Bedrock Flows Guardrails Check Informational N/A
007564470903
111111111111 Global BR-01 AmazonBedrockFullAccess role check High Passed
007564470903
111111111111 Global BR-03 Marketplace Subscription Access Check High Failed
007564470903
111111111111 Global BR-03 Marketplace Subscription Access Check High Failed
007564470903
111111111111 Global BR-03 Marketplace Subscription Access Check High Failed
007564470903
111111111111 Global BR-14 Stale Bedrock Access Check Medium Failed
007564470903
111111111111 Global BR-14 Stale Bedrock Access Check Medium Failed
007564470903
111111111111 Global BR-14 Stale Bedrock Access Check Medium Failed
007564470903
111111111111 Global BR-14 Stale Bedrock Access Check Medium Failed
007564470903
111111111111 Global BR-14 Stale Bedrock Access Check Medium Failed
007564470903
111111111111 Global BR-14 Stale Bedrock Access Check Medium Failed
007564470903
111111111111 Global BR-14 Stale Bedrock Access CheckRole 'cdk-hnb659fds-lookup-role-007564470903-us-east-1' last accessed Bedrock on neverRole 'cdk-hnb659fds-lookup-role-111111111111-us-east-1' last accessed Bedrock on never You can use last accessed information to refine your policies and allow access to only the services and actions that your IAM identities and policies use. This helps you to better adhere to the best practice of least privilege. Medium Failed
007564470903
111111111111 Global BR-14 Stale Bedrock Access Check Medium Failed
007564470903
111111111111 Global BR-14 Stale Bedrock Access Check Medium Failed
007564470903
111111111111 Global BR-14 Stale Bedrock Access Check Medium Failed
007564470903
111111111111 Global BR-14 Stale Bedrock Access Check Medium Failed
007564470903
111111111111 Global BR-14 Stale Bedrock Access Check Medium Failed
007564470903
111111111111 Global BR-14 Stale Bedrock Access Check Medium Failed
007564470903
111111111111 Global BR-14 Stale Bedrock Access Check Medium Failed
007564470903
111111111111 Global BR-14 Stale Bedrock Access Check Medium Failed
007564470903
111111111111 Global BR-14 Stale Bedrock Access Check Medium Failed
007564470903
111111111111 Global BR-14 Stale Bedrock Access Check Medium Failed
007564470903
111111111111 us-east-1 BR-02 Amazon Bedrock private connectivity check Informational N/A
007564470903
111111111111 us-east-1 BR-04 Bedrock Model Invocation Logging Check Informational N/A
007564470903
111111111111 us-east-1 BR-05 Bedrock Guardrails Check Informational N/A
007564470903
111111111111 us-east-1 BR-06 Bedrock CloudTrail Logging Check Informational N/A
007564470903
111111111111 us-east-1 BR-07 Bedrock Prompt Management Check Informational N/A
007564470903
111111111111 us-east-1 BR-08 Bedrock Agent IAM Roles Check Informational N/A
007564470903
111111111111 us-east-1 BR-09 Bedrock Knowledge Base Encryption CheckUnable to check Bedrock Knowledge Base API: An error occurred (AccessDeniedException) when calling the ListKnowledgeBases operation: User: arn:aws:sts::007564470903:assumed-role/aiml-sec-007564470903-BedrockSecurityAssessmentFunc-188U9EAkRKkw/aiml-security-aiml-sec-007564470903-BedrockAssessment is not authorized to perform: bedrock:ListKnowledgeBases on resource: arn:aws:bedrock:us-east-1:007564470903:knowledge-base/* because no identity-based policy allows the bedrock:ListKnowledgeBases actionUnable to check Bedrock Knowledge Base API: An error occurred (AccessDeniedException) when calling the ListKnowledgeBases operation: User: arn:aws:sts::111111111111:assumed-role/aiml-sec-111111111111-BedrockSecurityAssessmentFunc-188U9EAkRKkw/aiml-security-aiml-sec-111111111111-BedrockAssessment is not authorized to perform: bedrock:ListKnowledgeBases on resource: arn:aws:bedrock:us-east-1:111111111111:knowledge-base/* because no identity-based policy allows the bedrock:ListKnowledgeBases action Verify your AWS credentials and permissions to access Bedrock Knowledge Bases, then retry the assessment. Informational N/A
007564470903
111111111111 us-east-1 BR-10 Bedrock Guardrail IAM Enforcement Check Informational N/A
007564470903
111111111111 us-east-1 BR-11 Bedrock Custom Model Encryption Check Informational N/A
007564470903
111111111111 us-east-1 BR-12 Bedrock Invocation Log Encryption Check Informational N/A
007564470903
111111111111 us-east-1 BR-13 Bedrock Flows Guardrails Check Informational N/A
007564470903
111111111111 ap-southeast-2 SM-01 SageMaker Internet Access Check Informational N/A
007564470903
111111111111 ap-southeast-2 SM-02 SageMaker SSO Configuration Check Medium Passed
007564470903
111111111111 ap-southeast-2 SM-03 Data Protection Check Informational N/A
007564470903
111111111111 ap-southeast-2 SM-04 GuardDuty Enabled Medium Passed
007564470903
111111111111 ap-southeast-2 SM-05 SageMaker Model Registry Issue Informational N/A
007564470903
111111111111 ap-southeast-2 SM-05 SageMaker Feature Store Issue Informational N/A
007564470903
111111111111 ap-southeast-2 SM-05 SageMaker Pipelines Issue Informational N/A
007564470903
111111111111 ap-southeast-2 SM-06 SageMaker Clarify No Clarify Usage Informational N/A
007564470903
111111111111 ap-southeast-2 SM-07 SageMaker Model Monitor No Model Monitoring Informational N/A
007564470903
111111111111 ap-southeast-2 SM-08 Model Registry Registry Not Used Informational N/A
007564470903
111111111111 ap-southeast-2 SM-09 SageMaker Notebook Root Access Check Informational N/A
007564470903
111111111111 ap-southeast-2 SM-10 SageMaker Notebook VPC Deployment Check Informational N/A
007564470903
111111111111 ap-southeast-2 SM-11 SageMaker Model Network Isolation Check Informational N/A
007564470903
111111111111 ap-southeast-2 SM-12 SageMaker Endpoint Instance Count Check Informational N/A
007564470903
111111111111 ap-southeast-2 SM-13 SageMaker Monitoring Network Isolation Check Informational N/A
007564470903
111111111111 ap-southeast-2 SM-14 SageMaker Model Repository Access Check Informational N/A
007564470903
111111111111 ap-southeast-2 SM-15 SageMaker Feature Store Encryption Check Informational N/A
007564470903
111111111111 ap-southeast-2 SM-16 SageMaker Data Quality Job Encryption Check Informational N/A
007564470903
111111111111 ap-southeast-2 SM-17 SageMaker Processing Job Encryption Check Informational N/A
007564470903
111111111111 ap-southeast-2 SM-18 SageMaker Transform Job Encryption Check Informational N/A
007564470903
111111111111 ap-southeast-2 SM-19 SageMaker Hyperparameter Tuning Job Encryption Check Informational N/A
007564470903
111111111111 ap-southeast-2 SM-20 SageMaker Compilation Job Encryption Check Informational N/A
007564470903
111111111111 ap-southeast-2 SM-21 SageMaker AutoML Job Network Isolation Check Informational N/A
007564470903
111111111111 ap-southeast-2 SM-22 Model Approval Workflow Check Informational N/A
007564470903
111111111111 ap-southeast-2 SM-23 Model Drift Detection Check Medium Passed
007564470903
111111111111 ap-southeast-2 SM-24 A/B Testing and Shadow Deployment Check Low Passed
007564470903
111111111111 ap-southeast-2 SM-25 ML Lineage Tracking - Experiments Not Used Informational N/A
007564470903
111111111111 eu-west-1 SM-01 SageMaker Internet Access Check Informational N/A
007564470903
111111111111 eu-west-1 SM-02 SageMaker SSO Configuration Check Medium Passed
007564470903
111111111111 eu-west-1 SM-03 Data Protection Check Informational N/A
007564470903
111111111111 eu-west-1 SM-04 GuardDuty Enabled Medium Passed
007564470903
111111111111 eu-west-1 SM-05 SageMaker Model Registry Issue Informational N/A
007564470903
111111111111 eu-west-1 SM-05 SageMaker Feature Store Issue Informational N/A
007564470903
111111111111 eu-west-1 SM-05 SageMaker Pipelines Issue Informational N/A
007564470903
111111111111 eu-west-1 SM-06 SageMaker Clarify No Clarify Usage Informational N/A
007564470903
111111111111 eu-west-1 SM-07 SageMaker Model Monitor No Model Monitoring Informational N/A
007564470903
111111111111 eu-west-1 SM-08 Model Registry Registry Not Used Informational N/A
007564470903
111111111111 eu-west-1 SM-09 SageMaker Notebook Root Access Check Informational N/A
007564470903
111111111111 eu-west-1 SM-10 SageMaker Notebook VPC Deployment Check Informational N/A
007564470903
111111111111 eu-west-1 SM-11 SageMaker Model Network Isolation Check Informational N/A
007564470903
111111111111 eu-west-1 SM-12 SageMaker Endpoint Instance Count Check Informational N/A
007564470903
111111111111 eu-west-1 SM-13 SageMaker Monitoring Network Isolation Check Informational N/A
007564470903
111111111111 eu-west-1 SM-14 SageMaker Model Repository Access Check Informational N/A
007564470903
111111111111 eu-west-1 SM-15 SageMaker Feature Store Encryption Check Informational N/A
007564470903
111111111111 eu-west-1 SM-16 SageMaker Data Quality Job Encryption Check Informational N/A
007564470903
111111111111 eu-west-1 SM-17 SageMaker Processing Job Encryption Check Informational N/A
007564470903
111111111111 eu-west-1 SM-18 SageMaker Transform Job Encryption Check Informational N/A
007564470903
111111111111 eu-west-1 SM-19 SageMaker Hyperparameter Tuning Job Encryption Check Informational N/A
007564470903
111111111111 eu-west-1 SM-20 SageMaker Compilation Job Encryption Check Informational N/A
007564470903
111111111111 eu-west-1 SM-21 SageMaker AutoML Job Network Isolation Check Informational N/A
007564470903
111111111111 eu-west-1 SM-22 Model Approval Workflow Check Informational N/A
007564470903
111111111111 eu-west-1 SM-23 Model Drift Detection Check Medium Passed
007564470903
111111111111 eu-west-1 SM-24 A/B Testing and Shadow Deployment Check Low Passed
007564470903
111111111111 eu-west-1 SM-25 ML Lineage Tracking - Experiments Not Used Informational N/A
007564470903
111111111111 Global SM-02 SageMaker IAM Permissions Check High Passed
007564470903
111111111111 us-east-1 SM-01 SageMaker Internet Access Check Informational N/A
007564470903
111111111111 us-east-1 SM-02 SageMaker SSO Configuration Check Medium Passed
007564470903
111111111111 us-east-1 SM-03 Data Protection Check Informational N/A
007564470903
111111111111 us-east-1 SM-04 GuardDuty Enabled Medium Passed
007564470903
111111111111 us-east-1 SM-05 SageMaker Model Registry Issue Informational N/A
007564470903
111111111111 us-east-1 SM-05 SageMaker Feature Store Issue Informational N/A
007564470903
111111111111 us-east-1 SM-05 SageMaker Pipelines Issue Informational N/A
007564470903
111111111111 us-east-1 SM-06 SageMaker Clarify No Clarify Usage Informational N/A
007564470903
111111111111 us-east-1 SM-07 SageMaker Model Monitor No Model Monitoring Informational N/A
007564470903
111111111111 us-east-1 SM-08 Model Registry Registry Not Used Informational N/A
007564470903
111111111111 us-east-1 SM-09 SageMaker Notebook Root Access Check Informational N/A
007564470903
111111111111 us-east-1 SM-10 SageMaker Notebook VPC Deployment Check Informational N/A
007564470903
111111111111 us-east-1 SM-11 SageMaker Model Network Isolation Check Informational N/A
007564470903
111111111111 us-east-1 SM-12 SageMaker Endpoint Instance Count Check Informational N/A
007564470903
111111111111 us-east-1 SM-13 SageMaker Monitoring Network Isolation Check Informational N/A
007564470903
111111111111 us-east-1 SM-14 SageMaker Model Repository Access Check Informational N/A
007564470903
111111111111 us-east-1 SM-15 SageMaker Feature Store Encryption Check Informational N/A
007564470903
111111111111 us-east-1 SM-16 SageMaker Data Quality Job Encryption Check Informational N/A
007564470903
111111111111 us-east-1 SM-17 SageMaker Processing Job Encryption Check Informational N/A
007564470903
111111111111 us-east-1 SM-18 SageMaker Transform Job Encryption Check Informational N/A
007564470903
111111111111 us-east-1 SM-19 SageMaker Hyperparameter Tuning Job Encryption Check Informational N/A
007564470903
111111111111 us-east-1 SM-20 SageMaker Compilation Job Encryption Check Informational N/A
007564470903
111111111111 us-east-1 SM-21 SageMaker AutoML Job Network Isolation Check Informational N/A
007564470903
111111111111 us-east-1 SM-22 Model Approval Workflow Check Informational N/A
007564470903
111111111111 us-east-1 SM-23 Model Drift Detection Check Medium Passed
007564470903
111111111111 us-east-1 SM-24 A/B Testing and Shadow Deployment Check Low Passed
007564470903
111111111111 us-east-1 SM-25 ML Lineage Tracking - Experiments Not Used Informational N/A
007564470903
111111111111 ap-southeast-2 AC-01 AgentCore VPC Configuration Check Informational N/A
007564470903
111111111111 ap-southeast-2 AC-04 AgentCore Observability Check Informational N/A
007564470903
111111111111 ap-southeast-2 AC-05 AgentCore Encryption Check Informational N/A
007564470903
111111111111 ap-southeast-2 AC-06 AgentCore Browser Tool Recording Check Informational N/A
007564470903
111111111111 ap-southeast-2 AC-07 AgentCore Memory Configuration Check Informational N/A
007564470903
111111111111 ap-southeast-2 AC-13 AgentCore Gateway Configuration Check Informational N/A
007564470903
111111111111 ap-southeast-2 AC-08 AgentCore VPC Endpoints Check Informational N/A
007564470903
111111111111 ap-southeast-2 AC-10 AgentCore Resource-Based Policies Check Informational N/A
007564470903
111111111111 ap-southeast-2 AC-11 AgentCore Policy Engine Encryption Check Informational N/A
007564470903
111111111111 ap-southeast-2 AC-12 AgentCore Gateway Encryption Check Informational N/A
007564470903
111111111111 eu-west-1 AC-01 AgentCore VPC Configuration Check Informational N/A
007564470903
111111111111 eu-west-1 AC-04 AgentCore Observability Check Informational N/A
007564470903
111111111111 eu-west-1 AC-05 AgentCore Encryption Check Informational N/A
007564470903
111111111111 eu-west-1 AC-06 AgentCore Browser Tool Recording Check Informational N/A
007564470903
111111111111 eu-west-1 AC-07 AgentCore Memory Configuration Check Informational N/A
007564470903
111111111111 eu-west-1 AC-13 AgentCore Gateway Configuration Check Informational N/A
007564470903
111111111111 eu-west-1 AC-08 AgentCore VPC Endpoints Check Informational N/A
007564470903
111111111111 eu-west-1 AC-10 AgentCore Resource-Based Policies Check Informational N/A
007564470903
111111111111 eu-west-1 AC-11 AgentCore Policy Engine Encryption Check Informational N/A
007564470903
111111111111 eu-west-1 AC-12 AgentCore Gateway Encryption Check Informational N/A
007564470903
111111111111 Global AC-02 AgentCore IAM Full Access Check High Passed
007564470903
111111111111 Global AC-03 AgentCore Unused Permissions Medium Failed
007564470903
111111111111 Global AC-09 AgentCore Service-Linked Role Missing Medium Failed
007564470903
111111111111 us-east-1 AC-01 AgentCore VPC Configuration Check Informational N/A
007564470903
111111111111 us-east-1 AC-04 AgentCore Observability Check Informational N/A
007564470903
111111111111 us-east-1 AC-05 AgentCore Encryption Check Informational N/A
007564470903
111111111111 us-east-1 AC-06 AgentCore Browser Tool Recording Check Informational N/A
007564470903
111111111111 us-east-1 AC-07 AgentCore Memory Configuration Check Informational N/A
007564470903
111111111111 us-east-1 AC-13 AgentCore Gateway Configuration Check Informational N/A
007564470903
111111111111 us-east-1 AC-08 AgentCore VPC Endpoints Check Informational N/A
007564470903
111111111111 us-east-1 AC-10 AgentCore Resource-Based Policies Check Informational N/A
007564470903
111111111111 us-east-1 AC-11 AgentCore Policy Engine Encryption Check Informational N/A
007564470903
111111111111 us-east-1 AC-12 AgentCore Gateway Encryption Check Informational N/A
007564470903
111111111111 us-east-1 FS-00 FinServ Regional Scope Not Applicable Informational N/A
007564470903
111111111111 eu-west-1 FS-00 FinServ Regional Scope Not Applicable Informational N/A
007564470903
111111111111 ap-southeast-2 FS-00 FinServ Regional Scope Not Applicable
- +
Account IDRegionCheck IDFindingDetailsResolutionReferenceSeverityStatus
007564470903
+ @@ -2221,8 +2221,8 @@

Informational

- - + + @@ -2232,8 +2232,8 @@

Informational

- - + + @@ -2243,8 +2243,8 @@

Informational

- - + + @@ -2254,8 +2254,8 @@

Informational

- - + + @@ -2269,8 +2269,8 @@

Informational

- - + + @@ -2280,19 +2280,19 @@

Informational

- - + + - + - - + + @@ -2302,8 +2302,8 @@

Informational

- - + + @@ -2313,8 +2313,8 @@

Informational

- - + + @@ -2324,8 +2324,8 @@

Informational

- - + + @@ -2335,8 +2335,8 @@

Informational

- - + + @@ -2346,8 +2346,8 @@

Informational

- - + + @@ -2357,8 +2357,8 @@

Informational

- - + + @@ -2368,8 +2368,8 @@

Informational

- - + + @@ -2379,8 +2379,8 @@

Informational

- - + + @@ -2394,8 +2394,8 @@

Informational

- - + + @@ -2405,19 +2405,19 @@

Informational

- - + + - + - - + + @@ -2427,8 +2427,8 @@

Informational

- - + + @@ -2438,8 +2438,8 @@

Informational

- - + + @@ -2449,8 +2449,8 @@

Informational

- - + + @@ -2460,8 +2460,8 @@

Informational

- - + + @@ -2472,8 +2472,8 @@

High

- - + + @@ -2483,8 +2483,8 @@

High

- - + + @@ -2494,8 +2494,8 @@

High

- - + + @@ -2505,8 +2505,8 @@

High

- - + + @@ -2516,8 +2516,8 @@

Medium

- - + + @@ -2527,8 +2527,8 @@

Medium

- - + + @@ -2538,8 +2538,8 @@

Medium

- - + + @@ -2549,8 +2549,8 @@

Medium

- - + + @@ -2560,8 +2560,8 @@

Medium

- - + + @@ -2571,19 +2571,19 @@

Medium

- - + + - + - - + + @@ -2593,8 +2593,8 @@

Medium

- - + + @@ -2604,8 +2604,8 @@

Medium

- - + + @@ -2615,8 +2615,8 @@

Medium

- - + + @@ -2626,8 +2626,8 @@

Medium

- - + + @@ -2637,8 +2637,8 @@

Medium

- - + + @@ -2648,8 +2648,8 @@

Medium

- - + + @@ -2659,8 +2659,8 @@

Medium

- - + + @@ -2670,8 +2670,8 @@

Medium

- - + + @@ -2681,8 +2681,8 @@

Medium

- - + + @@ -2692,8 +2692,8 @@

Medium

- - + + @@ -2703,8 +2703,8 @@

Informational

- - + + @@ -2714,8 +2714,8 @@

Informational

- - + + @@ -2725,8 +2725,8 @@

Informational

- - + + @@ -2736,8 +2736,8 @@

Informational

- - + + @@ -2751,8 +2751,8 @@

Informational

- - + + @@ -2762,19 +2762,19 @@

Informational

- - + + - + - - + + @@ -2784,8 +2784,8 @@

Informational

- - + + @@ -2795,8 +2795,8 @@

Informational

- - + + @@ -2806,8 +2806,8 @@

Informational

- - + + @@ -2828,8 +2828,8 @@

-

Account IDRegionCheck IDFindingDetailsResolutionReferenceSeverityStatus
111111111111 ap-southeast-2 BR-02 Amazon Bedrock private connectivity checkN/A
007564470903
111111111111 ap-southeast-2 BR-04 Bedrock Model Invocation Logging CheckN/A
007564470903
111111111111 ap-southeast-2 BR-05 Bedrock Guardrails CheckN/A
007564470903
111111111111 ap-southeast-2 BR-06 Bedrock CloudTrail Logging CheckN/A
007564470903
111111111111 ap-southeast-2 BR-07 Bedrock Prompt Management CheckN/A
007564470903
111111111111 ap-southeast-2 BR-08 Bedrock Agent IAM Roles CheckN/A
007564470903
111111111111 ap-southeast-2 BR-09 Bedrock Knowledge Base Encryption CheckUnable to check Bedrock Knowledge Base API: An error occurred (AccessDeniedException) when calling the ListKnowledgeBases operation: User: arn:aws:sts::007564470903:assumed-role/aiml-sec-007564470903-BedrockSecurityAssessmentFunc-188U9EAkRKkw/aiml-security-aiml-sec-007564470903-BedrockAssessment is not authorized to perform: bedrock:ListKnowledgeBases on resource: arn:aws:bedrock:ap-southeast-2:007564470903:knowledge-base/* because no identity-based policy allows the bedrock:ListKnowledgeBases actionUnable to check Bedrock Knowledge Base API: An error occurred (AccessDeniedException) when calling the ListKnowledgeBases operation: User: arn:aws:sts::111111111111:assumed-role/aiml-sec-111111111111-BedrockSecurityAssessmentFunc-188U9EAkRKkw/aiml-security-aiml-sec-111111111111-BedrockAssessment is not authorized to perform: bedrock:ListKnowledgeBases on resource: arn:aws:bedrock:ap-southeast-2:111111111111:knowledge-base/* because no identity-based policy allows the bedrock:ListKnowledgeBases action Verify your AWS credentials and permissions to access Bedrock Knowledge Bases, then retry the assessment. Informational N/A
007564470903
111111111111 ap-southeast-2 BR-10 Bedrock Guardrail IAM Enforcement CheckN/A
007564470903
111111111111 ap-southeast-2 BR-11 Bedrock Custom Model Encryption CheckN/A
007564470903
111111111111 ap-southeast-2 BR-12 Bedrock Invocation Log Encryption CheckN/A
007564470903
111111111111 ap-southeast-2 BR-13 Bedrock Flows Guardrails CheckN/A
007564470903
111111111111 eu-west-1 BR-02 Amazon Bedrock private connectivity checkN/A
007564470903
111111111111 eu-west-1 BR-04 Bedrock Model Invocation Logging CheckN/A
007564470903
111111111111 eu-west-1 BR-05 Bedrock Guardrails CheckN/A
007564470903
111111111111 eu-west-1 BR-06 Bedrock CloudTrail Logging CheckN/A
007564470903
111111111111 eu-west-1 BR-07 Bedrock Prompt Management CheckN/A
007564470903
111111111111 eu-west-1 BR-08 Bedrock Agent IAM Roles CheckN/A
007564470903
111111111111 eu-west-1 BR-09 Bedrock Knowledge Base Encryption CheckUnable to check Bedrock Knowledge Base API: An error occurred (AccessDeniedException) when calling the ListKnowledgeBases operation: User: arn:aws:sts::007564470903:assumed-role/aiml-sec-007564470903-BedrockSecurityAssessmentFunc-188U9EAkRKkw/aiml-security-aiml-sec-007564470903-BedrockAssessment is not authorized to perform: bedrock:ListKnowledgeBases on resource: arn:aws:bedrock:eu-west-1:007564470903:knowledge-base/* because no identity-based policy allows the bedrock:ListKnowledgeBases actionUnable to check Bedrock Knowledge Base API: An error occurred (AccessDeniedException) when calling the ListKnowledgeBases operation: User: arn:aws:sts::111111111111:assumed-role/aiml-sec-111111111111-BedrockSecurityAssessmentFunc-188U9EAkRKkw/aiml-security-aiml-sec-111111111111-BedrockAssessment is not authorized to perform: bedrock:ListKnowledgeBases on resource: arn:aws:bedrock:eu-west-1:111111111111:knowledge-base/* because no identity-based policy allows the bedrock:ListKnowledgeBases action Verify your AWS credentials and permissions to access Bedrock Knowledge Bases, then retry the assessment. Informational N/A
007564470903
111111111111 eu-west-1 BR-10 Bedrock Guardrail IAM Enforcement CheckN/A
007564470903
111111111111 eu-west-1 BR-11 Bedrock Custom Model Encryption CheckN/A
007564470903
111111111111 eu-west-1 BR-12 Bedrock Invocation Log Encryption CheckN/A
007564470903
111111111111 eu-west-1 BR-13 Bedrock Flows Guardrails CheckN/A
007564470903
111111111111 Global BR-01 AmazonBedrockFullAccess role checkPassed
007564470903
111111111111 Global BR-03 Marketplace Subscription Access CheckFailed
007564470903
111111111111 Global BR-03 Marketplace Subscription Access CheckFailed
007564470903
111111111111 Global BR-03 Marketplace Subscription Access CheckFailed
007564470903
111111111111 Global BR-14 Stale Bedrock Access CheckFailed
007564470903
111111111111 Global BR-14 Stale Bedrock Access CheckFailed
007564470903
111111111111 Global BR-14 Stale Bedrock Access CheckFailed
007564470903
111111111111 Global BR-14 Stale Bedrock Access CheckFailed
007564470903
111111111111 Global BR-14 Stale Bedrock Access CheckFailed
007564470903
111111111111 Global BR-14 Stale Bedrock Access CheckFailed
007564470903
111111111111 Global BR-14 Stale Bedrock Access CheckRole 'cdk-hnb659fds-lookup-role-007564470903-us-east-1' last accessed Bedrock on neverRole 'cdk-hnb659fds-lookup-role-111111111111-us-east-1' last accessed Bedrock on never You can use last accessed information to refine your policies and allow access to only the services and actions that your IAM identities and policies use. This helps you to better adhere to the best practice of least privilege. Medium Failed
007564470903
111111111111 Global BR-14 Stale Bedrock Access CheckFailed
007564470903
111111111111 Global BR-14 Stale Bedrock Access CheckFailed
007564470903
111111111111 Global BR-14 Stale Bedrock Access CheckFailed
007564470903
111111111111 Global BR-14 Stale Bedrock Access CheckFailed
007564470903
111111111111 Global BR-14 Stale Bedrock Access CheckFailed
007564470903
111111111111 Global BR-14 Stale Bedrock Access CheckFailed
007564470903
111111111111 Global BR-14 Stale Bedrock Access CheckFailed
007564470903
111111111111 Global BR-14 Stale Bedrock Access CheckFailed
007564470903
111111111111 Global BR-14 Stale Bedrock Access CheckFailed
007564470903
111111111111 Global BR-14 Stale Bedrock Access CheckFailed
007564470903
111111111111 us-east-1 BR-02 Amazon Bedrock private connectivity checkN/A
007564470903
111111111111 us-east-1 BR-04 Bedrock Model Invocation Logging CheckN/A
007564470903
111111111111 us-east-1 BR-05 Bedrock Guardrails CheckN/A
007564470903
111111111111 us-east-1 BR-06 Bedrock CloudTrail Logging CheckN/A
007564470903
111111111111 us-east-1 BR-07 Bedrock Prompt Management CheckN/A
007564470903
111111111111 us-east-1 BR-08 Bedrock Agent IAM Roles CheckN/A
007564470903
111111111111 us-east-1 BR-09 Bedrock Knowledge Base Encryption CheckUnable to check Bedrock Knowledge Base API: An error occurred (AccessDeniedException) when calling the ListKnowledgeBases operation: User: arn:aws:sts::007564470903:assumed-role/aiml-sec-007564470903-BedrockSecurityAssessmentFunc-188U9EAkRKkw/aiml-security-aiml-sec-007564470903-BedrockAssessment is not authorized to perform: bedrock:ListKnowledgeBases on resource: arn:aws:bedrock:us-east-1:007564470903:knowledge-base/* because no identity-based policy allows the bedrock:ListKnowledgeBases actionUnable to check Bedrock Knowledge Base API: An error occurred (AccessDeniedException) when calling the ListKnowledgeBases operation: User: arn:aws:sts::111111111111:assumed-role/aiml-sec-111111111111-BedrockSecurityAssessmentFunc-188U9EAkRKkw/aiml-security-aiml-sec-111111111111-BedrockAssessment is not authorized to perform: bedrock:ListKnowledgeBases on resource: arn:aws:bedrock:us-east-1:111111111111:knowledge-base/* because no identity-based policy allows the bedrock:ListKnowledgeBases action Verify your AWS credentials and permissions to access Bedrock Knowledge Bases, then retry the assessment. Informational N/A
007564470903
111111111111 us-east-1 BR-10 Bedrock Guardrail IAM Enforcement CheckN/A
007564470903
111111111111 us-east-1 BR-11 Bedrock Custom Model Encryption CheckN/A
007564470903
111111111111 us-east-1 BR-12 Bedrock Invocation Log Encryption CheckN/A
007564470903
111111111111 us-east-1 BR-13 Bedrock Flows Guardrails Check
- +
Account IDRegionCheck IDFindingDetailsResolutionReferenceSeverityStatus
007564470903
+ @@ -2839,8 +2839,8 @@

Informational

- - + + @@ -2850,8 +2850,8 @@

Medium

- - + + @@ -2861,8 +2861,8 @@

Informational

- - + + @@ -2872,8 +2872,8 @@

Medium

- - + + @@ -2883,8 +2883,8 @@

Informational

- - + + @@ -2894,8 +2894,8 @@

Informational

- - + + @@ -2905,8 +2905,8 @@

Informational

- - + + @@ -2916,8 +2916,8 @@

Informational

- - + + @@ -2927,8 +2927,8 @@

Informational

- - + + @@ -2938,8 +2938,8 @@

Informational

- - + + @@ -2949,8 +2949,8 @@

Informational

- - + + @@ -2960,8 +2960,8 @@

Informational

- - + + @@ -2971,8 +2971,8 @@

Informational

- - + + @@ -2982,8 +2982,8 @@

Informational

- - + + @@ -2993,8 +2993,8 @@

Informational

- - + + @@ -3004,8 +3004,8 @@

Informational

- - + + @@ -3015,8 +3015,8 @@

Informational

- - + + @@ -3026,8 +3026,8 @@

Informational

- - + + @@ -3037,8 +3037,8 @@

Informational

- - + + @@ -3048,8 +3048,8 @@

Informational

- - + + @@ -3059,8 +3059,8 @@

Informational

- - + + @@ -3070,8 +3070,8 @@

Informational

- - + + @@ -3081,8 +3081,8 @@

Informational

- - + + @@ -3092,8 +3092,8 @@

Informational

- - + + @@ -3103,8 +3103,8 @@

Medium

- - + + @@ -3114,8 +3114,8 @@

Low

- - + + @@ -3125,8 +3125,8 @@

Informational

- - + + @@ -3136,8 +3136,8 @@

Informational

- - + + @@ -3147,8 +3147,8 @@

Medium

- - + + @@ -3158,8 +3158,8 @@

Informational

- - + + @@ -3169,8 +3169,8 @@

Medium

- - + + @@ -3180,8 +3180,8 @@

Informational

- - + + @@ -3191,8 +3191,8 @@

Informational

- - + + @@ -3202,8 +3202,8 @@

Informational

- - + + @@ -3213,8 +3213,8 @@

Informational

- - + + @@ -3224,8 +3224,8 @@

Informational

- - + + @@ -3235,8 +3235,8 @@

Informational

- - + + @@ -3246,8 +3246,8 @@

Informational

- - + + @@ -3257,8 +3257,8 @@

Informational

- - + + @@ -3268,8 +3268,8 @@

Informational

- - + + @@ -3279,8 +3279,8 @@

Informational

- - + + @@ -3290,8 +3290,8 @@

Informational

- - + + @@ -3301,8 +3301,8 @@

Informational

- - + + @@ -3312,8 +3312,8 @@

Informational

- - + + @@ -3323,8 +3323,8 @@

Informational

- - + + @@ -3334,8 +3334,8 @@

Informational

- - + + @@ -3345,8 +3345,8 @@

Informational

- - + + @@ -3356,8 +3356,8 @@

Informational

- - + + @@ -3367,8 +3367,8 @@

Informational

- - + + @@ -3378,8 +3378,8 @@

Informational

- - + + @@ -3389,8 +3389,8 @@

Informational

- - + + @@ -3400,8 +3400,8 @@

Medium

- - + + @@ -3411,8 +3411,8 @@

Low

- - + + @@ -3422,8 +3422,8 @@

Informational

- - + + @@ -3433,8 +3433,8 @@

High

- - + + @@ -3444,8 +3444,8 @@

Informational

- - + + @@ -3455,8 +3455,8 @@

Medium

- - + + @@ -3466,8 +3466,8 @@

Informational

- - + + @@ -3477,8 +3477,8 @@

Medium

- - + + @@ -3488,8 +3488,8 @@

Informational

- - + + @@ -3499,8 +3499,8 @@

Informational

- - + + @@ -3510,8 +3510,8 @@

Informational

- - + + @@ -3521,8 +3521,8 @@

Informational

- - + + @@ -3532,8 +3532,8 @@

Informational

- - + + @@ -3543,8 +3543,8 @@

Informational

- - + + @@ -3554,8 +3554,8 @@

Informational

- - + + @@ -3565,8 +3565,8 @@

Informational

- - + + @@ -3576,8 +3576,8 @@

Informational

- - + + @@ -3587,8 +3587,8 @@

Informational

- - + + @@ -3598,8 +3598,8 @@

Informational

- - + + @@ -3609,8 +3609,8 @@

Informational

- - + + @@ -3620,8 +3620,8 @@

Informational

- - + + @@ -3631,8 +3631,8 @@

Informational

- - + + @@ -3642,8 +3642,8 @@

Informational

- - + + @@ -3653,8 +3653,8 @@

Informational

- - + + @@ -3664,8 +3664,8 @@

Informational

- - + + @@ -3675,8 +3675,8 @@

Informational

- - + + @@ -3686,8 +3686,8 @@

Informational

- - + + @@ -3697,8 +3697,8 @@

Informational

- - + + @@ -3708,8 +3708,8 @@

Medium

- - + + @@ -3719,8 +3719,8 @@

Low

- - + + @@ -3741,8 +3741,8 @@

-

Account IDRegionCheck IDFindingDetailsResolutionReferenceSeverityStatus
111111111111 ap-southeast-2 SM-01 SageMaker Internet Access CheckN/A
007564470903
111111111111 ap-southeast-2 SM-02 SageMaker SSO Configuration CheckPassed
007564470903
111111111111 ap-southeast-2 SM-03 Data Protection CheckN/A
007564470903
111111111111 ap-southeast-2 SM-04 GuardDuty EnabledPassed
007564470903
111111111111 ap-southeast-2 SM-05 SageMaker Model Registry IssueN/A
007564470903
111111111111 ap-southeast-2 SM-05 SageMaker Feature Store IssueN/A
007564470903
111111111111 ap-southeast-2 SM-05 SageMaker Pipelines IssueN/A
007564470903
111111111111 ap-southeast-2 SM-06 SageMaker Clarify No Clarify UsageN/A
007564470903
111111111111 ap-southeast-2 SM-07 SageMaker Model Monitor No Model MonitoringN/A
007564470903
111111111111 ap-southeast-2 SM-08 Model Registry Registry Not UsedN/A
007564470903
111111111111 ap-southeast-2 SM-09 SageMaker Notebook Root Access CheckN/A
007564470903
111111111111 ap-southeast-2 SM-10 SageMaker Notebook VPC Deployment CheckN/A
007564470903
111111111111 ap-southeast-2 SM-11 SageMaker Model Network Isolation CheckN/A
007564470903
111111111111 ap-southeast-2 SM-12 SageMaker Endpoint Instance Count CheckN/A
007564470903
111111111111 ap-southeast-2 SM-13 SageMaker Monitoring Network Isolation CheckN/A
007564470903
111111111111 ap-southeast-2 SM-14 SageMaker Model Repository Access CheckN/A
007564470903
111111111111 ap-southeast-2 SM-15 SageMaker Feature Store Encryption CheckN/A
007564470903
111111111111 ap-southeast-2 SM-16 SageMaker Data Quality Job Encryption CheckN/A
007564470903
111111111111 ap-southeast-2 SM-17 SageMaker Processing Job Encryption CheckN/A
007564470903
111111111111 ap-southeast-2 SM-18 SageMaker Transform Job Encryption CheckN/A
007564470903
111111111111 ap-southeast-2 SM-19 SageMaker Hyperparameter Tuning Job Encryption CheckN/A
007564470903
111111111111 ap-southeast-2 SM-20 SageMaker Compilation Job Encryption CheckN/A
007564470903
111111111111 ap-southeast-2 SM-21 SageMaker AutoML Job Network Isolation CheckN/A
007564470903
111111111111 ap-southeast-2 SM-22 Model Approval Workflow CheckN/A
007564470903
111111111111 ap-southeast-2 SM-23 Model Drift Detection CheckPassed
007564470903
111111111111 ap-southeast-2 SM-24 A/B Testing and Shadow Deployment CheckPassed
007564470903
111111111111 ap-southeast-2 SM-25 ML Lineage Tracking - Experiments Not UsedN/A
007564470903
111111111111 eu-west-1 SM-01 SageMaker Internet Access CheckN/A
007564470903
111111111111 eu-west-1 SM-02 SageMaker SSO Configuration CheckPassed
007564470903
111111111111 eu-west-1 SM-03 Data Protection CheckN/A
007564470903
111111111111 eu-west-1 SM-04 GuardDuty EnabledPassed
007564470903
111111111111 eu-west-1 SM-05 SageMaker Model Registry IssueN/A
007564470903
111111111111 eu-west-1 SM-05 SageMaker Feature Store IssueN/A
007564470903
111111111111 eu-west-1 SM-05 SageMaker Pipelines IssueN/A
007564470903
111111111111 eu-west-1 SM-06 SageMaker Clarify No Clarify UsageN/A
007564470903
111111111111 eu-west-1 SM-07 SageMaker Model Monitor No Model MonitoringN/A
007564470903
111111111111 eu-west-1 SM-08 Model Registry Registry Not UsedN/A
007564470903
111111111111 eu-west-1 SM-09 SageMaker Notebook Root Access CheckN/A
007564470903
111111111111 eu-west-1 SM-10 SageMaker Notebook VPC Deployment CheckN/A
007564470903
111111111111 eu-west-1 SM-11 SageMaker Model Network Isolation CheckN/A
007564470903
111111111111 eu-west-1 SM-12 SageMaker Endpoint Instance Count CheckN/A
007564470903
111111111111 eu-west-1 SM-13 SageMaker Monitoring Network Isolation CheckN/A
007564470903
111111111111 eu-west-1 SM-14 SageMaker Model Repository Access CheckN/A
007564470903
111111111111 eu-west-1 SM-15 SageMaker Feature Store Encryption CheckN/A
007564470903
111111111111 eu-west-1 SM-16 SageMaker Data Quality Job Encryption CheckN/A
007564470903
111111111111 eu-west-1 SM-17 SageMaker Processing Job Encryption CheckN/A
007564470903
111111111111 eu-west-1 SM-18 SageMaker Transform Job Encryption CheckN/A
007564470903
111111111111 eu-west-1 SM-19 SageMaker Hyperparameter Tuning Job Encryption CheckN/A
007564470903
111111111111 eu-west-1 SM-20 SageMaker Compilation Job Encryption CheckN/A
007564470903
111111111111 eu-west-1 SM-21 SageMaker AutoML Job Network Isolation CheckN/A
007564470903
111111111111 eu-west-1 SM-22 Model Approval Workflow CheckN/A
007564470903
111111111111 eu-west-1 SM-23 Model Drift Detection CheckPassed
007564470903
111111111111 eu-west-1 SM-24 A/B Testing and Shadow Deployment CheckPassed
007564470903
111111111111 eu-west-1 SM-25 ML Lineage Tracking - Experiments Not UsedN/A
007564470903
111111111111 Global SM-02 SageMaker IAM Permissions CheckPassed
007564470903
111111111111 us-east-1 SM-01 SageMaker Internet Access CheckN/A
007564470903
111111111111 us-east-1 SM-02 SageMaker SSO Configuration CheckPassed
007564470903
111111111111 us-east-1 SM-03 Data Protection CheckN/A
007564470903
111111111111 us-east-1 SM-04 GuardDuty EnabledPassed
007564470903
111111111111 us-east-1 SM-05 SageMaker Model Registry IssueN/A
007564470903
111111111111 us-east-1 SM-05 SageMaker Feature Store IssueN/A
007564470903
111111111111 us-east-1 SM-05 SageMaker Pipelines IssueN/A
007564470903
111111111111 us-east-1 SM-06 SageMaker Clarify No Clarify UsageN/A
007564470903
111111111111 us-east-1 SM-07 SageMaker Model Monitor No Model MonitoringN/A
007564470903
111111111111 us-east-1 SM-08 Model Registry Registry Not UsedN/A
007564470903
111111111111 us-east-1 SM-09 SageMaker Notebook Root Access CheckN/A
007564470903
111111111111 us-east-1 SM-10 SageMaker Notebook VPC Deployment CheckN/A
007564470903
111111111111 us-east-1 SM-11 SageMaker Model Network Isolation CheckN/A
007564470903
111111111111 us-east-1 SM-12 SageMaker Endpoint Instance Count CheckN/A
007564470903
111111111111 us-east-1 SM-13 SageMaker Monitoring Network Isolation CheckN/A
007564470903
111111111111 us-east-1 SM-14 SageMaker Model Repository Access CheckN/A
007564470903
111111111111 us-east-1 SM-15 SageMaker Feature Store Encryption CheckN/A
007564470903
111111111111 us-east-1 SM-16 SageMaker Data Quality Job Encryption CheckN/A
007564470903
111111111111 us-east-1 SM-17 SageMaker Processing Job Encryption CheckN/A
007564470903
111111111111 us-east-1 SM-18 SageMaker Transform Job Encryption CheckN/A
007564470903
111111111111 us-east-1 SM-19 SageMaker Hyperparameter Tuning Job Encryption CheckN/A
007564470903
111111111111 us-east-1 SM-20 SageMaker Compilation Job Encryption CheckN/A
007564470903
111111111111 us-east-1 SM-21 SageMaker AutoML Job Network Isolation CheckN/A
007564470903
111111111111 us-east-1 SM-22 Model Approval Workflow CheckN/A
007564470903
111111111111 us-east-1 SM-23 Model Drift Detection CheckPassed
007564470903
111111111111 us-east-1 SM-24 A/B Testing and Shadow Deployment CheckPassed
007564470903
111111111111 us-east-1 SM-25 ML Lineage Tracking - Experiments Not Used
- +
Account IDRegionCheck IDFindingDetailsResolutionReferenceSeverityStatus
007564470903
+ @@ -3752,8 +3752,8 @@

Informational

- - + + @@ -3763,8 +3763,8 @@

Informational

- - + + @@ -3774,8 +3774,8 @@

Informational

- - + + @@ -3785,8 +3785,8 @@

Informational

- - + + @@ -3796,8 +3796,8 @@

Informational

- - + + @@ -3807,8 +3807,8 @@

Informational

- - + + @@ -3818,8 +3818,8 @@

Informational

- - + + @@ -3829,8 +3829,8 @@

Informational

- - + + @@ -3840,8 +3840,8 @@

Informational

- - + + @@ -3851,8 +3851,8 @@

Informational

- - + + @@ -3862,8 +3862,8 @@

Informational

- - + + @@ -3873,8 +3873,8 @@

Informational

- - + + @@ -3884,8 +3884,8 @@

Informational

- - + + @@ -3895,8 +3895,8 @@

Informational

- - + + @@ -3906,8 +3906,8 @@

Informational

- - + + @@ -3917,8 +3917,8 @@

Informational

- - + + @@ -3928,8 +3928,8 @@

Informational

- - + + @@ -3939,8 +3939,8 @@

Informational

- - + + @@ -3950,8 +3950,8 @@

Informational

- - + + @@ -3961,8 +3961,8 @@

Informational

- - + + @@ -3972,8 +3972,8 @@

High

- - + + @@ -3983,8 +3983,8 @@

Medium

- - + + @@ -3994,8 +3994,8 @@

Medium

- - + + @@ -4005,8 +4005,8 @@

Informational

- - + + @@ -4016,8 +4016,8 @@

Informational

- - + + @@ -4027,8 +4027,8 @@

Informational

- - + + @@ -4038,8 +4038,8 @@

Informational

- - + + @@ -4049,8 +4049,8 @@

Informational

- - + + @@ -4060,8 +4060,8 @@

Informational

- - + + @@ -4071,8 +4071,8 @@

Informational

- - + + @@ -4082,8 +4082,8 @@

Informational

- - + + @@ -4093,8 +4093,8 @@

Informational

- - + + @@ -4105,8 +4105,8 @@

N/A

Account IDRegionCheck IDFindingDetailsResolutionReferenceSeverityStatus
111111111111 ap-southeast-2 AC-01 AgentCore VPC Configuration CheckN/A
007564470903
111111111111 ap-southeast-2 AC-04 AgentCore Observability CheckN/A
007564470903
111111111111 ap-southeast-2 AC-05 AgentCore Encryption CheckN/A
007564470903
111111111111 ap-southeast-2 AC-06 AgentCore Browser Tool Recording CheckN/A
007564470903
111111111111 ap-southeast-2 AC-07 AgentCore Memory Configuration CheckN/A
007564470903
111111111111 ap-southeast-2 AC-13 AgentCore Gateway Configuration CheckN/A
007564470903
111111111111 ap-southeast-2 AC-08 AgentCore VPC Endpoints CheckN/A
007564470903
111111111111 ap-southeast-2 AC-10 AgentCore Resource-Based Policies CheckN/A
007564470903
111111111111 ap-southeast-2 AC-11 AgentCore Policy Engine Encryption CheckN/A
007564470903
111111111111 ap-southeast-2 AC-12 AgentCore Gateway Encryption CheckN/A
007564470903
111111111111 eu-west-1 AC-01 AgentCore VPC Configuration CheckN/A
007564470903
111111111111 eu-west-1 AC-04 AgentCore Observability CheckN/A
007564470903
111111111111 eu-west-1 AC-05 AgentCore Encryption CheckN/A
007564470903
111111111111 eu-west-1 AC-06 AgentCore Browser Tool Recording CheckN/A
007564470903
111111111111 eu-west-1 AC-07 AgentCore Memory Configuration CheckN/A
007564470903
111111111111 eu-west-1 AC-13 AgentCore Gateway Configuration CheckN/A
007564470903
111111111111 eu-west-1 AC-08 AgentCore VPC Endpoints CheckN/A
007564470903
111111111111 eu-west-1 AC-10 AgentCore Resource-Based Policies CheckN/A
007564470903
111111111111 eu-west-1 AC-11 AgentCore Policy Engine Encryption CheckN/A
007564470903
111111111111 eu-west-1 AC-12 AgentCore Gateway Encryption CheckN/A
007564470903
111111111111 Global AC-02 AgentCore IAM Full Access CheckPassed
007564470903
111111111111 Global AC-03 AgentCore Unused PermissionsFailed
007564470903
111111111111 Global AC-09 AgentCore Service-Linked Role MissingFailed
007564470903
111111111111 us-east-1 AC-01 AgentCore VPC Configuration CheckN/A
007564470903
111111111111 us-east-1 AC-04 AgentCore Observability CheckN/A
007564470903
111111111111 us-east-1 AC-05 AgentCore Encryption CheckN/A
007564470903
111111111111 us-east-1 AC-06 AgentCore Browser Tool Recording CheckN/A
007564470903
111111111111 us-east-1 AC-07 AgentCore Memory Configuration CheckN/A
007564470903
111111111111 us-east-1 AC-13 AgentCore Gateway Configuration CheckN/A
007564470903
111111111111 us-east-1 AC-08 AgentCore VPC Endpoints CheckN/A
007564470903
111111111111 us-east-1 AC-10 AgentCore Resource-Based Policies CheckN/A
007564470903
111111111111 us-east-1 AC-11 AgentCore Policy Engine Encryption CheckN/A
007564470903
111111111111 us-east-1 AC-12 AgentCore Gateway Encryption Check
-
Financial Services GenAI Risk Findings
Scope: this assessment records findings against each resolved CloudFormation TargetRegions entry. These checks are based on the AWS User Guide to Governance, Risk, and Compliance for Responsible AI Adoption. Severities follow a documented Likelihood × Impact methodology (see docs).
- +
Financial Services GenAI Risk Findings
Scope: this assessment records findings against each resolved CloudFormation TargetRegions entry. These checks are based on the AWS User Guide to Governance, Risk, and Compliance for Responsible AI Adoption. Severities follow a documented Likelihood × Impact methodology (see docs).
Account IDRegionCheck IDFindingDetailsResolutionReferenceSeverityStatus
007564470903
+ @@ -4116,8 +4116,8 @@

Informational

- - + + @@ -4127,8 +4127,8 @@

Informational

- - + + From 797fb5ff54bd54a91bdd05d7886baa629c2d4b6c Mon Sep 17 00:00:00 2001 From: Agasthi Kothurkar Date: Fri, 19 Jun 2026 23:09:25 -0400 Subject: [PATCH 21/30] Apply Ruff formatting --- .../security/agentcore_assessments/app.py | 45 ++- .../security/agentcore_assessments/schema.py | 41 ++- .../security/bedrock_assessments/app.py | 146 +++++++--- .../security/bedrock_assessments/schema.py | 41 ++- .../security/finserv_assessments/schema.py | 4 +- .../report_template.py | 27 +- .../generate_consolidated_report/schema.py | 39 ++- .../security/iam_permission_caching/app.py | 212 ++++++++------ .../security/iam_permission_caching/schema.py | 41 ++- .../functions/security/resolve_regions/app.py | 4 +- .../security/sagemaker_assessments/app.py | 272 ++++++++++++------ .../security/sagemaker_assessments/schema.py | 41 ++- consolidate_html_reports.py | 6 +- sample-reports/scripts/capture_screenshots.py | 22 +- tests/test_agentcore_checks.py | 20 +- tests/test_bedrock_checks.py | 85 +++--- tests/test_consolidated_report.py | 4 +- tests/test_sagemaker_checks.py | 43 ++- tests/test_sagemaker_template_permissions.py | 4 +- 19 files changed, 717 insertions(+), 380 deletions(-) diff --git a/aiml-security-assessment/functions/security/agentcore_assessments/app.py b/aiml-security-assessment/functions/security/agentcore_assessments/app.py index 1793a40..ec68256 100644 --- a/aiml-security-assessment/functions/security/agentcore_assessments/app.py +++ b/aiml-security-assessment/functions/security/agentcore_assessments/app.py @@ -179,7 +179,9 @@ def generate_csv_report(findings: List[Dict[str, Any]]) -> str: return csv_content -def write_to_s3(execution_id: str, csv_content: str, bucket_name: str, region: str = "") -> str: +def write_to_s3( + execution_id: str, csv_content: str, bucket_name: str, region: str = "" +) -> str: """ Upload CSV report to S3. @@ -2316,7 +2318,9 @@ def lambda_handler(event, context): ecr_client = boto3.client("ecr", config=boto3_config, region_name=region) logs_client = boto3.client("logs", config=boto3_config, region_name=region) xray_client = boto3.client("xray", config=boto3_config, region_name=region) - cloudwatch_client = boto3.client("cloudwatch", config=boto3_config, region_name=region) + cloudwatch_client = boto3.client( + "cloudwatch", config=boto3_config, region_name=region + ) # Collect all findings all_findings = [] @@ -2334,8 +2338,14 @@ def lambda_handler(event, context): # not available in the primary region. if is_primary_region: global_checks = [ - ("IAM Full Access", lambda: check_agentcore_full_access_roles(permission_cache)), - ("Stale Access", lambda: check_stale_agentcore_access(permission_cache)), + ( + "IAM Full Access", + lambda: check_agentcore_full_access_roles(permission_cache), + ), + ( + "Stale Access", + lambda: check_stale_agentcore_access(permission_cache), + ), # AC-09 inspects a global IAM service-linked role, so it is also # run once on the primary region rather than per scanned region. ("Service-Linked Role", check_agentcore_service_linked_role), @@ -2366,12 +2376,16 @@ def lambda_handler(event, context): # region's client if creation below fails. agentcore_client = None try: - agentcore_client = boto3.client("bedrock-agentcore-control", config=boto3_config, region_name=region) + agentcore_client = boto3.client( + "bedrock-agentcore-control", config=boto3_config, region_name=region + ) except Exception as e: # The client could not even be constructed (e.g. the SDK in this # runtime does not know the service). This is the one case where the # region genuinely cannot be assessed. - logger.warning(f"Failed to initialize bedrock-agentcore-control client: {e}") + logger.warning( + f"Failed to initialize bedrock-agentcore-control client: {e}" + ) agentcore_client = None if agentcore_client is not None: @@ -2380,17 +2394,23 @@ def lambda_handler(event, context): agentcore_client.list_agent_runtimes(maxResults=1) logger.info("Successfully initialized bedrock-agentcore-control client") except EndpointConnectionError: - logger.info(f"AgentCore service not available in region {region}, skipping") + logger.info( + f"AgentCore service not available in region {region}, skipping" + ) agentcore_client = None except ClientError as e: error_code = e.response.get("Error", {}).get("Code", "") if error_code in REGION_UNAVAILABLE_ERROR_CODES: - logger.info(f"AgentCore not accessible in region {region} ({error_code}), skipping") + logger.info( + f"AgentCore not accessible in region {region} ({error_code}), skipping" + ) agentcore_client = None else: # Service is reachable but returned another API error (e.g. access # denied) — proceed; individual checks handle their own errors. - logger.info(f"AgentCore client initialized (probe returned {error_code})") + logger.info( + f"AgentCore client initialized (probe returned {error_code})" + ) except Exception as e: # An unexpected probe failure (e.g. a boto3/botocore SDK param or # operation mismatch such as ParamValidationError/AttributeError) @@ -2425,7 +2445,12 @@ def lambda_handler(event, context): s3_url = write_to_s3(execution_id, csv_content, BUCKET_NAME, region=region) return { "statusCode": 200, - "body": json.dumps({"message": f"AgentCore not available in {region}", "s3_url": s3_url}), + "body": json.dumps( + { + "message": f"AgentCore not available in {region}", + "s3_url": s3_url, + } + ), } logger.info( diff --git a/aiml-security-assessment/functions/security/agentcore_assessments/schema.py b/aiml-security-assessment/functions/security/agentcore_assessments/schema.py index c2c4938..16a66d9 100644 --- a/aiml-security-assessment/functions/security/agentcore_assessments/schema.py +++ b/aiml-security-assessment/functions/security/agentcore_assessments/schema.py @@ -3,43 +3,59 @@ from pydantic import BaseModel, Field, field_validator import re + class SeverityEnum(str, Enum): HIGH = "High" MEDIUM = "Medium" LOW = "Low" INFORMATIONAL = "Informational" + class StatusEnum(str, Enum): FAILED = "Failed" PASSED = "Passed" NA = "N/A" + class Finding(BaseModel): """Represents a security finding with required fields and validations""" - Check_ID: str = Field(..., min_length=1, description="Unique check identifier (e.g., SM-01, BR-01, AC-01)") + + Check_ID: str = Field( + ..., + min_length=1, + description="Unique check identifier (e.g., SM-01, BR-01, AC-01)", + ) Finding: str = Field(..., min_length=1, description="The name/title of the finding") - Finding_Details: str = Field(..., min_length=1, description="Detailed description of the finding") - Resolution: str = Field(..., min_length=0, description="Steps to resolve the finding") + Finding_Details: str = Field( + ..., min_length=1, description="Detailed description of the finding" + ) + Resolution: str = Field( + ..., min_length=0, description="Steps to resolve the finding" + ) Reference: str = Field(..., description="Documentation reference URL") Severity: SeverityEnum = Field(..., description="Severity level of the finding") Status: StatusEnum = Field(..., description="Current status of the finding") - Region: str = Field(default="", description="AWS region where the finding was identified") + Region: str = Field( + default="", description="AWS region where the finding was identified" + ) @field_validator("Check_ID") @classmethod def validate_check_id(cls, v): """Validate that Check_ID follows the pattern XX-NN (e.g., SM-01, BR-14, AC-05)""" - pattern = r'^[A-Z]{2,3}-\d{2}$' + pattern = r"^[A-Z]{2,3}-\d{2}$" if not re.match(pattern, v): - raise ValueError('Check_ID must follow pattern XX-NN (e.g., SM-01, BR-14, AC-05)') + raise ValueError( + "Check_ID must follow pattern XX-NN (e.g., SM-01, BR-14, AC-05)" + ) return v @field_validator("Reference") @classmethod def validate_reference_url(cls, v): """Validate that reference URL starts with https://""" - if not str(v).startswith('https://'): - raise ValueError('Reference URL must start with https://') + if not str(v).startswith("https://"): + raise ValueError("Reference URL must start with https://") return v @field_validator("Severity") @@ -47,7 +63,7 @@ def validate_reference_url(cls, v): def validate_severity(cls, v): """Validate that severity is one of the allowed values""" if v not in SeverityEnum.__members__.values(): - raise ValueError('Severity must be one of the allowed values') + raise ValueError("Severity must be one of the allowed values") return v @field_validator("Status") @@ -55,9 +71,10 @@ def validate_severity(cls, v): def validate_status(cls, v): """Validate that status is one of the allowed values""" if v not in StatusEnum.__members__.values(): - raise ValueError('Status must be one of the allowed values') + raise ValueError("Status must be one of the allowed values") return v + def create_finding( check_id: str, finding_name: str, @@ -66,7 +83,7 @@ def create_finding( reference: str, severity: SeverityEnum, status: StatusEnum, - region: str = "" + region: str = "", ) -> Dict[str, Any]: """ Create a validated finding object @@ -95,6 +112,6 @@ def create_finding( Reference=reference, Severity=severity, Status=status, - Region=region + Region=region, ) return dict(finding.model_dump()) # Convert to regular dictionary diff --git a/aiml-security-assessment/functions/security/bedrock_assessments/app.py b/aiml-security-assessment/functions/security/bedrock_assessments/app.py index 70e3430..d30008f 100644 --- a/aiml-security-assessment/functions/security/bedrock_assessments/app.py +++ b/aiml-security-assessment/functions/security/bedrock_assessments/app.py @@ -93,7 +93,11 @@ def _probe_bedrock_resource_list(probe_label: str, probe_func) -> Optional[bool] ) return None - if code == "ValidationException" or "UnknownOperation" in error_text or "Unknown operation" in error_text: + if ( + code == "ValidationException" + or "UnknownOperation" in error_text + or "Unknown operation" in error_text + ): logger.info(f"{probe_label} API not available in this region") return False @@ -240,7 +244,9 @@ def get_permissions_cache(execution_id: str) -> Optional[Dict[str, Any]]: return None -def check_marketplace_subscription_access(permission_cache, region: str = "") -> Dict[str, Any]: +def check_marketplace_subscription_access( + permission_cache, region: str = "" +) -> Dict[str, Any]: logger.debug("Starting check for overly permissive Marketplace subscription access") try: findings = { @@ -618,7 +624,9 @@ def check_stale_bedrock_access(permission_cache, region: str = "") -> Dict[str, } -def check_bedrock_full_access_roles(permission_cache, region: str = "") -> Dict[str, Any]: +def check_bedrock_full_access_roles( + permission_cache, region: str = "" +) -> Dict[str, Any]: """ Check for roles with AmazonBedrockFullAccess policy using cached permissions """ @@ -837,7 +845,9 @@ def handle_aws_throttling(func, *args, **kwargs): raise -def check_bedrock_access_and_vpc_endpoints(permission_cache, region: str = "") -> Dict[str, Any]: +def check_bedrock_access_and_vpc_endpoints( + permission_cache, region: str = "" +) -> Dict[str, Any]: logger.debug("Starting check for Bedrock access and VPC endpoints") try: findings = { @@ -967,7 +977,9 @@ def check_bedrock_guardrails(region: str = "") -> Dict[str, Any]: "csv_data": [], } - bedrock_client = boto3.client("bedrock", config=boto3_config, region_name=region) + bedrock_client = boto3.client( + "bedrock", config=boto3_config, region_name=region + ) try: # List all guardrails @@ -993,7 +1005,9 @@ def check_bedrock_guardrails(region: str = "") -> Dict[str, Any]: ) ) else: - bedrock_footprint_found = detect_bedrock_regional_footprint(region=region) + bedrock_footprint_found = detect_bedrock_regional_footprint( + region=region + ) if bedrock_footprint_found is False: findings["details"] = "No regional Bedrock resources found" @@ -1100,7 +1114,9 @@ def check_bedrock_logging_configuration(region: str = "") -> Dict[str, Any]: ) return findings - bedrock_client = boto3.client("bedrock", config=boto3_config, region_name=region) + bedrock_client = boto3.client( + "bedrock", config=boto3_config, region_name=region + ) try: # Get current logging configuration @@ -1232,7 +1248,9 @@ def check_bedrock_cloudtrail_logging(region: str = "") -> Dict[str, Any]: ) return findings - cloudtrail_client = boto3.client("cloudtrail", config=boto3_config, region_name=region) + cloudtrail_client = boto3.client( + "cloudtrail", config=boto3_config, region_name=region + ) try: # Get all trails @@ -1379,7 +1397,9 @@ def check_bedrock_prompt_management(region: str = "") -> Dict[str, Any]: "csv_data": [], } - bedrock_client = boto3.client("bedrock-agent", config=boto3_config, region_name=region) + bedrock_client = boto3.client( + "bedrock-agent", config=boto3_config, region_name=region + ) try: # List all prompts @@ -1514,7 +1534,9 @@ def check_bedrock_knowledge_base_encryption(region: str = "") -> Dict[str, Any]: "csv_data": [], } - bedrock_agent_client = boto3.client("bedrock-agent", config=boto3_config, region_name=region) + bedrock_agent_client = boto3.client( + "bedrock-agent", config=boto3_config, region_name=region + ) try: # List all knowledge bases @@ -1643,7 +1665,9 @@ def check_bedrock_knowledge_base_encryption(region: str = "") -> Dict[str, Any]: } -def check_bedrock_guardrail_iam_enforcement(permission_cache, region: str = "") -> Dict[str, Any]: +def check_bedrock_guardrail_iam_enforcement( + permission_cache, region: str = "" +) -> Dict[str, Any]: """ Check if IAM policies enforce the use of specific guardrails via the bedrock:GuardrailIdentifier condition key @@ -1657,7 +1681,9 @@ def check_bedrock_guardrail_iam_enforcement(permission_cache, region: str = "") "csv_data": [], } - bedrock_client = boto3.client("bedrock", config=boto3_config, region_name=region) + bedrock_client = boto3.client( + "bedrock", config=boto3_config, region_name=region + ) # First check if any guardrails exist try: @@ -1845,7 +1871,9 @@ def check_bedrock_custom_model_encryption(region: str = "") -> Dict[str, Any]: "csv_data": [], } - bedrock_client = boto3.client("bedrock", config=boto3_config, region_name=region) + bedrock_client = boto3.client( + "bedrock", config=boto3_config, region_name=region + ) try: # List custom models @@ -2003,7 +2031,9 @@ def check_bedrock_invocation_log_encryption(region: str = "") -> Dict[str, Any]: "csv_data": [], } - bedrock_client = boto3.client("bedrock", config=boto3_config, region_name=region) + bedrock_client = boto3.client( + "bedrock", config=boto3_config, region_name=region + ) s3_client = boto3.client("s3", config=boto3_config, region_name=region) try: @@ -2171,7 +2201,9 @@ def check_bedrock_flows_guardrails(region: str = "") -> Dict[str, Any]: "csv_data": [], } - bedrock_agent_client = boto3.client("bedrock-agent", config=boto3_config, region_name=region) + bedrock_agent_client = boto3.client( + "bedrock-agent", config=boto3_config, region_name=region + ) try: # List all flows @@ -2362,7 +2394,9 @@ def check_bedrock_agent_roles(permission_cache, region: str = "") -> Dict[str, A "csv_data": [], } - bedrock_client = boto3.client("bedrock-agent", config=boto3_config, region_name=region) + bedrock_client = boto3.client( + "bedrock-agent", config=boto3_config, region_name=region + ) try: # Get all Bedrock agents @@ -2571,7 +2605,9 @@ def get_current_utc_date(): return datetime.now(timezone.utc).strftime("%Y/%m/%d") -def write_to_s3(execution_id, csv_content: str, bucket_name: str, region: str = "") -> str: +def write_to_s3( + execution_id, csv_content: str, bucket_name: str, region: str = "" +) -> str: """ Write CSV report to S3 bucket """ @@ -2628,12 +2664,16 @@ def lambda_handler(event, context): if is_primary_region: logger.info("Running global AmazonBedrockFullAccess check (BR-01)") all_findings.append( - check_bedrock_full_access_roles(permission_cache, region=GLOBAL_REGION_LABEL) + check_bedrock_full_access_roles( + permission_cache, region=GLOBAL_REGION_LABEL + ) ) logger.info("Running global marketplace subscription access check (BR-03)") all_findings.append( - check_marketplace_subscription_access(permission_cache, region=GLOBAL_REGION_LABEL) + check_marketplace_subscription_access( + permission_cache, region=GLOBAL_REGION_LABEL + ) ) logger.info("Running global stale Bedrock access check (BR-14)") @@ -2648,7 +2688,9 @@ def lambda_handler(event, context): bedrock_unavailable = False unavailable_detail = "" try: - test_client = boto3.client("bedrock", config=boto3_config, region_name=region) + test_client = boto3.client( + "bedrock", config=boto3_config, region_name=region + ) test_client.get_model_invocation_logging_configuration() except EndpointConnectionError: bedrock_unavailable = True @@ -2661,31 +2703,41 @@ def lambda_handler(event, context): else: # Service is reachable (e.g. ValidationException, AccessDenied) — # proceed; individual checks handle their own errors. - logger.info(f"Bedrock availability probe returned {error_code}; proceeding with checks") + logger.info( + f"Bedrock availability probe returned {error_code}; proceeding with checks" + ) if bedrock_unavailable: logger.info(f"Bedrock service not available in region {region}, skipping") - all_findings.append({ - "check_name": "Bedrock Service Availability", - "status": "N/A", - "details": f"Bedrock is not available in region {region}", - "csv_data": [ - create_finding( - check_id="BR-00", - finding_name="Bedrock Service Availability", - finding_details=unavailable_detail, - resolution="No action required. Bedrock is not deployed in this region.", - reference="https://docs.aws.amazon.com/bedrock/latest/userguide/bedrock-regions.html", - severity="Informational", - status="N/A", - region=region, - ) - ], - }) + all_findings.append( + { + "check_name": "Bedrock Service Availability", + "status": "N/A", + "details": f"Bedrock is not available in region {region}", + "csv_data": [ + create_finding( + check_id="BR-00", + finding_name="Bedrock Service Availability", + finding_details=unavailable_detail, + resolution="No action required. Bedrock is not deployed in this region.", + reference="https://docs.aws.amazon.com/bedrock/latest/userguide/bedrock-regions.html", + severity="Informational", + status="N/A", + region=region, + ) + ], + } + ) csv_content = generate_csv_report(all_findings) bucket_name = os.environ.get("AIML_ASSESSMENT_BUCKET_NAME") s3_url = write_to_s3(execution_id, csv_content, bucket_name, region=region) - return {"statusCode": 200, "body": {"message": f"Bedrock not available in {region}", "report_url": s3_url}} + return { + "statusCode": 200, + "body": { + "message": f"Bedrock not available in {region}", + "report_url": s3_url, + }, + } # Run regional checks using the cached permissions logger.info("Running Bedrock access and VPC endpoints check") @@ -2707,11 +2759,15 @@ def lambda_handler(event, context): all_findings.append(bedrock_cloudtrail_findings) logger.info("Running Bedrock Prompt Management check") - bedrock_prompt_management_findings = check_bedrock_prompt_management(region=region) + bedrock_prompt_management_findings = check_bedrock_prompt_management( + region=region + ) all_findings.append(bedrock_prompt_management_findings) logger.info("Running Bedrock agent IAM roles check") - bedrock_agent_roles_findings = check_bedrock_agent_roles(permission_cache, region=region) + bedrock_agent_roles_findings = check_bedrock_agent_roles( + permission_cache, region=region + ) all_findings.append(bedrock_agent_roles_findings) logger.info("Running Bedrock Knowledge Base encryption check") @@ -2725,11 +2781,15 @@ def lambda_handler(event, context): all_findings.append(guardrail_iam_findings) logger.info("Running Bedrock custom model encryption check") - custom_model_encryption_findings = check_bedrock_custom_model_encryption(region=region) + custom_model_encryption_findings = check_bedrock_custom_model_encryption( + region=region + ) all_findings.append(custom_model_encryption_findings) logger.info("Running Bedrock invocation log encryption check") - invocation_log_encryption_findings = check_bedrock_invocation_log_encryption(region=region) + invocation_log_encryption_findings = check_bedrock_invocation_log_encryption( + region=region + ) all_findings.append(invocation_log_encryption_findings) logger.info("Running Bedrock Flows guardrails check") diff --git a/aiml-security-assessment/functions/security/bedrock_assessments/schema.py b/aiml-security-assessment/functions/security/bedrock_assessments/schema.py index c2c4938..16a66d9 100644 --- a/aiml-security-assessment/functions/security/bedrock_assessments/schema.py +++ b/aiml-security-assessment/functions/security/bedrock_assessments/schema.py @@ -3,43 +3,59 @@ from pydantic import BaseModel, Field, field_validator import re + class SeverityEnum(str, Enum): HIGH = "High" MEDIUM = "Medium" LOW = "Low" INFORMATIONAL = "Informational" + class StatusEnum(str, Enum): FAILED = "Failed" PASSED = "Passed" NA = "N/A" + class Finding(BaseModel): """Represents a security finding with required fields and validations""" - Check_ID: str = Field(..., min_length=1, description="Unique check identifier (e.g., SM-01, BR-01, AC-01)") + + Check_ID: str = Field( + ..., + min_length=1, + description="Unique check identifier (e.g., SM-01, BR-01, AC-01)", + ) Finding: str = Field(..., min_length=1, description="The name/title of the finding") - Finding_Details: str = Field(..., min_length=1, description="Detailed description of the finding") - Resolution: str = Field(..., min_length=0, description="Steps to resolve the finding") + Finding_Details: str = Field( + ..., min_length=1, description="Detailed description of the finding" + ) + Resolution: str = Field( + ..., min_length=0, description="Steps to resolve the finding" + ) Reference: str = Field(..., description="Documentation reference URL") Severity: SeverityEnum = Field(..., description="Severity level of the finding") Status: StatusEnum = Field(..., description="Current status of the finding") - Region: str = Field(default="", description="AWS region where the finding was identified") + Region: str = Field( + default="", description="AWS region where the finding was identified" + ) @field_validator("Check_ID") @classmethod def validate_check_id(cls, v): """Validate that Check_ID follows the pattern XX-NN (e.g., SM-01, BR-14, AC-05)""" - pattern = r'^[A-Z]{2,3}-\d{2}$' + pattern = r"^[A-Z]{2,3}-\d{2}$" if not re.match(pattern, v): - raise ValueError('Check_ID must follow pattern XX-NN (e.g., SM-01, BR-14, AC-05)') + raise ValueError( + "Check_ID must follow pattern XX-NN (e.g., SM-01, BR-14, AC-05)" + ) return v @field_validator("Reference") @classmethod def validate_reference_url(cls, v): """Validate that reference URL starts with https://""" - if not str(v).startswith('https://'): - raise ValueError('Reference URL must start with https://') + if not str(v).startswith("https://"): + raise ValueError("Reference URL must start with https://") return v @field_validator("Severity") @@ -47,7 +63,7 @@ def validate_reference_url(cls, v): def validate_severity(cls, v): """Validate that severity is one of the allowed values""" if v not in SeverityEnum.__members__.values(): - raise ValueError('Severity must be one of the allowed values') + raise ValueError("Severity must be one of the allowed values") return v @field_validator("Status") @@ -55,9 +71,10 @@ def validate_severity(cls, v): def validate_status(cls, v): """Validate that status is one of the allowed values""" if v not in StatusEnum.__members__.values(): - raise ValueError('Status must be one of the allowed values') + raise ValueError("Status must be one of the allowed values") return v + def create_finding( check_id: str, finding_name: str, @@ -66,7 +83,7 @@ def create_finding( reference: str, severity: SeverityEnum, status: StatusEnum, - region: str = "" + region: str = "", ) -> Dict[str, Any]: """ Create a validated finding object @@ -95,6 +112,6 @@ def create_finding( Reference=reference, Severity=severity, Status=status, - Region=region + Region=region, ) return dict(finding.model_dump()) # Convert to regular dictionary diff --git a/aiml-security-assessment/functions/security/finserv_assessments/schema.py b/aiml-security-assessment/functions/security/finserv_assessments/schema.py index d55425e..22a4ed7 100644 --- a/aiml-security-assessment/functions/security/finserv_assessments/schema.py +++ b/aiml-security-assessment/functions/security/finserv_assessments/schema.py @@ -40,7 +40,9 @@ class Finding(BaseModel): Reference: str = Field(..., description="Documentation reference URL") Severity: SeverityEnum = Field(..., description="Severity level of the finding") Status: StatusEnum = Field(..., description="Current status of the finding") - Region: str = Field(default="", description="AWS region where the finding was identified") + Region: str = Field( + default="", description="AWS region where the finding was identified" + ) Compliance_Frameworks: str = Field( default="", description=( diff --git a/aiml-security-assessment/functions/security/generate_consolidated_report/report_template.py b/aiml-security-assessment/functions/security/generate_consolidated_report/report_template.py index fa8523a..27cf527 100644 --- a/aiml-security-assessment/functions/security/generate_consolidated_report/report_template.py +++ b/aiml-security-assessment/functions/security/generate_consolidated_report/report_template.py @@ -797,10 +797,7 @@ def generate_html_report( num_real_regions = len(regions) if regions else 0 if num_real_regions + (1 if has_global else 0) > 1: region_options = "".join( - [ - f'' - for r in sorted(regions or []) - ] + [f'' for r in sorted(regions or [])] ) if has_global: region_options += '' @@ -888,7 +885,9 @@ def generate_html_report( sidebar_subtitle = "Assessment Report" account_info = f"Account: {account_id or 'Unknown'}" if num_regions > 1: - header_account_info = f"Account: {account_id or 'Unknown'} · {num_regions} Regions" + header_account_info = ( + f"Account: {account_id or 'Unknown'} · {num_regions} Regions" + ) findings_sub = f"Across {num_regions} regions" security_checks_sub = f"Evaluated across {num_regions} regions" else: @@ -907,21 +906,23 @@ def generate_html_report( region_metrics_html = "" for reg in sorted(regions): reg_findings = [ - f for f in all_findings - if f.get("region", f.get("Region", "")) == reg + f for f in all_findings if f.get("region", f.get("Region", "")) == reg ] reg_high = sum( - 1 for f in reg_findings + 1 + for f in reg_findings if f.get("severity", f.get("Severity", "")).lower() == "high" and f.get("status", f.get("Status", "")).lower() == "failed" ) reg_medium = sum( - 1 for f in reg_findings + 1 + for f in reg_findings if f.get("severity", f.get("Severity", "")).lower() == "medium" and f.get("status", f.get("Status", "")).lower() == "failed" ) reg_low = sum( - 1 for f in reg_findings + 1 + for f in reg_findings if f.get("severity", f.get("Severity", "")).lower() == "low" and f.get("status", f.get("Status", "")).lower() == "failed" ) @@ -997,7 +998,7 @@ def generate_html_report( + 'Financial Services GenAI Risk' ) finserv_scope_source = ( - f' Financial Services GenAI Risk checks are based on ' + f" Financial Services GenAI Risk checks are based on " f'the AWS User Guide to Governance, Risk, and Compliance for Responsible AI Adoption.' ) finserv_section = ( @@ -1007,7 +1008,7 @@ def generate_html_report( + "Financial Services GenAI Risk Findings" '
Scope: this assessment records findings against each resolved CloudFormation TargetRegions entry. These checks are based on ' f'the AWS User Guide to Governance, Risk, and Compliance for Responsible AI Adoption. ' - 'Severities follow a documented Likelihood × Impact methodology (see docs).
' + "Severities follow a documented Likelihood × Impact methodology (see docs)." '
' '
' + finserv_account_filter @@ -1107,7 +1108,7 @@ def generate_html_report( 'Amazon Bedrock AgentCore
' + "" + finserv_scope_industry_block - + '

dict: """ Create a validated finding object @@ -75,6 +88,6 @@ def create_finding( Reference=reference, Severity=severity, Status=status, - Region=region + Region=region, ) - return dict(finding.model_dump()) # Convert to regular dictionary \ No newline at end of file + return dict(finding.model_dump()) # Convert to regular dictionary diff --git a/aiml-security-assessment/functions/security/iam_permission_caching/app.py b/aiml-security-assessment/functions/security/iam_permission_caching/app.py index 8c1cde5..3359d75 100644 --- a/aiml-security-assessment/functions/security/iam_permission_caching/app.py +++ b/aiml-security-assessment/functions/security/iam_permission_caching/app.py @@ -12,9 +12,9 @@ def get_current_utc_date(): # Configure boto3 with retries boto3_config = Config( - retries = dict( - max_attempts = 10, # Maximum number of retries - mode = 'adaptive' # Exponential backoff with adaptive mode + retries=dict( + max_attempts=10, # Maximum number of retries + mode="adaptive", # Exponential backoff with adaptive mode ) ) @@ -22,54 +22,55 @@ def get_current_utc_date(): logger = logging.getLogger() logger.setLevel(logging.ERROR) + def write_permissions_to_s3(permission_cache, execution_id): """ Write the IAM permissions cache to S3 as a JSON file - + Args: permission_cache (IAMPermissionCache): The permission cache object s3_bucket (str): The name of the S3 bucket to write to """ try: # Create S3 client with the same retry configuration - s3_client = boto3.client('s3', config=boto3_config) - + s3_client = boto3.client("s3", config=boto3_config) + # Prepare the data to be written cache_data = { - 'role_permissions': permission_cache.role_permissions, - 'user_permissions': permission_cache.user_permissions, - 'generated_at': datetime.now().isoformat() + "role_permissions": permission_cache.role_permissions, + "user_permissions": permission_cache.user_permissions, + "generated_at": datetime.now().isoformat(), } - + # Convert to JSON string json_data = json.dumps(cache_data, default=str, indent=2) - + # Define the S3 key (filename) - write to bucket root - s3_key = f'permissions_cache_{execution_id}.json' - s3_bucket = os.environ.get('AIML_ASSESSMENT_BUCKET_NAME') + s3_key = f"permissions_cache_{execution_id}.json" + s3_bucket = os.environ.get("AIML_ASSESSMENT_BUCKET_NAME") # Upload to S3 s3_client.put_object( - Bucket=s3_bucket, - Key=s3_key, - Body=json_data, - ContentType='application/json' + Bucket=s3_bucket, Key=s3_key, Body=json_data, ContentType="application/json" + ) + + logger.info( + f"Successfully wrote permissions cache to s3://{s3_bucket}/{s3_key}" ) - - logger.info(f"Successfully wrote permissions cache to s3://{s3_bucket}/{s3_key}") return s3_key - + except Exception as e: logger.error(f"Error writing permissions cache to S3: {str(e)}", exc_info=True) raise + class IAMPermissionCache: def __init__(self, iam_client): self.iam_client = iam_client self.role_permissions = {} self.user_permissions = {} self.policy_cache = {} - + def initialize(self): """ Get all IAM permissions and cache them @@ -77,7 +78,7 @@ def initialize(self): logger.info("Initializing IAM permission cache") self._cache_role_permissions() self._cache_user_permissions() - + def _get_policy_document(self, policy_arn, version_id): """ Get policy document with caching @@ -86,12 +87,13 @@ def _get_policy_document(self, policy_arn, version_id): if cache_key not in self.policy_cache: try: response = self.iam_client.get_policy_version( - PolicyArn=policy_arn, - VersionId=version_id + PolicyArn=policy_arn, VersionId=version_id ) - self.policy_cache[cache_key] = response['PolicyVersion']['Document'] + self.policy_cache[cache_key] = response["PolicyVersion"]["Document"] except Exception as e: - logger.error(f"Error getting policy document for {policy_arn}: {str(e)}") + logger.error( + f"Error getting policy document for {policy_arn}: {str(e)}" + ) return None return self.policy_cache[cache_key] @@ -100,109 +102,142 @@ def _cache_role_permissions(self): Cache all role permissions """ logger.info("Caching role permissions") - paginator = self.iam_client.get_paginator('list_roles') + paginator = self.iam_client.get_paginator("list_roles") for page in paginator.paginate(): - for role in page['Roles']: - role_name = role['RoleName'] + for role in page["Roles"]: + role_name = role["RoleName"] self.role_permissions[role_name] = { - 'attached_policies': [], - 'inline_policies': [] + "attached_policies": [], + "inline_policies": [], } - + # Get attached policies try: - attached_policies = self.iam_client.list_attached_role_policies(RoleName=role_name) - for policy in attached_policies['AttachedPolicies']: - policy_arn = policy['PolicyArn'] + attached_policies = self.iam_client.list_attached_role_policies( + RoleName=role_name + ) + for policy in attached_policies["AttachedPolicies"]: + policy_arn = policy["PolicyArn"] try: - policy_info = self.iam_client.get_policy(PolicyArn=policy_arn)['Policy'] - policy_doc = self._get_policy_document(policy_arn, policy_info['DefaultVersionId']) + policy_info = self.iam_client.get_policy( + PolicyArn=policy_arn + )["Policy"] + policy_doc = self._get_policy_document( + policy_arn, policy_info["DefaultVersionId"] + ) if policy_doc: - self.role_permissions[role_name]['attached_policies'].append({ - 'name': policy['PolicyName'], - 'arn': policy_arn, - 'document': policy_doc - }) + self.role_permissions[role_name][ + "attached_policies" + ].append( + { + "name": policy["PolicyName"], + "arn": policy_arn, + "document": policy_doc, + } + ) except Exception as e: logger.error(f"Error getting policy {policy_arn}: {str(e)}") except Exception as e: - logger.error(f"Error getting attached policies for role {role_name}: {str(e)}") + logger.error( + f"Error getting attached policies for role {role_name}: {str(e)}" + ) # Get inline policies try: - inline_policies = self.iam_client.list_role_policies(RoleName=role_name) - for policy_name in inline_policies['PolicyNames']: + inline_policies = self.iam_client.list_role_policies( + RoleName=role_name + ) + for policy_name in inline_policies["PolicyNames"]: try: policy_doc = self.iam_client.get_role_policy( - RoleName=role_name, - PolicyName=policy_name - )['PolicyDocument'] - self.role_permissions[role_name]['inline_policies'].append({ - 'name': policy_name, - 'document': policy_doc - }) + RoleName=role_name, PolicyName=policy_name + )["PolicyDocument"] + self.role_permissions[role_name]["inline_policies"].append( + {"name": policy_name, "document": policy_doc} + ) except Exception as e: - logger.error(f"Error getting inline policy {policy_name}: {str(e)}") + logger.error( + f"Error getting inline policy {policy_name}: {str(e)}" + ) except Exception as e: - logger.error(f"Error getting inline policies for role {role_name}: {str(e)}") + logger.error( + f"Error getting inline policies for role {role_name}: {str(e)}" + ) def _cache_user_permissions(self): """ Cache all user permissions """ logger.info("Caching user permissions") - paginator = self.iam_client.get_paginator('list_users') + paginator = self.iam_client.get_paginator("list_users") for page in paginator.paginate(): - for user in page['Users']: - user_name = user['UserName'] + for user in page["Users"]: + user_name = user["UserName"] self.user_permissions[user_name] = { - 'attached_policies': [], - 'inline_policies': [] + "attached_policies": [], + "inline_policies": [], } - + # Get attached policies try: - attached_policies = self.iam_client.list_attached_user_policies(UserName=user_name) - for policy in attached_policies['AttachedPolicies']: - policy_arn = policy['PolicyArn'] + attached_policies = self.iam_client.list_attached_user_policies( + UserName=user_name + ) + for policy in attached_policies["AttachedPolicies"]: + policy_arn = policy["PolicyArn"] try: - policy_info = self.iam_client.get_policy(PolicyArn=policy_arn)['Policy'] - policy_doc = self._get_policy_document(policy_arn, policy_info['DefaultVersionId']) + policy_info = self.iam_client.get_policy( + PolicyArn=policy_arn + )["Policy"] + policy_doc = self._get_policy_document( + policy_arn, policy_info["DefaultVersionId"] + ) if policy_doc: - self.user_permissions[user_name]['attached_policies'].append({ - 'name': policy['PolicyName'], - 'arn': policy_arn, - 'document': policy_doc - }) + self.user_permissions[user_name][ + "attached_policies" + ].append( + { + "name": policy["PolicyName"], + "arn": policy_arn, + "document": policy_doc, + } + ) except Exception as e: logger.error(f"Error getting policy {policy_arn}: {str(e)}") except Exception as e: - logger.error(f"Error getting attached policies for user {user_name}: {str(e)}") + logger.error( + f"Error getting attached policies for user {user_name}: {str(e)}" + ) # Get inline policies try: - inline_policies = self.iam_client.list_user_policies(UserName=user_name) - for policy_name in inline_policies['PolicyNames']: + inline_policies = self.iam_client.list_user_policies( + UserName=user_name + ) + for policy_name in inline_policies["PolicyNames"]: try: policy_doc = self.iam_client.get_user_policy( - UserName=user_name, - PolicyName=policy_name - )['PolicyDocument'] - self.user_permissions[user_name]['inline_policies'].append({ - 'name': policy_name, - 'document': policy_doc - }) + UserName=user_name, PolicyName=policy_name + )["PolicyDocument"] + self.user_permissions[user_name]["inline_policies"].append( + {"name": policy_name, "document": policy_doc} + ) except Exception as e: - logger.error(f"Error getting inline policy {policy_name}: {str(e)}") + logger.error( + f"Error getting inline policy {policy_name}: {str(e)}" + ) except Exception as e: - logger.error(f"Error getting inline policies for user {user_name}: {str(e)}") - + logger.error( + f"Error getting inline policies for user {user_name}: {str(e)}" + ) + + def lambda_handler(event, context): """ Main Lambda handler """ logger.info("Starting Bedrock security assessment") - iam_client = boto3.client('iam', config=boto3_config) + iam_client = boto3.client("iam", config=boto3_config) logger.info(event, context) try: # Initialize permission cache @@ -213,13 +248,10 @@ def lambda_handler(event, context): s3_key = write_permissions_to_s3(permission_cache, execution_id) return { - 'statusCode': 200, - 'body': f'Successfully cached IAM permissions to {s3_key}' + "statusCode": 200, + "body": f"Successfully cached IAM permissions to {s3_key}", } except Exception as e: logger.error(f"Error in lambda_handler: {str(e)}", exc_info=True) - return { - 'statusCode': 500, - 'body': f'Error during security checks: {str(e)}' - } + return {"statusCode": 500, "body": f"Error during security checks: {str(e)}"} diff --git a/aiml-security-assessment/functions/security/iam_permission_caching/schema.py b/aiml-security-assessment/functions/security/iam_permission_caching/schema.py index 6515a38..ed2a321 100644 --- a/aiml-security-assessment/functions/security/iam_permission_caching/schema.py +++ b/aiml-security-assessment/functions/security/iam_permission_caching/schema.py @@ -1,45 +1,56 @@ from enum import Enum from pydantic import BaseModel, Field, validator + class SeverityEnum(str, Enum): HIGH = "High" MEDIUM = "Medium" LOW = "Low" INFORMATIONAL = "Informational" + class StatusEnum(str, Enum): FAILED = "Failed" PASSED = "Passed" NA = "N/A" + class Finding(BaseModel): """Represents a security finding with required fields and validations""" + Finding: str = Field(..., min_length=1, description="The name/title of the finding") - Finding_Details: str = Field(..., min_length=1, description="Detailed description of the finding") - Resolution: str = Field(..., min_length=0, description="Steps to resolve the finding") + Finding_Details: str = Field( + ..., min_length=1, description="Detailed description of the finding" + ) + Resolution: str = Field( + ..., min_length=0, description="Steps to resolve the finding" + ) Reference: str = Field(..., description="Documentation reference URL") Severity: SeverityEnum = Field(..., description="Severity level of the finding") Status: StatusEnum = Field(..., description="Current status of the finding") - @validator('Reference') + @validator("Reference") def validate_reference_url(cls, v): """Validate that reference URL starts with https://""" - if not str(v).startswith('https://'): - raise ValueError('Reference URL must start with https://') + if not str(v).startswith("https://"): + raise ValueError("Reference URL must start with https://") return v - @validator('Severity') + + @validator("Severity") def validate_severity(cls, v): """Validate that severity is one of the allowed values""" if v not in SeverityEnum.__members__.values(): - raise ValueError('Severity must be one of the allowed values') + raise ValueError("Severity must be one of the allowed values") return v - @validator('Status') + + @validator("Status") def validate_status(cls, v): """Validate that status is one of the allowed values""" if v not in StatusEnum.__members__.values(): - raise ValueError('Status must be one of the allowed values') + raise ValueError("Status must be one of the allowed values") return v + # Example usage: def create_finding( finding_name: str, @@ -47,11 +58,11 @@ def create_finding( resolution: str, reference: str, severity: SeverityEnum, - status: StatusEnum + status: StatusEnum, ) -> Finding: """ Create a validated finding object - + Args: finding_name: Name of the finding finding_details: Detailed description @@ -59,10 +70,10 @@ def create_finding( reference: Documentation URL severity: Severity level status: Current status - + Returns: Finding: Validated finding object - + Raises: ValidationError: If any field fails validation """ @@ -72,6 +83,6 @@ def create_finding( Resolution=resolution, Reference=reference, Severity=severity, - Status=status + Status=status, ) - return dict(finding.model_dump()) # Convert to regular dictionary \ No newline at end of file + return dict(finding.model_dump()) # Convert to regular dictionary diff --git a/aiml-security-assessment/functions/security/resolve_regions/app.py b/aiml-security-assessment/functions/security/resolve_regions/app.py index ccd3eb2..027b863 100644 --- a/aiml-security-assessment/functions/security/resolve_regions/app.py +++ b/aiml-security-assessment/functions/security/resolve_regions/app.py @@ -36,7 +36,9 @@ def get_available_regions(): def resolve_regions(): """Resolve target regions from environment variable.""" target_regions = os.environ.get("TARGET_REGIONS", "").strip() - current_region = os.environ.get("AWS_REGION", os.environ.get("AWS_DEFAULT_REGION", "us-east-1")) + current_region = os.environ.get( + "AWS_REGION", os.environ.get("AWS_DEFAULT_REGION", "us-east-1") + ) if not target_regions: return [current_region] diff --git a/aiml-security-assessment/functions/security/sagemaker_assessments/app.py b/aiml-security-assessment/functions/security/sagemaker_assessments/app.py index 68dc586..2946b20 100644 --- a/aiml-security-assessment/functions/security/sagemaker_assessments/app.py +++ b/aiml-security-assessment/functions/security/sagemaker_assessments/app.py @@ -110,7 +110,9 @@ def check_sagemaker_internet_access(region: str = "") -> Dict[str, Any]: total_resources_checked = 0 # Create SageMaker client - sagemaker_client = boto3.client("sagemaker", config=boto3_config, region_name=region) + sagemaker_client = boto3.client( + "sagemaker", config=boto3_config, region_name=region + ) # Check Notebook Instances try: @@ -270,7 +272,9 @@ def check_guardduty_enabled(region: str = "") -> Dict[str, Any]: } try: - guardduty_client = boto3.client("guardduty", config=boto3_config, region_name=region) + guardduty_client = boto3.client( + "guardduty", config=boto3_config, region_name=region + ) # Get list of detectors in the current region detectors = guardduty_client.list_detectors() @@ -333,7 +337,9 @@ def check_guardduty_enabled(region: str = "") -> Dict[str, Any]: return findings -def check_sagemaker_iam_permissions(permission_cache, region: str = "") -> Dict[str, Any]: +def check_sagemaker_iam_permissions( + permission_cache, region: str = "" +) -> Dict[str, Any]: """ Check SageMaker IAM permissions and stale access. @@ -477,7 +483,9 @@ def check_sagemaker_sso_configuration(region: str = "") -> Dict[str, Any]: findings = {"csv_data": []} domains_without_sso = [] - sagemaker_client = boto3.client("sagemaker", config=boto3_config, region_name=region) + sagemaker_client = boto3.client( + "sagemaker", config=boto3_config, region_name=region + ) paginator = sagemaker_client.get_paginator("list_domains") for page in paginator.paginate(): @@ -614,7 +622,9 @@ def check_sagemaker_data_protection(region: str = "") -> Dict[str, Any]: try: findings = {"csv_data": []} - sagemaker_client = boto3.client("sagemaker", config=boto3_config, region_name=region) + sagemaker_client = boto3.client( + "sagemaker", config=boto3_config, region_name=region + ) # Track resources with encryption issues resources_with_aws_managed_keys = [] @@ -849,7 +859,9 @@ def check_sagemaker_data_protection(region: str = "") -> Dict[str, Any]: } -def check_sagemaker_mlops_utilization(permission_cache, region: str = "") -> Dict[str, Any]: +def check_sagemaker_mlops_utilization( + permission_cache, region: str = "" +) -> Dict[str, Any]: """ Check if SageMaker MLOps features (Model Registry, Feature Store, and Pipelines) are being utilized properly @@ -858,7 +870,9 @@ def check_sagemaker_mlops_utilization(permission_cache, region: str = "") -> Dic try: findings = {"csv_data": []} - sagemaker_client = boto3.client("sagemaker", config=boto3_config, region_name=region) + sagemaker_client = boto3.client( + "sagemaker", config=boto3_config, region_name=region + ) issues_found = [] # Check Model Registry Usage @@ -1077,7 +1091,9 @@ def check_sagemaker_clarify_usage(permission_cache, region: str = "") -> Dict[st try: findings = {"csv_data": []} - sagemaker_client = boto3.client("sagemaker", config=boto3_config, region_name=region) + sagemaker_client = boto3.client( + "sagemaker", config=boto3_config, region_name=region + ) issues_found = [] try: @@ -1173,7 +1189,9 @@ def check_sagemaker_clarify_usage(permission_cache, region: str = "") -> Dict[st } -def check_sagemaker_model_monitor_usage(permission_cache, region: str = "") -> Dict[str, Any]: +def check_sagemaker_model_monitor_usage( + permission_cache, region: str = "" +) -> Dict[str, Any]: """ Check if SageMaker Model Monitor is configured and actively monitoring models """ @@ -1187,7 +1205,9 @@ def check_sagemaker_model_monitor_usage(permission_cache, region: str = "") -> D try: findings = {"csv_data": []} - sagemaker_client = boto3.client("sagemaker", config=boto3_config, region_name=region) + sagemaker_client = boto3.client( + "sagemaker", config=boto3_config, region_name=region + ) issues_found = [] try: @@ -1287,7 +1307,9 @@ def check_sagemaker_notebook_root_access(region: str = "") -> Dict[str, Any]: try: findings = {"csv_data": []} - sagemaker_client = boto3.client("sagemaker", config=boto3_config, region_name=region) + sagemaker_client = boto3.client( + "sagemaker", config=boto3_config, region_name=region + ) notebooks_with_root = [] notebooks_without_root = [] @@ -1395,7 +1417,9 @@ def check_sagemaker_notebook_vpc_deployment(region: str = "") -> Dict[str, Any]: try: findings = {"csv_data": []} - sagemaker_client = boto3.client("sagemaker", config=boto3_config, region_name=region) + sagemaker_client = boto3.client( + "sagemaker", config=boto3_config, region_name=region + ) notebooks_without_vpc = [] notebooks_with_vpc = [] @@ -1509,7 +1533,9 @@ def check_sagemaker_model_network_isolation(region: str = "") -> Dict[str, Any]: try: findings = {"csv_data": []} - sagemaker_client = boto3.client("sagemaker", config=boto3_config, region_name=region) + sagemaker_client = boto3.client( + "sagemaker", config=boto3_config, region_name=region + ) models_without_isolation = [] models_with_isolation = [] @@ -1640,7 +1666,9 @@ def check_sagemaker_endpoint_instance_count(region: str = "") -> Dict[str, Any]: try: findings = {"csv_data": []} - sagemaker_client = boto3.client("sagemaker", config=boto3_config, region_name=region) + sagemaker_client = boto3.client( + "sagemaker", config=boto3_config, region_name=region + ) endpoints_single_instance = [] endpoints_multi_instance = [] @@ -1768,7 +1796,9 @@ def check_sagemaker_monitoring_network_isolation(region: str = "") -> Dict[str, try: findings = {"csv_data": []} - sagemaker_client = boto3.client("sagemaker", config=boto3_config, region_name=region) + sagemaker_client = boto3.client( + "sagemaker", config=boto3_config, region_name=region + ) schedules_without_isolation = [] schedules_with_isolation = [] @@ -1891,7 +1921,9 @@ def check_sagemaker_model_container_repository(region: str = "") -> Dict[str, An try: findings = {"csv_data": []} - sagemaker_client = boto3.client("sagemaker", config=boto3_config, region_name=region) + sagemaker_client = boto3.client( + "sagemaker", config=boto3_config, region_name=region + ) models_platform_mode = [] models_vpc_mode = [] @@ -2051,7 +2083,9 @@ def check_sagemaker_feature_store_encryption(region: str = "") -> Dict[str, Any] try: findings = {"csv_data": []} - sagemaker_client = boto3.client("sagemaker", config=boto3_config, region_name=region) + sagemaker_client = boto3.client( + "sagemaker", config=boto3_config, region_name=region + ) feature_groups_without_encryption = [] feature_groups_with_encryption = [] @@ -2172,7 +2206,9 @@ def check_sagemaker_data_quality_encryption(region: str = "") -> Dict[str, Any]: try: findings = {"csv_data": []} - sagemaker_client = boto3.client("sagemaker", config=boto3_config, region_name=region) + sagemaker_client = boto3.client( + "sagemaker", config=boto3_config, region_name=region + ) jobs_without_encryption = [] jobs_with_encryption = [] @@ -2286,7 +2322,9 @@ def check_sagemaker_processing_job_encryption(region: str = "") -> Dict[str, Any try: findings = {"csv_data": []} - sagemaker_client = boto3.client("sagemaker", config=boto3_config, region_name=region) + sagemaker_client = boto3.client( + "sagemaker", config=boto3_config, region_name=region + ) jobs_without_encryption = [] jobs_with_encryption = [] @@ -2417,7 +2455,9 @@ def check_sagemaker_transform_job_encryption(region: str = "") -> Dict[str, Any] try: findings = {"csv_data": []} - sagemaker_client = boto3.client("sagemaker", config=boto3_config, region_name=region) + sagemaker_client = boto3.client( + "sagemaker", config=boto3_config, region_name=region + ) jobs_without_encryption = [] jobs_with_encryption = [] @@ -2536,7 +2576,9 @@ def check_sagemaker_transform_job_encryption(region: str = "") -> Dict[str, Any] } -def check_sagemaker_hyperparameter_tuning_encryption(region: str = "") -> Dict[str, Any]: +def check_sagemaker_hyperparameter_tuning_encryption( + region: str = "", +) -> Dict[str, Any]: """ Check if SageMaker hyperparameter tuning jobs have volume encryption enabled. Aligns with AWS Security Hub control SageMaker.12 @@ -2545,7 +2587,9 @@ def check_sagemaker_hyperparameter_tuning_encryption(region: str = "") -> Dict[s try: findings = {"csv_data": []} - sagemaker_client = boto3.client("sagemaker", config=boto3_config, region_name=region) + sagemaker_client = boto3.client( + "sagemaker", config=boto3_config, region_name=region + ) jobs_without_encryption = [] jobs_with_encryption = [] @@ -2680,7 +2724,9 @@ def check_sagemaker_compilation_job_encryption(region: str = "") -> Dict[str, An try: findings = {"csv_data": []} - sagemaker_client = boto3.client("sagemaker", config=boto3_config, region_name=region) + sagemaker_client = boto3.client( + "sagemaker", config=boto3_config, region_name=region + ) jobs_without_encryption = [] jobs_with_encryption = [] @@ -2806,7 +2852,9 @@ def check_sagemaker_automl_network_isolation(region: str = "") -> Dict[str, Any] try: findings = {"csv_data": []} - sagemaker_client = boto3.client("sagemaker", config=boto3_config, region_name=region) + sagemaker_client = boto3.client( + "sagemaker", config=boto3_config, region_name=region + ) jobs_without_isolation = [] jobs_with_isolation = [] @@ -2946,7 +2994,9 @@ def check_model_approval_workflow(region: str = "") -> Dict[str, Any]: try: findings = {"csv_data": []} - sagemaker_client = boto3.client("sagemaker", config=boto3_config, region_name=region) + sagemaker_client = boto3.client( + "sagemaker", config=boto3_config, region_name=region + ) issues_found = [] groups_checked = 0 @@ -3096,7 +3146,9 @@ def check_model_drift_detection(region: str = "") -> Dict[str, Any]: try: findings = {"csv_data": []} - sagemaker_client = boto3.client("sagemaker", config=boto3_config, region_name=region) + sagemaker_client = boto3.client( + "sagemaker", config=boto3_config, region_name=region + ) endpoints_without_monitoring = [] endpoints_with_monitoring = [] @@ -3279,7 +3331,9 @@ def check_ab_testing_shadow_deployment(region: str = "") -> Dict[str, Any]: try: findings = {"csv_data": []} - sagemaker_client = boto3.client("sagemaker", config=boto3_config, region_name=region) + sagemaker_client = boto3.client( + "sagemaker", config=boto3_config, region_name=region + ) single_variant_endpoints = [] multi_variant_endpoints = [] @@ -3463,7 +3517,9 @@ def check_ml_lineage_tracking(region: str = "") -> Dict[str, Any]: try: findings = {"csv_data": []} - sagemaker_client = boto3.client("sagemaker", config=boto3_config, region_name=region) + sagemaker_client = boto3.client( + "sagemaker", config=boto3_config, region_name=region + ) experiments_found = False trials_found = False @@ -3625,7 +3681,9 @@ def check_model_registry_usage(permission_cache, region: str = "") -> Dict[str, try: findings = {"csv_data": []} - sagemaker_client = boto3.client("sagemaker", config=boto3_config, region_name=region) + sagemaker_client = boto3.client( + "sagemaker", config=boto3_config, region_name=region + ) issues_found = [] try: @@ -3834,7 +3892,9 @@ def get_current_utc_date(): return datetime.now(timezone.utc).strftime("%Y/%m/%d") -def write_to_s3(execution_id, csv_content: str, bucket_name: str, region: str = "") -> Dict[str, str]: +def write_to_s3( + execution_id, csv_content: str, bucket_name: str, region: str = "" +) -> Dict[str, str]: """ Write CSV reports to S3 bucket """ @@ -3900,39 +3960,14 @@ def lambda_handler(event, context): # Verify SageMaker is available in this region try: - test_client = boto3.client("sagemaker", config=boto3_config, region_name=region) + test_client = boto3.client( + "sagemaker", config=boto3_config, region_name=region + ) test_client.list_notebook_instances(MaxResults=1) except EndpointConnectionError: logger.info(f"SageMaker service not available in region {region}, skipping") - all_findings.append({ - "check_name": "SageMaker Service Availability", - "status": "N/A", - "details": f"SageMaker is not available in region {region}", - "csv_data": [ - create_finding( - check_id="SM-00", - finding_name="SageMaker Service Availability", - finding_details=f"Amazon SageMaker is not available in region {region}. No checks performed.", - resolution="No action required. SageMaker is not deployed in this region.", - reference="https://docs.aws.amazon.com/general/latest/gr/sagemaker.html", - severity="Informational", - status="N/A", - region=region, - ) - ], - }) - csv_content = generate_csv_report(all_findings) - bucket_name = os.environ.get("AIML_ASSESSMENT_BUCKET_NAME") - s3_url = write_to_s3(execution_id, csv_content, bucket_name, region=region) - return {"statusCode": 200, "body": {"message": f"SageMaker not available in {region}", "report_url": s3_url}} - except ClientError as e: - # A region that exists but is not enabled for the account surfaces as - # an auth/opt-in error rather than a connection failure. Treat it the - # same as "not available" instead of running every check against it. - error_code = e.response.get("Error", {}).get("Code", "") - if error_code in REGION_UNAVAILABLE_ERROR_CODES: - logger.info(f"SageMaker not accessible in region {region} ({error_code}), skipping") - all_findings.append({ + all_findings.append( + { "check_name": "SageMaker Service Availability", "status": "N/A", "details": f"SageMaker is not available in region {region}", @@ -3940,25 +3975,76 @@ def lambda_handler(event, context): create_finding( check_id="SM-00", finding_name="SageMaker Service Availability", - finding_details=f"Amazon SageMaker is not available or not enabled in region {region} ({error_code}). No checks performed.", - resolution="No action required if the region is intentionally disabled. Otherwise enable the region for this account.", + finding_details=f"Amazon SageMaker is not available in region {region}. No checks performed.", + resolution="No action required. SageMaker is not deployed in this region.", reference="https://docs.aws.amazon.com/general/latest/gr/sagemaker.html", severity="Informational", status="N/A", region=region, ) ], - }) + } + ) + csv_content = generate_csv_report(all_findings) + bucket_name = os.environ.get("AIML_ASSESSMENT_BUCKET_NAME") + s3_url = write_to_s3(execution_id, csv_content, bucket_name, region=region) + return { + "statusCode": 200, + "body": { + "message": f"SageMaker not available in {region}", + "report_url": s3_url, + }, + } + except ClientError as e: + # A region that exists but is not enabled for the account surfaces as + # an auth/opt-in error rather than a connection failure. Treat it the + # same as "not available" instead of running every check against it. + error_code = e.response.get("Error", {}).get("Code", "") + if error_code in REGION_UNAVAILABLE_ERROR_CODES: + logger.info( + f"SageMaker not accessible in region {region} ({error_code}), skipping" + ) + all_findings.append( + { + "check_name": "SageMaker Service Availability", + "status": "N/A", + "details": f"SageMaker is not available in region {region}", + "csv_data": [ + create_finding( + check_id="SM-00", + finding_name="SageMaker Service Availability", + finding_details=f"Amazon SageMaker is not available or not enabled in region {region} ({error_code}). No checks performed.", + resolution="No action required if the region is intentionally disabled. Otherwise enable the region for this account.", + reference="https://docs.aws.amazon.com/general/latest/gr/sagemaker.html", + severity="Informational", + status="N/A", + region=region, + ) + ], + } + ) csv_content = generate_csv_report(all_findings) bucket_name = os.environ.get("AIML_ASSESSMENT_BUCKET_NAME") - s3_url = write_to_s3(execution_id, csv_content, bucket_name, region=region) - return {"statusCode": 200, "body": {"message": f"SageMaker not available in {region}", "report_url": s3_url}} + s3_url = write_to_s3( + execution_id, csv_content, bucket_name, region=region + ) + return { + "statusCode": 200, + "body": { + "message": f"SageMaker not available in {region}", + "report_url": s3_url, + }, + } # Service is reachable but returned another API error (e.g. AccessDenied) # — proceed; individual checks handle their own errors. - logger.info(f"SageMaker availability probe returned {error_code}; proceeding with checks") + logger.info( + f"SageMaker availability probe returned {error_code}; proceeding with checks" + ) logger.info("Running SageMaker internet access check") - sagemaker_internet_access_findings = check_sagemaker_internet_access(region=region) + sagemaker_internet_access_findings = check_sagemaker_internet_access( + region=region + ) all_findings.append(sagemaker_internet_access_findings) logger.info("Running SageMaker SSO configuration check") @@ -3966,7 +4052,9 @@ def lambda_handler(event, context): all_findings.append(sagemaker_sso_findings) logger.info("Running SageMaker data protection check") - sagemaker_data_protection_findings = check_sagemaker_data_protection(region=region) + sagemaker_data_protection_findings = check_sagemaker_data_protection( + region=region + ) all_findings.append(sagemaker_data_protection_findings) logger.info("Running GuardDuty SageMaker monitoring check") @@ -3974,15 +4062,21 @@ def lambda_handler(event, context): all_findings.append(guardduty_findings) logger.info("Running SageMaker MLOps features utilization check") - mlops_findings = check_sagemaker_mlops_utilization(permission_cache, region=region) + mlops_findings = check_sagemaker_mlops_utilization( + permission_cache, region=region + ) all_findings.append(mlops_findings) logger.info("Running SageMaker Clarify usage check") - clarify_findings = check_sagemaker_clarify_usage(permission_cache, region=region) + clarify_findings = check_sagemaker_clarify_usage( + permission_cache, region=region + ) all_findings.append(clarify_findings) logger.info("Running SageMaker Model Monitor usage check") - monitor_findings = check_sagemaker_model_monitor_usage(permission_cache, region=region) + monitor_findings = check_sagemaker_model_monitor_usage( + permission_cache, region=region + ) all_findings.append(monitor_findings) logger.info("Running Model Registry usage check") @@ -3998,36 +4092,52 @@ def lambda_handler(event, context): all_findings.append(notebook_vpc_findings) logger.info("Running SageMaker model network isolation check") - model_isolation_findings = check_sagemaker_model_network_isolation(region=region) + model_isolation_findings = check_sagemaker_model_network_isolation( + region=region + ) all_findings.append(model_isolation_findings) logger.info("Running SageMaker endpoint instance count check") - endpoint_instance_findings = check_sagemaker_endpoint_instance_count(region=region) + endpoint_instance_findings = check_sagemaker_endpoint_instance_count( + region=region + ) all_findings.append(endpoint_instance_findings) logger.info("Running SageMaker monitoring network isolation check") - monitoring_isolation_findings = check_sagemaker_monitoring_network_isolation(region=region) + monitoring_isolation_findings = check_sagemaker_monitoring_network_isolation( + region=region + ) all_findings.append(monitoring_isolation_findings) logger.info("Running SageMaker model container repository check") - model_repository_findings = check_sagemaker_model_container_repository(region=region) + model_repository_findings = check_sagemaker_model_container_repository( + region=region + ) all_findings.append(model_repository_findings) logger.info("Running SageMaker Feature Store encryption check") - feature_store_encryption_findings = check_sagemaker_feature_store_encryption(region=region) + feature_store_encryption_findings = check_sagemaker_feature_store_encryption( + region=region + ) all_findings.append(feature_store_encryption_findings) logger.info("Running SageMaker data quality job encryption check") - data_quality_encryption_findings = check_sagemaker_data_quality_encryption(region=region) + data_quality_encryption_findings = check_sagemaker_data_quality_encryption( + region=region + ) all_findings.append(data_quality_encryption_findings) # Additional AWS Security Hub Controls logger.info("Running SageMaker processing job encryption check (SageMaker.10)") - processing_job_encryption_findings = check_sagemaker_processing_job_encryption(region=region) + processing_job_encryption_findings = check_sagemaker_processing_job_encryption( + region=region + ) all_findings.append(processing_job_encryption_findings) logger.info("Running SageMaker transform job encryption check (SageMaker.11)") - transform_job_encryption_findings = check_sagemaker_transform_job_encryption(region=region) + transform_job_encryption_findings = check_sagemaker_transform_job_encryption( + region=region + ) all_findings.append(transform_job_encryption_findings) logger.info( @@ -4047,7 +4157,9 @@ def lambda_handler(event, context): logger.info( "Running SageMaker AutoML job network isolation check (SageMaker.15)" ) - automl_network_isolation_findings = check_sagemaker_automl_network_isolation(region=region) + automl_network_isolation_findings = check_sagemaker_automl_network_isolation( + region=region + ) all_findings.append(automl_network_isolation_findings) # Model Governance Checks diff --git a/aiml-security-assessment/functions/security/sagemaker_assessments/schema.py b/aiml-security-assessment/functions/security/sagemaker_assessments/schema.py index c2c4938..16a66d9 100644 --- a/aiml-security-assessment/functions/security/sagemaker_assessments/schema.py +++ b/aiml-security-assessment/functions/security/sagemaker_assessments/schema.py @@ -3,43 +3,59 @@ from pydantic import BaseModel, Field, field_validator import re + class SeverityEnum(str, Enum): HIGH = "High" MEDIUM = "Medium" LOW = "Low" INFORMATIONAL = "Informational" + class StatusEnum(str, Enum): FAILED = "Failed" PASSED = "Passed" NA = "N/A" + class Finding(BaseModel): """Represents a security finding with required fields and validations""" - Check_ID: str = Field(..., min_length=1, description="Unique check identifier (e.g., SM-01, BR-01, AC-01)") + + Check_ID: str = Field( + ..., + min_length=1, + description="Unique check identifier (e.g., SM-01, BR-01, AC-01)", + ) Finding: str = Field(..., min_length=1, description="The name/title of the finding") - Finding_Details: str = Field(..., min_length=1, description="Detailed description of the finding") - Resolution: str = Field(..., min_length=0, description="Steps to resolve the finding") + Finding_Details: str = Field( + ..., min_length=1, description="Detailed description of the finding" + ) + Resolution: str = Field( + ..., min_length=0, description="Steps to resolve the finding" + ) Reference: str = Field(..., description="Documentation reference URL") Severity: SeverityEnum = Field(..., description="Severity level of the finding") Status: StatusEnum = Field(..., description="Current status of the finding") - Region: str = Field(default="", description="AWS region where the finding was identified") + Region: str = Field( + default="", description="AWS region where the finding was identified" + ) @field_validator("Check_ID") @classmethod def validate_check_id(cls, v): """Validate that Check_ID follows the pattern XX-NN (e.g., SM-01, BR-14, AC-05)""" - pattern = r'^[A-Z]{2,3}-\d{2}$' + pattern = r"^[A-Z]{2,3}-\d{2}$" if not re.match(pattern, v): - raise ValueError('Check_ID must follow pattern XX-NN (e.g., SM-01, BR-14, AC-05)') + raise ValueError( + "Check_ID must follow pattern XX-NN (e.g., SM-01, BR-14, AC-05)" + ) return v @field_validator("Reference") @classmethod def validate_reference_url(cls, v): """Validate that reference URL starts with https://""" - if not str(v).startswith('https://'): - raise ValueError('Reference URL must start with https://') + if not str(v).startswith("https://"): + raise ValueError("Reference URL must start with https://") return v @field_validator("Severity") @@ -47,7 +63,7 @@ def validate_reference_url(cls, v): def validate_severity(cls, v): """Validate that severity is one of the allowed values""" if v not in SeverityEnum.__members__.values(): - raise ValueError('Severity must be one of the allowed values') + raise ValueError("Severity must be one of the allowed values") return v @field_validator("Status") @@ -55,9 +71,10 @@ def validate_severity(cls, v): def validate_status(cls, v): """Validate that status is one of the allowed values""" if v not in StatusEnum.__members__.values(): - raise ValueError('Status must be one of the allowed values') + raise ValueError("Status must be one of the allowed values") return v + def create_finding( check_id: str, finding_name: str, @@ -66,7 +83,7 @@ def create_finding( reference: str, severity: SeverityEnum, status: StatusEnum, - region: str = "" + region: str = "", ) -> Dict[str, Any]: """ Create a validated finding object @@ -95,6 +112,6 @@ def create_finding( Reference=reference, Severity=severity, Status=status, - Region=region + Region=region, ) return dict(finding.model_dump()) # Convert to regular dictionary diff --git a/consolidate_html_reports.py b/consolidate_html_reports.py index 1b21607..e0037bf 100644 --- a/consolidate_html_reports.py +++ b/consolidate_html_reports.py @@ -110,7 +110,11 @@ def consolidate_html_reports(): # Map CSV columns to finding structure region = row.get("Region", "") # "Global" tags IAM-only findings; not a scanned region. - if region and region != GLOBAL_REGION_LABEL and "," not in region: + if ( + region + and region != GLOBAL_REGION_LABEL + and "," not in region + ): regions.add(region) finding = { "account_id": account_id, diff --git a/sample-reports/scripts/capture_screenshots.py b/sample-reports/scripts/capture_screenshots.py index 7e5b080..bb66cea 100755 --- a/sample-reports/scripts/capture_screenshots.py +++ b/sample-reports/scripts/capture_screenshots.py @@ -91,23 +91,25 @@ def optimize_png(image_path: Path, max_size_kb: int = 300) -> None: img = Image.open(image_path) # Convert RGBA to RGB if needed (reduces size) - if img.mode == 'RGBA': - background = Image.new('RGB', img.size, (255, 255, 255)) + if img.mode == "RGBA": + background = Image.new("RGB", img.size, (255, 255, 255)) background.paste(img, mask=img.split()[3]) # Use alpha channel as mask img = background # Save with optimization - img.save(image_path, 'PNG', optimize=True) + img.save(image_path, "PNG", optimize=True) # Check file size file_size_kb = image_path.stat().st_size / 1024 # If still too large, reduce quality by converting to JPEG if file_size_kb > max_size_kb: - jpeg_path = image_path.with_suffix('.jpg') - img.save(jpeg_path, 'JPEG', quality=JPEG_QUALITY, optimize=True) + jpeg_path = image_path.with_suffix(".jpg") + img.save(jpeg_path, "JPEG", quality=JPEG_QUALITY, optimize=True) image_path.unlink() # Remove PNG - print(f" Converted to JPEG: {jpeg_path.name} ({jpeg_path.stat().st_size / 1024:.1f} KB)") + print( + f" Converted to JPEG: {jpeg_path.name} ({jpeg_path.stat().st_size / 1024:.1f} KB)" + ) return jpeg_path print(f" Optimized PNG: {image_path.name} ({file_size_kb:.1f} KB)") @@ -135,7 +137,9 @@ def capture_screenshot(browser, screenshot_config: dict) -> Path: print(f" Source: {screenshot_config['file']}") # Create a new page - page = browser.new_page(viewport={"width": VIEWPORT_WIDTH, "height": VIEWPORT_HEIGHT}) + page = browser.new_page( + viewport={"width": VIEWPORT_WIDTH, "height": VIEWPORT_HEIGHT} + ) # Navigate to the HTML file page.goto(f"file://{html_file.absolute()}") @@ -143,7 +147,9 @@ def capture_screenshot(browser, screenshot_config: dict) -> Path: # Execute actions for action in screenshot_config["actions"]: if action["type"] == "wait": - page.wait_for_selector(action["selector"], timeout=action.get("timeout", 5000)) + page.wait_for_selector( + action["selector"], timeout=action.get("timeout", 5000) + ) elif action["type"] == "click": page.click(action["selector"]) elif action["type"] == "scroll": diff --git a/tests/test_agentcore_checks.py b/tests/test_agentcore_checks.py index 24d8f2c..53b9a2a 100644 --- a/tests/test_agentcore_checks.py +++ b/tests/test_agentcore_checks.py @@ -669,12 +669,16 @@ def client_dispatch(service, *args, **kwargs): return agentcore_mock return MagicMock() - with patch("agentcore_app.boto3.client", side_effect=client_dispatch), \ - patch.object(agentcore_app, "get_permissions_cache", return_value={ - "role_permissions": {}, "user_permissions": {} - }), \ - patch.object(agentcore_app, "generate_csv_report", side_effect=fake_csv), \ - patch.object(agentcore_app, "write_to_s3", return_value="s3://b/r.csv"): + with ( + patch("agentcore_app.boto3.client", side_effect=client_dispatch), + patch.object( + agentcore_app, + "get_permissions_cache", + return_value={"role_permissions": {}, "user_permissions": {}}, + ), + patch.object(agentcore_app, "generate_csv_report", side_effect=fake_csv), + patch.object(agentcore_app, "write_to_s3", return_value="s3://b/r.csv"), + ): resp = agentcore_app.lambda_handler(event, None) return resp, captured.get("findings", []) @@ -746,7 +750,9 @@ def test_unexpected_probe_error_proceeds_with_checks(self): try: from botocore.exceptions import ParamValidationError - probe_error = ParamValidationError(report="maxResults is not a valid parameter") + probe_error = ParamValidationError( + report="maxResults is not a valid parameter" + ) except Exception: probe_error = TypeError("unexpected SDK signature") diff --git a/tests/test_bedrock_checks.py b/tests/test_bedrock_checks.py index 0a4df33..ac8f1f8 100644 --- a/tests/test_bedrock_checks.py +++ b/tests/test_bedrock_checks.py @@ -220,9 +220,7 @@ class TestBR04LoggingConfiguration: @patch("boto3.client") @patch("bedrock_app.detect_bedrock_regional_footprint", return_value=True) - def test_br04_logging_enabled_s3_returns_passed( - self, mock_footprint, mock_client - ): + def test_br04_logging_enabled_s3_returns_passed(self, mock_footprint, mock_client): check = bedrock_app.check_bedrock_logging_configuration mock_bedrock = MagicMock() mock_client.return_value = mock_bedrock @@ -240,9 +238,7 @@ def test_br04_logging_enabled_s3_returns_passed( @patch("boto3.client") @patch("bedrock_app.detect_bedrock_regional_footprint", return_value=True) - def test_br04_logging_disabled_returns_failed( - self, mock_footprint, mock_client - ): + def test_br04_logging_disabled_returns_failed(self, mock_footprint, mock_client): check = bedrock_app.check_bedrock_logging_configuration mock_bedrock = MagicMock() mock_client.return_value = mock_bedrock @@ -257,9 +253,7 @@ def test_br04_logging_disabled_returns_failed( @patch("boto3.client") @patch("bedrock_app.detect_bedrock_regional_footprint", return_value=False) - def test_br04_no_regional_footprint_returns_na( - self, mock_footprint, mock_client - ): + def test_br04_no_regional_footprint_returns_na(self, mock_footprint, mock_client): check = bedrock_app.check_bedrock_logging_configuration result = check(region="eu-west-1") findings = extract_csv_data(result) @@ -322,9 +316,7 @@ def test_br05_no_guardrails_returns_failed(self, mock_client): mock_bedrock = MagicMock() mock_client.return_value = mock_bedrock mock_bedrock.list_guardrails.return_value = {"guardrails": []} - with patch( - "bedrock_app.detect_bedrock_regional_footprint", return_value=True - ): + with patch("bedrock_app.detect_bedrock_regional_footprint", return_value=True): result = check() findings = extract_csv_data(result) assert len(findings) >= 1 @@ -337,9 +329,7 @@ def test_br05_no_guardrails_and_no_regional_footprint_returns_na(self, mock_clie mock_bedrock = MagicMock() mock_client.return_value = mock_bedrock mock_bedrock.list_guardrails.return_value = {"guardrails": []} - with patch( - "bedrock_app.detect_bedrock_regional_footprint", return_value=False - ): + with patch("bedrock_app.detect_bedrock_regional_footprint", return_value=False): result = check(region="eu-west-3") findings = extract_csv_data(result) assert len(findings) >= 1 @@ -363,9 +353,7 @@ def test_br05_schema_valid(self, mock_client): mock_bedrock = MagicMock() mock_client.return_value = mock_bedrock mock_bedrock.list_guardrails.return_value = {"guardrails": []} - with patch( - "bedrock_app.detect_bedrock_regional_footprint", return_value=True - ): + with patch("bedrock_app.detect_bedrock_regional_footprint", return_value=True): result = check() for f in extract_csv_data(result): assert_finding_schema(f) @@ -379,9 +367,7 @@ class TestBR06CloudTrailLogging: @patch("boto3.client") @patch("bedrock_app.detect_bedrock_regional_footprint", return_value=True) - def test_br06_trail_is_logging_returns_passed( - self, mock_footprint, mock_client - ): + def test_br06_trail_is_logging_returns_passed(self, mock_footprint, mock_client): check = bedrock_app.check_bedrock_cloudtrail_logging mock_ct = MagicMock() mock_client.return_value = mock_ct @@ -422,9 +408,7 @@ def test_br06_no_trails_returns_failed(self, mock_footprint, mock_client): @patch("boto3.client") @patch("bedrock_app.detect_bedrock_regional_footprint", return_value=True) - def test_br06_trail_not_logging_returns_failed( - self, mock_footprint, mock_client - ): + def test_br06_trail_not_logging_returns_failed(self, mock_footprint, mock_client): check = bedrock_app.check_bedrock_cloudtrail_logging mock_ct = MagicMock() mock_client.return_value = mock_ct @@ -444,9 +428,7 @@ def test_br06_trail_not_logging_returns_failed( @patch("boto3.client") @patch("bedrock_app.detect_bedrock_regional_footprint", return_value=False) - def test_br06_no_regional_footprint_returns_na( - self, mock_footprint, mock_client - ): + def test_br06_no_regional_footprint_returns_na(self, mock_footprint, mock_client): check = bedrock_app.check_bedrock_cloudtrail_logging result = check(region="eu-west-1") findings = extract_csv_data(result) @@ -528,9 +510,7 @@ def test_br07_list_prompts_api_error_returns_na(self, mock_client): check = bedrock_app.check_bedrock_prompt_management mock_agent = MagicMock() mock_client.return_value = mock_agent - mock_agent.list_prompts.side_effect = Exception( - "InternalServerErrorException" - ) + mock_agent.list_prompts.side_effect = Exception("InternalServerErrorException") result = check() findings = extract_csv_data(result) assert len(findings) >= 1 @@ -1078,9 +1058,7 @@ def test_br13_unknown_operation_returns_clean_message(self, mock_client): check = bedrock_app.check_bedrock_flows_guardrails mock_agent = MagicMock() mock_client.return_value = mock_agent - mock_agent.get_paginator.side_effect = Exception( - "UnknownOperationException" - ) + mock_agent.get_paginator.side_effect = Exception("UnknownOperationException") result = check(region="us-west-1") findings = extract_csv_data(result) assert len(findings) >= 1 @@ -1159,10 +1137,17 @@ def fake_csv(findings): ) mock_client.return_value = test_client - with patch.object(bedrock_app, "get_permissions_cache", return_value={ - "role_permissions": {}, "user_permissions": {} - }), patch.object(bedrock_app, "generate_csv_report", side_effect=fake_csv), \ - patch.object(bedrock_app, "write_to_s3", return_value="s3://bucket/report.csv"): + with ( + patch.object( + bedrock_app, + "get_permissions_cache", + return_value={"role_permissions": {}, "user_permissions": {}}, + ), + patch.object(bedrock_app, "generate_csv_report", side_effect=fake_csv), + patch.object( + bedrock_app, "write_to_s3", return_value="s3://bucket/report.csv" + ), + ): resp = bedrock_app.lambda_handler(event, None) return resp, captured.get("findings", []) @@ -1220,10 +1205,15 @@ def fake_csv(findings): ) mock_client.return_value = test_client - with patch.object(bedrock_app, "get_permissions_cache", return_value={ - "role_permissions": {}, "user_permissions": {} - }), patch.object(bedrock_app, "generate_csv_report", side_effect=fake_csv), \ - patch.object(bedrock_app, "write_to_s3", return_value="s3://b/r.csv"): + with ( + patch.object( + bedrock_app, + "get_permissions_cache", + return_value={"role_permissions": {}, "user_permissions": {}}, + ), + patch.object(bedrock_app, "generate_csv_report", side_effect=fake_csv), + patch.object(bedrock_app, "write_to_s3", return_value="s3://b/r.csv"), + ): resp = bedrock_app.lambda_handler( _bedrock_event(region="me-south-1", region_index=1), None ) @@ -1251,10 +1241,15 @@ def fake_csv(findings): ) mock_client.return_value = test_client - with patch.object(bedrock_app, "get_permissions_cache", return_value={ - "role_permissions": {}, "user_permissions": {} - }), patch.object(bedrock_app, "generate_csv_report", side_effect=fake_csv), \ - patch.object(bedrock_app, "write_to_s3", return_value="s3://b/r.csv"): + with ( + patch.object( + bedrock_app, + "get_permissions_cache", + return_value={"role_permissions": {}, "user_permissions": {}}, + ), + patch.object(bedrock_app, "generate_csv_report", side_effect=fake_csv), + patch.object(bedrock_app, "write_to_s3", return_value="s3://b/r.csv"), + ): resp = bedrock_app.lambda_handler( _bedrock_event(region="us-east-1", region_index=0), None ) diff --git a/tests/test_consolidated_report.py b/tests/test_consolidated_report.py index 16c8ac3..6d83d40 100644 --- a/tests/test_consolidated_report.py +++ b/tests/test_consolidated_report.py @@ -67,7 +67,9 @@ def fake_template(**kwargs): captured.update(kwargs) return "" - monkeypatch.setattr(consolidated_app, "generate_report_from_template", fake_template) + monkeypatch.setattr( + consolidated_app, "generate_report_from_template", fake_template + ) # Same global BR-01 finding written into two different region files. results = _build_results( diff --git a/tests/test_sagemaker_checks.py b/tests/test_sagemaker_checks.py index 923428a..09cd414 100644 --- a/tests/test_sagemaker_checks.py +++ b/tests/test_sagemaker_checks.py @@ -203,9 +203,7 @@ def test_sso_non_sso_domain_returns_failed(self, mock_client): mock_sm = MagicMock() mock_client.return_value = mock_sm mock_paginator = MagicMock() - mock_paginator.paginate.return_value = [ - {"Domains": [{"DomainId": "d-123"}]} - ] + mock_paginator.paginate.return_value = [{"Domains": [{"DomainId": "d-123"}]}] mock_sm.get_paginator.return_value = mock_paginator mock_sm.describe_domain.return_value = { "DomainName": "test-domain", @@ -1428,10 +1426,15 @@ def fake_csv(findings): ) mock_client.return_value = test_client - with patch.object(sagemaker_app, "get_permissions_cache", return_value={ - "role_permissions": {}, "user_permissions": {} - }), patch.object(sagemaker_app, "generate_csv_report", side_effect=fake_csv), \ - patch.object(sagemaker_app, "write_to_s3", return_value="s3://b/r.csv"): + with ( + patch.object( + sagemaker_app, + "get_permissions_cache", + return_value={"role_permissions": {}, "user_permissions": {}}, + ), + patch.object(sagemaker_app, "generate_csv_report", side_effect=fake_csv), + patch.object(sagemaker_app, "write_to_s3", return_value="s3://b/r.csv"), + ): resp = sagemaker_app.lambda_handler(event, None) return resp, captured.get("findings", []) @@ -1483,10 +1486,15 @@ def fake_csv(findings): ) mock_client.return_value = test_client - with patch.object(sagemaker_app, "get_permissions_cache", return_value={ - "role_permissions": {}, "user_permissions": {} - }), patch.object(sagemaker_app, "generate_csv_report", side_effect=fake_csv), \ - patch.object(sagemaker_app, "write_to_s3", return_value="s3://b/r.csv"): + with ( + patch.object( + sagemaker_app, + "get_permissions_cache", + return_value={"role_permissions": {}, "user_permissions": {}}, + ), + patch.object(sagemaker_app, "generate_csv_report", side_effect=fake_csv), + patch.object(sagemaker_app, "write_to_s3", return_value="s3://b/r.csv"), + ): resp = sagemaker_app.lambda_handler( _sagemaker_event(region="ap-east-1", region_index=1), None ) @@ -1514,10 +1522,15 @@ def fake_csv(findings): ) mock_client.return_value = test_client - with patch.object(sagemaker_app, "get_permissions_cache", return_value={ - "role_permissions": {}, "user_permissions": {} - }), patch.object(sagemaker_app, "generate_csv_report", side_effect=fake_csv), \ - patch.object(sagemaker_app, "write_to_s3", return_value="s3://b/r.csv"): + with ( + patch.object( + sagemaker_app, + "get_permissions_cache", + return_value={"role_permissions": {}, "user_permissions": {}}, + ), + patch.object(sagemaker_app, "generate_csv_report", side_effect=fake_csv), + patch.object(sagemaker_app, "write_to_s3", return_value="s3://b/r.csv"), + ): resp = sagemaker_app.lambda_handler( _sagemaker_event(region="us-east-1", region_index=0), None ) diff --git a/tests/test_sagemaker_template_permissions.py b/tests/test_sagemaker_template_permissions.py index 381b6cf..17cd16f 100644 --- a/tests/test_sagemaker_template_permissions.py +++ b/tests/test_sagemaker_template_permissions.py @@ -30,7 +30,9 @@ def test_sagemaker_lambda_templates_include_required_actions(): for template_path in TEMPLATE_PATHS: template_text = template_path.read_text(encoding="utf-8") missing_actions = [ - action for action in REQUIRED_SAGEMAKER_ACTIONS if action not in template_text + action + for action in REQUIRED_SAGEMAKER_ACTIONS + if action not in template_text ] assert not missing_actions, ( f"{template_path.name} is missing SageMaker Lambda permissions: " From 6a35b3f48d408a13f0cd5cf3d8197be072b5cd8c Mon Sep 17 00:00:00 2001 From: Agasthi Kothurkar Date: Sat, 20 Jun 2026 09:31:59 -0400 Subject: [PATCH 22/30] Update project documentation and remove stale followups --- FOLLOWUPS.md | 196 ---------------------------------------- docs/CLEANUP.md | 135 +++++++++++++++++++-------- docs/DEVELOPER_GUIDE.md | 111 ++++++++++++++++------- docs/TROUBLESHOOTING.md | 129 +++++++++++++++++++------- 4 files changed, 276 insertions(+), 295 deletions(-) delete mode 100644 FOLLOWUPS.md diff --git a/FOLLOWUPS.md b/FOLLOWUPS.md deleted file mode 100644 index 537ab51..0000000 --- a/FOLLOWUPS.md +++ /dev/null @@ -1,196 +0,0 @@ -# Follow-up items (deferred from PR #23 Round 3) - -> GitHub Issues are disabled on the fork `mehtadman87/sample-aiml-security-assessment`, so -> deferred work is tracked here in-repo (task T1b.7). Promote to an upstream issue/PR when ready. - -## FU-1 — Evaluate adopting a tool-wide `CRITICAL` severity tier - -**Status:** Deferred (intentionally out of scope for Round 3). - -**Background.** The FinServ severity methodology -([`docs/SECURITY_CHECKS_FINSERV_SEVERITY_METHODOLOGY.md`](./SECURITY_CHECKS_FINSERV_SEVERITY_METHODOLOGY.md)) -defines a Likelihood × Impact matrix mapped to the AWS Security Hub ASFF label set, which includes a -**CRITICAL** band (the Impact=High × Likelihood=High cell). Round 3 **capped that cell at High** and -did **not** introduce `Critical`, to keep the FinServ checks consistent with the upstream -Bedrock/SageMaker/AgentCore checks (which have no Critical tier). The drift-guard -(`tests/test_severity_register.py`) asserts no `Critical` is currently emitted. - -**Why it is a separate item.** Adopting `Critical` is a **tool-wide** change. Doing it for FinServ -alone would make FinServ inconsistent with the other three services. A tool-wide rollout touches: -- the shared `SeverityEnum` (each package's `schema.py`); -- all four assessment Lambdas (re-score the I=High × L=High controls); -- the report template severity filters/colors (`generate_consolidated_report/report_template.py` - and `consolidate_html_reports.py`); -- every service's unit tests plus the FinServ drift-guard. - -**Decision needed.** -1. Do we want a `CRITICAL` tier across the whole assessment? -2. If yes, which controls qualify (e.g., full guardrail bypass enabling a regulatory breach; - unauthorized high-value autonomous financial action)? - -**References:** methodology §2 (label scale) and §6 (the deferred decision); ASFF severity — -https://docs.aws.amazon.com/securityhub/1.0/APIReference/API_Severity.html - ---- - -## FU-2 — Report UI design for FinServ / OWASP (REQ-8) - -Broader report UI re-design (top-page FinServ summary box placement, OWASP grouping, Risk -Distribution treatment) is deferred to a separate PR per the reviewer's suggestion. Round 3 delivers -FinServ as a functionally first-class service (REQ-1, Wave 2); the UI-design discussion is tracked -here for a follow-up PR. - ---- ---- ---- - -## FU-3 — Shared-inventory refactor for the FinServ assessment Lambda (REQ-13 C3) - -**Status:** Deferred from Round 3.1 (Wave 5.5 task T5h.9). Classified **Should-Fix** (performance / -scalability optimization), not a correctness blocker. Tracked here for a focused follow-up PR. - -### Background - -The FinServ Lambda (`finserv_assessments/app.py`) runs all 64 checks **sequentially in a single -invocation** via `build_finserv_checks()`. There is no cross-check caching, so several checks -independently re-enumerate the **same** account-wide inventories, and many then issue an N+1 -per-resource call on top. The Round-3.1 pre-release audit (the per-check AWS API inventory) -identified the following duplicate full-account sweeps within one run: - -| Inventory (API) | Times enumerated | Checks performing the enumeration | Per-resource N+1 follow-up | -| --- | --- | --- | --- | -| `lambda:ListFunctions` | ~6× | FS-09, FS-52, FS-55, FS-58, FS-67, FS-69 | FS-09 `GetFunctionConcurrency` per function | -| `bedrock:ListGuardrails` | ~9× | FS-27a, FS-28, FS-36, FS-38, FS-45, FS-47, FS-50, FS-51, FS-59 | `GetGuardrail` per guardrail in each check | -| `bedrock:ListKnowledgeBases` | ~6× | FS-24, FS-31, FS-33, FS-48, FS-61, FS-65 | FS-31/33/65 `ListDataSources`/`GetDataSource` per KB | -| `s3:ListBuckets` (full account) | ~3–4× | FS-21, FS-46 (full sweep); FS-33, FS-65 (KB data-source buckets) | `GetBucketVersioning`/`GetBucketTagging`/`GetBucketNotification` per bucket | -| `wafv2:ListWebACLs` | 4× | FS-01, FS-53, FS-56, FS-68 | `GetWebACL` per ACL in each check | - -In small/empty accounts this is harmless (it is why the Wave-5 test account ran in ~3 minutes). In -**large enterprise accounts** — thousands of Lambda functions, hundreds of S3 buckets, many -guardrails/KBs/ACLs — the repeated full sweeps plus N+1 calls multiply API volume, drive adaptive -retries/throttling (`Config(retries=adaptive)`), and inflate wall-clock time. This is the most -likely driver of a slow run, and previously of a `States.Timeout`. - -### Why it was deferred (and why that is safe) - -1. **It is a large, regression-prone refactor.** It touches ~20 check functions and the way they - obtain AWS data, plus every corresponding mocked unit test (the per-check tests patch - `app.boto3.client`). Bundling that into a correctness-focused PR that "must be 100% sure" - (the reviewer's bar) trades correctness risk for a performance gain. -2. **The blast-radius risk it is associated with (audit C2) is already mitigated in Round 3.1.** - A `Catch [States.ALL] → "FinServ Assessment Incomplete"` was added to the FinServ task in - `statemachine/assessments.asl.json`, and the FinServ Lambda `Timeout` was raised 600 → 900 s. - So even if the FinServ Lambda is slow or times out in a very large account, the consolidated - report is still generated with the other services' findings and a visible incomplete marker — - the failure no longer sinks the whole assessment. -3. **It is purely an optimization** (Should-Fix). The disposition logic and severities are unchanged - by this work; nothing about the *correctness* of any finding depends on it. - -### Who is affected - -Customers running the FinServ assessment (`EnableFinServAssessment=true`) against accounts with large -resource estates — primarily large enterprises. Symptoms: long FinServ Lambda duration, CloudWatch -throttling/retry noise, and (pre-mitigation) the risk of a 900 s timeout. Small accounts are -unaffected. - -### Proposed design - -Collect each shared inventory **once** at the start of `lambda_handler`, into a read-only context -object, and pass it to the checks that need it — mirroring the existing `permission_cache` pattern -(which is already injected via `functools.partial` in `build_finserv_checks()`). - -1. **Add a `ResourceInventory` collected once per invocation**, e.g.: - - ```python - @dataclass - class ResourceInventory: - lambda_functions: list # lambda:ListFunctions (+ concurrency map) - guardrails: list # bedrock:ListGuardrails + GetGuardrail detail, keyed by id - knowledge_bases: list # bedrock:ListKnowledgeBases (+ data sources per KB) - buckets: list # s3:ListBuckets - web_acls: list # wafv2:ListWebACLs + GetWebACL detail (REGIONAL) - ``` - - Each field is populated by one paginated enumeration (reuse `_paginate`). Per-resource detail - (e.g., `GetGuardrail`, `GetWebACL`) is fetched once and stored alongside, eliminating the N+1 - repetition across checks. - -2. **Inject it like `permission_cache`.** In `build_finserv_checks(permission_cache, inventory)`, - bind the inventory to the relevant checks with `functools.partial`, so every registry entry - stays uniformly zero-arg and the handler loop is unchanged. - -3. **Make collection resilient.** A failure (e.g., `AccessDenied`) collecting one inventory must not - abort the others or the whole run. Per-inventory collection should be wrapped so a failed - inventory yields an explicit "unavailable" sentinel; checks that depend on an unavailable - inventory then emit `COULD_NOT_ASSESS` (consistent with `_is_access_error` handling today) rather - than a false `Failed`/`Passed`. Preserve the existing per-check `try/except` safety net. - -4. **Keep region/pagination semantics identical.** Same default-region clients, same `_paginate` - token handling; this is a call-site consolidation, not a behavior change. - -### Test strategy - -- Update the affected per-check unit tests to pass a constructed `ResourceInventory` (or a partial) - instead of patching `boto3.client` for the enumeration calls. Checks that still make non-inventory - calls keep their existing mocks. -- Add tests for the collector itself: one enumeration per inventory; pagination; and the - per-inventory failure path producing the "unavailable" sentinel (→ `COULD_NOT_ASSESS` downstream). -- **Behavior-preserving guarantee:** every existing disposition test (Passed/Failed/N/A per check) - and the severity drift-guard (`tests/test_severity_register.py`) must remain green with **no** - disposition or severity changes. That equivalence is the acceptance bar. -- Optionally add a counter/assertion (in a unit harness) proving each inventory API is called at - most once per handler invocation. - -### Acceptance criteria - -- Each shared inventory (`ListFunctions`, `ListGuardrails`+`GetGuardrail`, `ListKnowledgeBases`, - `ListBuckets`, `ListWebACLs`+`GetWebACL`) is enumerated **at most once** per FinServ run. -- No change to any finding's status or severity (all existing tests + the drift-guard pass - unchanged). -- A per-inventory collection failure degrades only the dependent checks (to `COULD_NOT_ASSESS`), not - the whole run. -- Workspace `finserv_assessments/` stays byte-identical to the fork copy after sync. - -### Risk / considerations - -- **Test isolation** if any memoization is module-level: prefer an explicit per-invocation object - passed as an argument over a module-global cache, to avoid state leaking across unit tests. -- **Partial-inventory correctness:** ensure a check that needs two inventories handles one being - unavailable independently. -- **Memory:** holding full inventories (e.g., all guardrail details) in memory is bounded and far - smaller than the permission cache already loaded; no concern at 1024 MB. - -### Effort estimate - -Roughly 1–2 focused days: ~0.5 day for the collector + injection, ~0.5–1 day updating the ~20 -affected checks and their tests, ~0.5 day validation (full suite + a large-account timing check). - -### References - -- Round-3.1 requirement: **REQ-13 (audit finding C3)** and design section "REQ-13 — Enterprise-scale - resilience & scope (audit C)" in `.kiro/specs/pr-review-round3-fixes/design.md`. -- Deferred task: **T5h.9** in `.kiro/specs/pr-review-round3-fixes/tasks.md`. -- Related mitigation already shipped in Round 3.1: **T5h.8** (ASL `Catch` on the FinServ task + - Lambda `Timeout` 600 → 900 s) addressing audit finding C2. -- Existing pattern to mirror: the `permission_cache` injection in `get_permissions_cache()` / - `build_finserv_checks()` in `finserv_assessments/app.py`. - -## FU-4 — Migrate upstream schemas from Pydantic V1 `@validator` to V2 `@field_validator` - -**Priority:** Low (tech-debt) — not a blocker. - -The upstream `schema.py` files for the Bedrock, SageMaker, AgentCore, consolidated-report, -and IAM-permission-caching Lambdas still use the deprecated Pydantic V1 `@validator` decorator, -which emits `PydanticDeprecatedSince20` warnings and will break when Pydantic V3 removes V1-style -validators. The FinServ `schema.py` already uses the V2 `@field_validator` form. - -**Why deferred:** this PR is scoped to `feature/finserv-risk-checks`. The affected files are -upstream/shared components; migrating them here would exceed the PR's scope and risk merge -conflicts with upstream. Best handled as a dedicated upstream change. - -**Scope:** swap `@validator("X")` → `@field_validator("X")` + `@classmethod`, adjust signatures, -and re-run each module's tests. - -### References - -- Identified in the pre-Wave-6 verification pass (`.kiro/specs/pr-review-round3-fixes/tasks.md`). diff --git a/docs/CLEANUP.md b/docs/CLEANUP.md index 0f929dc..4fb1c49 100644 --- a/docs/CLEANUP.md +++ b/docs/CLEANUP.md @@ -1,15 +1,59 @@ # Cleanup Guide -This guide provides step-by-step instructions for removing all resources deployed by the AI/ML Security Assessment framework. +This guide provides step-by-step instructions for removing resources deployed by the AI/ML Security Assessment framework. + +Before deleting any stacks, record the S3 bucket names from the stack outputs. After a stack is deleted, its outputs are no longer available. + +The deployment creates two kinds of buckets: + +- **Infrastructure stack bucket**: The `AssessmentBucket` output from the stack you deployed manually, such as `aiml-security-single-account` or `aiml-security-multi-account`. +- **Assessment stack buckets**: The `AssessmentBucketName` output from the auto-created SAM stacks, such as `aiml-sec-{account_id}`, `aiml-security-{account_id}`, or `aiml-security-mgmt`. These buckets use `DeletionPolicy: Retain`, so they remain after the SAM assessment stack is deleted and must be deleted manually if you want a full cleanup. ## Cleanup Order For a clean removal, delete resources in this order: -1. **Assessment stacks** (auto-created by SAM) -2. **Infrastructure stack** (the stack you deployed manually) -3. AWS CloudFormation StackSet member roles (multi-account only) -4. Any remaining Amazon S3 buckets manually +1. Record S3 bucket names from stack outputs +2. **Assessment stacks** (auto-created by SAM) +3. Manually empty and delete retained assessment buckets +4. **Infrastructure stack** (the stack you deployed manually) +5. Manually empty and delete the infrastructure stack bucket if stack deletion fails +6. AWS CloudFormation StackSet member roles (multi-account only) +7. Optional: CloudWatch log groups created during assessment runs + +--- + +## Emptying and Deleting Versioned S3 Buckets + +The buckets created by this framework are versioned. A recursive `aws s3 rm` removes current objects, but versioned buckets can still contain noncurrent versions and delete markers. Use the following helper to remove current objects, noncurrent versions, delete markers, and then the bucket. + +This command requires `jq`. + +```bash +BUCKET_NAME="" + +aws s3 rm "s3://${BUCKET_NAME}" --recursive + +while true; do + delete_payload=$(aws s3api list-object-versions \ + --bucket "${BUCKET_NAME}" \ + --output json \ + | jq '{Objects: (((.Versions // []) + (.DeleteMarkers // [])) | map({Key, VersionId}) | .[0:1000])}') + + object_count=$(echo "${delete_payload}" | jq '.Objects | length') + if [ "${object_count}" -eq 0 ]; then + break + fi + + aws s3api delete-objects \ + --bucket "${BUCKET_NAME}" \ + --delete "${delete_payload}" +done + +aws s3 rb "s3://${BUCKET_NAME}" +``` + +Repeat this for each infrastructure and assessment bucket you want to remove. --- @@ -20,27 +64,20 @@ To remove all resources deployed for single-account assessment: 1. **Delete the AWS SAM-deployed assessment stack**: - Navigate to **AWS CloudFormation** > **Stacks** - Select the `aiml-sec-{account_id}` stack (for example, `aiml-sec-123456789012`) + - Before deleting it, open **Outputs** and record the `AssessmentBucketName` value - Click **Delete** - Wait for stack deletion to complete + - The assessment bucket is retained by design, so delete it manually using the S3 cleanup helper above if you no longer need the report artifacts 2. **Delete the AWS CodeBuild infrastructure stack**: - Select the `aiml-security-single-account` stack (or your custom stack name) + - Before deleting it, open **Outputs** and record the `AssessmentBucket` value - Click **Delete** - Wait for stack deletion to complete -3. **Clean up Amazon S3 buckets** (if stack deletion fails due to non-empty buckets): - ```bash - # Empty the assessment bucket - aws s3 rm s3:// --recursive - - # If versioning is enabled, delete version markers - aws s3api delete-objects --bucket --delete \ - "$(aws s3api list-object-versions --bucket \ - --query '{Objects: Versions[].{Key:Key,VersionId:VersionId}}')" - - # Delete the bucket - aws s3 rb s3:// - ``` +3. **Clean up Amazon S3 buckets**: + - Delete the retained `AssessmentBucketName` bucket from the SAM assessment stack. + - If the infrastructure stack deletion fails because its `AssessmentBucket` is not empty, empty and delete that bucket, then retry stack deletion. --- @@ -49,46 +86,42 @@ To remove all resources deployed for single-account assessment: To remove all resources deployed for multi-account assessment: 1. **Delete AWS SAM-deployed stacks in each member account**: - - For each account that was scanned, navigate to **AWS CloudFormation** > **Stacks** + - In the deployment region, for each account that was scanned, navigate to **AWS CloudFormation** > **Stacks** - Select the `aiml-security-{account_id}` stack (for example, `aiml-security-123456789012`) - For the management account, select `aiml-security-mgmt` + - Before deleting each stack, open **Outputs** and record the `AssessmentBucketName` value - Click **Delete** - Alternatively, use the AWS CLI to delete across accounts: ```bash # Assume role in member account and delete stack aws cloudformation delete-stack --stack-name aiml-security- \ - --region + --region ``` + - The assessment buckets are retained by design, so delete them manually using the S3 cleanup helper above if you no longer need the report artifacts 2. **Delete the central AWS CodeBuild infrastructure stack**: - In the management account, navigate to **AWS CloudFormation** > **Stacks** - - Select the `aiml-security-multi-account` stack + - Select the `aiml-security-multi-account` stack, or the custom stack name you chose + - Before deleting it, open **Outputs** and record the `AssessmentBucket` value - Click **Delete** - Wait for stack deletion to complete 3. **Delete the AWS CloudFormation StackSet member roles**: - Navigate to **AWS CloudFormation** > **StackSets** - - Select the `aiml-security-member-roles` AWS CloudFormation StackSet + - Select the StackSet created from `deployment/1-aiml-security-member-roles.yaml` (for example, `aiml-security-member-roles`, or your custom StackSet name) - Click **Actions** > **Delete stacks from StackSet** - Select all deployment targets (OUs or accounts) - Wait for stack instances to be deleted - Once all stack instances are removed, delete the AWS CloudFormation StackSet itself -4. **Clean up Amazon S3 buckets** (if stack deletion fails due to non-empty buckets): - ```bash - # List and identify assessment buckets - aws s3 ls | grep aiml-security +4. **Clean up Amazon S3 buckets**: + - Delete each retained `AssessmentBucketName` bucket from the per-account SAM assessment stacks. + - If the central infrastructure stack deletion fails because its `AssessmentBucket` is not empty, empty and delete that bucket, then retry stack deletion. - # Empty each bucket - aws s3 rm s3:// --recursive + To find likely assessment buckets: - # Delete version markers if versioning was enabled - aws s3api delete-objects --bucket --delete \ - "$(aws s3api list-object-versions --bucket \ - --query '{Objects: Versions[].{Key:Key,VersionId:VersionId}}')" - - # Delete the bucket - aws s3 rb s3:// + ```bash + aws s3 ls | grep aiml-security ``` --- @@ -99,7 +132,35 @@ The deployment creates multiple AWS CloudFormation stacks. Here's how to identif | Stack Type | How to Identify | Action | |------------|-----------------|--------| -| **Infrastructure Stack** (yours) | The name you chose (for example, `aiml-security-single-account`) | Delete second | -| **Assessment Stack** (auto-generated) | `aiml-sec-{account_id}` (single) or `aiml-security-{account_id}` (multi) | Delete first | +| **Infrastructure Stack** (yours) | The name you chose (for example, `aiml-security-single-account`) | Delete after assessment stacks | +| **Assessment Stack** (auto-generated) | `aiml-sec-{account_id}` (single) or `aiml-security-{account_id}` (multi) | Delete before the infrastructure stack | **Quick Check**: If you see a stack name starting with `aiml-sec-` or `aiml-security-` followed by numbers (or `aiml-security-mgmt`), that's an auto-generated assessment stack. + +--- + +## Optional CloudWatch Logs Cleanup + +AWS Lambda and AWS CodeBuild create Amazon CloudWatch log groups during assessment runs. These log groups can remain after stack deletion unless you delete them or configure retention. + +Common log group name patterns include: + +- `/aws/lambda/aiml-security-*` +- `/aws/codebuild/AIMLSecurityCodeBuild` +- `/aws/codebuild/AIMLSecurityMultiAccountCodeBuild` + +To list likely log groups: + +```bash +aws logs describe-log-groups \ + --log-group-name-prefix /aws/lambda/aiml-security- + +aws logs describe-log-groups \ + --log-group-name-prefix /aws/codebuild/AIMLSecurity +``` + +To delete a log group: + +```bash +aws logs delete-log-group --log-group-name "" +``` diff --git a/docs/DEVELOPER_GUIDE.md b/docs/DEVELOPER_GUIDE.md index 0c270de..330c9ed 100644 --- a/docs/DEVELOPER_GUIDE.md +++ b/docs/DEVELOPER_GUIDE.md @@ -48,9 +48,10 @@ The AI/ML Security Assessment Framework is a serverless, multi-account security ### Security Design Principles -- All roles follow the principle of least privilege -- Cross-account trust is limited to the specific AWS CodeBuild role -- Amazon S3 bucket enforces SSL-only access +- Runtime assessment Lambda roles are read-oriented and scoped to the APIs needed by each assessment +- AWS CodeBuild and member-account roles require deployment permissions because they create or update the SAM assessment stacks before running checks +- Cross-account trust is limited to the specific AWS CodeBuild role in the management account +- Amazon S3 buckets enforce SSL-only access - Assessment data is encrypted in transit and at rest - No persistent credentials are stored in AWS CodeBuild @@ -72,7 +73,7 @@ The AI/ML Security Assessment Framework is a serverless, multi-account security #### Step 1: Member Account Roles (`1-aiml-security-member-roles.yaml`) - **AWS CloudFormation StackSets Deployment**: Deploys `AIMLSecurityMemberRole` to all target accounts - **Cross-Account Trust**: Establishes trust relationship with central management account -- **Assessment Permissions**: Grants read-only access to AI/ML services (Amazon Bedrock, Amazon SageMaker AI, Amazon Bedrock AgentCore) for security assessment +- **Assessment and Deployment Permissions**: Grants read-oriented service permissions for assessment checks and deployment permissions needed for CodeBuild to create or update per-account SAM stacks #### Step 2: Central Infrastructure (`2-aiml-security-codebuild.yaml`) - **AWS CodeBuild Project**: Orchestrates multi-account deployments and assessments @@ -85,11 +86,11 @@ The AI/ML Security Assessment Framework is a serverless, multi-account security ### Phase 2: Assessment Execution (AWS CodeBuild Orchestration) #### AWS CodeBuild Execution Flow -1. **Account Discovery**: Lists active accounts from AWS Organizations -2. **Role Assumption**: Assumes `AIMLSecurityMemberRole` in each target account -3. **AWS SAM Deployment**: Deploys the AI/ML assessment stack through AWS SAM -4. **Assessment Execution**: Triggers AWS Step Functions workflow in each account -5. **Results Consolidation**: Collects and consolidates results from all accounts +1. **Account Discovery**: In multi-account mode, lists active accounts from AWS Organizations or uses `MultiAccountListOverride` +2. **Role Assumption**: In multi-account mode, assumes `AIMLSecurityMemberRole` in each target account +3. **AWS SAM Deployment**: Deploys or updates the AI/ML assessment stack through AWS SAM +4. **Assessment Execution**: Triggers AWS Step Functions workflow in each account, passing `enableFinServ` from the deployment parameter +5. **Results Consolidation**: Syncs per-account reports to the infrastructure bucket and creates a consolidated report for multi-account runs #### Project Structure ``` @@ -99,6 +100,8 @@ sample-aiml-security-assessment/ │ │ ├── bedrock_assessments/ # Bedrock security checks (14) │ │ ├── sagemaker_assessments/ # SageMaker security checks (25) │ │ ├── agentcore_assessments/ # AgentCore security checks (13) +│ │ ├── finserv_assessments/ # Optional Financial Services GenAI risk checks (64) +│ │ ├── finserv_tests/ # FinServ-specific unit and coverage tests │ │ ├── iam_permission_caching/ # AWS IAM permissions cache │ │ ├── cleanup_bucket/ # Amazon S3 cleanup │ │ ├── resolve_regions/ # Multi-region resolution Lambda @@ -122,6 +125,8 @@ sample-aiml-security-assessment/ │ ├── *.html # Sample HTML reports │ └── *.png # Report screenshots ├── tests/ # Unit tests for assessment functions +│ └── requirements.txt # Test dependencies +├── .github/workflows/ # PR lint, test, SAM validate, and security scans ├── buildspec.yml # AWS CodeBuild orchestration └── consolidate_html_reports.py # Multi-account report consolidation ``` @@ -129,7 +134,7 @@ sample-aiml-security-assessment/ #### Member Account Resources (Deployed by AWS SAM) - **AWS SAM Application**: AI/ML security assessment stack - **AWS Step Functions**: Single workflow orchestrating all assessments -- **AWS Lambda Functions**: One per service (Amazon Bedrock, Amazon SageMaker AI, Amazon Bedrock AgentCore) plus utilities +- **AWS Lambda Functions**: One per core service (Amazon Bedrock, Amazon SageMaker AI, Amazon Bedrock AgentCore), one FinServ assessment Lambda invoked only when enabled, plus utilities - **Local Amazon S3 Bucket**: Storage for account-specific results ### Assessment Execution Workflow @@ -177,7 +182,18 @@ sample-aiml-security-assessment/ "Branches": [ {"StartAt": "Bedrock Security Assessment", "States": {...}}, {"StartAt": "Sagemaker Security Assessment", "States": {...}}, - {"StartAt": "AgentCore Security Assessment", "States": {...}} + {"StartAt": "AgentCore Security Assessment", "States": {...}}, + { + "StartAt": "FinServ Enabled?", + "States": { + "FinServ Enabled?": { + "Type": "Choice", + "Comment": "Runs FinServ only when enableFinServ is true and RegionIndex is 0" + }, + "FinServ Security Assessment": {"Type": "Task", "Resource": "arn:aws:states:::lambda:invoke", "End": true}, + "FinServ Assessment Skipped": {"Type": "Pass", "End": true} + } + } ], "End": true } @@ -199,7 +215,7 @@ The framework includes **52 core security checks** across three AI/ML services, ### AWS Lambda Functions -Each assessment AWS Lambda function: +Each core service assessment AWS Lambda function: 1. Receives execution context and target region from AWS Step Functions (via the Map state) 2. Verifies the service is available in the target region (returns N/A finding if not) 3. Reads cached AWS IAM permissions from Amazon S3 @@ -209,6 +225,8 @@ Each assessment AWS Lambda function: 7. Uploads results to Amazon S3 with region-suffixed filename 8. Returns findings summary to AWS Step Functions +The Financial Services assessment Lambda is different. It is deployed in both SAM templates, but Step Functions invokes it only when the execution input includes `"enableFinServ": "true"` and only from the first region iteration (`RegionIndex == 0`). It receives the full `TargetRegions` list and emits FinServ findings with Region values so the report can display the same regional filters as the core services. + **Additional Functions:** - **AWS IAM Permission Caching**: Pre-fetches AWS IAM policies to optimize assessment (global, runs once) - **Cleanup Bucket**: Removes old assessment data @@ -354,13 +372,16 @@ def create_finding( ### Step 2: Update AWS SAM Template -Add your new function to `aiml-security-assessment/template.yaml`: +Add your new function to both SAM templates: + +- `aiml-security-assessment/template.yaml` +- `aiml-security-assessment/template-multi-account.yaml` ```yaml ComprehendSecurityAssessmentFunction: Type: AWS::Serverless::Function Properties: - FunctionName: !Sub 'ComprehendSecurityAssessment-${AWS::AccountId}' + FunctionName: !Sub 'aiml-security-${AWS::StackName}-ComprehendAssessment' CodeUri: functions/security/comprehend_assessments/ Handler: app.lambda_handler Runtime: python3.12 @@ -385,7 +406,7 @@ Add your new function to `aiml-security-assessment/template.yaml`: ### Step 3: Update AWS Step Functions Definition -Add new service to the parallel execution in `aiml-security-assessment/statemachine/assessments.asl.json`: +Add the new service to the `Run Security Assessments` parallel branch inside the `Scan Regions` Map state in `aiml-security-assessment/statemachine/assessments.asl.json`. Also add the function ARN substitution and `LambdaInvokePolicy` for the new function in both SAM templates. ```json { @@ -415,7 +436,7 @@ Add new service to the parallel execution in `aiml-security-assessment/statemach ### Step 4: Update AWS IAM Permissions -Add required permissions to member role template: +Add required permissions to every role that may deploy or run the new service assessment: **In `deployment/1-aiml-security-member-roles.yaml`**: ```yaml @@ -434,13 +455,22 @@ Add required permissions to member role template: - comprehend:Get* ``` +**In `deployment/2-aiml-security-codebuild.yaml`** (for management-account multi-account mode): +```yaml +- comprehend:List* +- comprehend:Describe* +- comprehend:Get* +``` + +Also add runtime permissions to the new Lambda role statements in both SAM templates if the new service function needs service-specific access at execution time. + ### Step 5: Test Locally Test your new assessment function locally: ```bash cd aiml-security-assessment -sam build +sam build --template template.yaml sam local invoke ComprehendSecurityAssessmentFunction --event testfile.json ``` @@ -451,7 +481,7 @@ sam local invoke ComprehendSecurityAssessmentFunction --event testfile.json - **Handle Exceptions**: Implement proper error handling and logging - **Follow Least Privilege**: Only request necessary permissions - **Standardize Findings**: Use the `create_finding()` function for consistent output -- **Check ID Convention**: Use service prefixes for check IDs (BR-XX for Amazon Bedrock, SM-XX for Amazon SageMaker AI, AC-XX for Amazon Bedrock AgentCore) +- **Check ID Convention**: Use service prefixes for check IDs (BR-XX for Amazon Bedrock, SM-XX for Amazon SageMaker AI, AC-XX for Amazon Bedrock AgentCore, FS-XX for Financial Services GenAI risk checks) - **Status Semantics**: Use correct status values: - `Passed`: Resources were checked and met the assessed best practice - `Failed`: Resources were checked and found non-compliant @@ -500,9 +530,9 @@ except Exception as e: ### 1. Local Testing ```bash -# Test individual function +# Test an individual SAM function cd aiml-security-assessment -sam build +sam build --template template.yaml sam local invoke NewServiceSecurityAssessmentFunction --event test-event.json ``` @@ -514,7 +544,7 @@ sam deploy --stack-name aiml-security-test --capabilities CAPABILITY_IAM # Execute AWS Step Functions aws stepfunctions start-execution \ --state-machine-arn arn:aws:states:region:account:stateMachine:TestStateMachine \ - --input '{"accountId":"123456789012"}' + --input '{"accountId":"123456789012","enableFinServ":"false"}' ``` ### 3. Multi-Account Testing @@ -541,6 +571,8 @@ For detailed troubleshooting guidance, common issues, and debugging tips, see th ### Development Pattern - Each AWS AI/ML service gets its own dedicated AWS Lambda function - AWS Step Functions orchestrates parallel execution of service assessments +- Multi-region scans use a Step Functions Map state with configurable `MaxRegionConcurrency` +- FinServ checks are opt-in through `EnableFinServAssessment`; the Lambda is deployed by default but invoked only when enabled - Results are consolidated into a single HTML/CSV report - AWS CodeBuild orchestrates deployment and execution across multiple accounts @@ -573,7 +605,7 @@ Both call `generate_html_report()` from `report_template.py` with different para To update report styling, layout, or features: 1. Edit `report_template.py` only - changes apply to both single and multi-account reports -2. Run tests: `python test_generate_report.py` +2. Run the report generator tests from the report package directory: `python -m pytest test_generate_report.py -v` 3. Key functions: - `get_html_template()` - HTML/CSS/JS structure - `generate_table_rows()` - Finding row generation @@ -587,16 +619,16 @@ When you modify the report template or add new features, update the sample repor #### 1. Generate New Sample Reports -After making changes to `report_template.py`, regenerate sample reports: +After making changes to `report_template.py`, regenerate sample reports from a fresh assessment run or from the local report test fixtures. The existing `test_generate_report.py` file is a pytest/unittest test module, not a standalone `--mode/--output` CLI. ```bash -# Single-account mode -python test_generate_report.py --mode single --output sample-reports/security_assessment_single_account.html - -# Multi-account mode -python test_generate_report.py --mode multi --output sample-reports/security_assessment_multi_account.html +# Generate local viewable reports from fixtures +cd aiml-security-assessment/functions/security/generate_consolidated_report +python -m pytest test_generate_report.py -k "generate_viewable_report or generate_multi_account_report" -s ``` +The fixture reports are written under `aiml-security-assessment/functions/security/generate_consolidated_report/test_reports/`. Use them to validate report rendering before refreshing the canonical files in `sample-reports/`. + #### 2. Capture Screenshots The repository includes an automated screenshot capture tool: @@ -707,6 +739,7 @@ GitHub Actions workflows run automatically to validate code quality and security | Workflow | File | What It Checks | |----------|------|----------------| | **Python Code Quality** | `.github/workflows/python-lint.yml` | `ruff check` (lint) and `ruff format --check` (formatting) on changed `.py` files | +| **Python Tests** | `.github/workflows/python-tests.yml` | Runs upstream tests, FinServ tests, and report-pipeline tests in separate pytest sessions | | **CloudFormation Lint** | `.github/workflows/cfn-lint.yml` | Validates deployment and SAM templates with `cfn-lint` | | **SAM Validate & Build** | `.github/workflows/sam-validate.yml` | Runs `sam validate --lint` and `sam build` on SAM templates | | **ASH Security Scan** | `.github/workflows/ash-security-scan.yml` | Scans changed files for secrets, dependency vulnerabilities, and IaC misconfigurations | @@ -726,14 +759,28 @@ Before pushing, run these checks locally to catch issues early: ```bash # Install tools (first time only) -pip install ruff cfn-lint pytest boto3 pydantic +pip install ruff cfn-lint +pip install -r tests/requirements.txt +pip install "pydantic>=2.0.0" # Python lint and format ruff check aiml-security-assessment/functions/security/ ruff format --check aiml-security-assessment/functions/security/ -# Unit tests (213 tests, ~5 seconds, no AWS credentials needed) -python -m pytest tests/ -v +# Unit tests. Run these as separate pytest sessions because multiple +# assessment packages use top-level app.py imports. +export AIML_ASSESSMENT_BUCKET_NAME=test-assessment-bucket +export AWS_DEFAULT_REGION=us-east-1 +export AWS_ACCESS_KEY_ID=testing +export AWS_SECRET_ACCESS_KEY=testing + +python -m pytest tests/ -v --tb=short +python -m pytest aiml-security-assessment/functions/security/finserv_tests/ -v --tb=short +python -m pytest tests/test_consolidate_finserv.py -v --tb=short + +cd aiml-security-assessment/functions/security/generate_consolidated_report +python -m pytest test_generate_report.py -v --tb=short +cd - # CloudFormation lint cfn-lint deployment/*.yaml @@ -743,7 +790,9 @@ cfn-lint aiml-security-assessment/template-multi-account.yaml # SAM validate and build cd aiml-security-assessment sam validate --template template.yaml --lint +sam validate --template template-multi-account.yaml --lint sam build --template template.yaml +sam build --template template-multi-account.yaml ``` ## Support and Resources diff --git a/docs/TROUBLESHOOTING.md b/docs/TROUBLESHOOTING.md index 488e7ce..5128e2c 100644 --- a/docs/TROUBLESHOOTING.md +++ b/docs/TROUBLESHOOTING.md @@ -43,9 +43,11 @@ This guide covers common issues, debugging tips, and frequently asked questions **Solutions:** - Check CodeBuild logs in CloudWatch for specific errors -- Verify the S3 bucket for SAM artifacts exists and is accessible +- Verify the GitHub repository URL and `GitHubBranch` parameter point to a branch, tag, or commit that CodeBuild can clone +- Verify the S3 bucket for SAM artifacts exists and is accessible. If the `aws-sam-cli-managed-default` stack is stuck in `ROLLBACK_COMPLETE` or `DELETE_FAILED`, delete it and re-run CodeBuild - Look for IAM permission errors in the logs - Check if a previous deployment left orphaned resources +- Check whether `TARGET_REGIONS` failed validation. It must be empty, `all`, or a comma-separated list such as `us-east-1,us-west-2` ### 4. AWS Step Functions Execution Failures @@ -54,8 +56,9 @@ This guide covers common issues, debugging tips, and frequently asked questions **Solutions:** - Monitor state machine executions in each account - Check Lambda function logs for errors -- Verify Lambda has sufficient timeout (default 10 minutes) +- Verify Lambda has sufficient timeout. Most assessment Lambdas default to 10 minutes; FinServ has its own timeout in the SAM templates - Verify AWS IAM permissions allow Lambda to access required services +- In multi-region scans, review each region's Map state iteration. A single service branch can be marked incomplete while the state machine still generates a report for the remaining services and regions ### 5. EarlyValidation::ResourceExistenceCheck Error @@ -64,25 +67,72 @@ This guide covers common issues, debugging tips, and frequently asked questions **Cause:** A resource with the same physical name already exists outside of CloudFormation management, typically from a failed deployment. **Solution:** +The versioned bucket cleanup command requires `jq`. + ```bash -# Find the orphaned bucket +# Find likely orphaned buckets aws s3 ls | grep aiml-security -# Empty the bucket -aws s3 rm s3:// --recursive +BUCKET_NAME="" + +aws s3 rm "s3://${BUCKET_NAME}" --recursive + +while true; do + delete_payload=$(aws s3api list-object-versions \ + --bucket "${BUCKET_NAME}" \ + --output json \ + | jq '{Objects: (((.Versions // []) + (.DeleteMarkers // [])) | map({Key, VersionId}) | .[0:1000])}') -# Delete version markers if versioned -aws s3api delete-objects --bucket --delete \ - "$(aws s3api list-object-versions --bucket \ - --query '{Objects: Versions[].{Key:Key,VersionId:VersionId}}')" + object_count=$(echo "${delete_payload}" | jq '.Objects | length') + if [ "${object_count}" -eq 0 ]; then + break + fi -# Delete the bucket -aws s3 rb s3:// + aws s3api delete-objects \ + --bucket "${BUCKET_NAME}" \ + --delete "${delete_payload}" +done + +aws s3 rb "s3://${BUCKET_NAME}" # Re-run the CodeBuild project ``` -### 6. CodeBuild Timeout or Out-of-Memory with Many Accounts +For full bucket cleanup guidance, see [Cleanup Guide](CLEANUP.md#emptying-and-deleting-versioned-s3-buckets). + +### 6. Financial Services Checks Do Not Appear in the Report + +**Symptoms:** The report does not include the **Financial Services** section or any `FS-` findings. + +**Solutions:** +- Confirm the infrastructure stack parameter `EnableFinServAssessment` is set to `true` +- Confirm CodeBuild logs show `FinServ assessment enabled is true` +- If you updated an existing deployment, re-run the CodeBuild project after the CloudFormation update completes. The parameter is passed to the Step Functions execution input when CodeBuild starts the assessment +- Use the CodeBuild-based deployment templates for normal operation. If you deploy the SAM template directly and start Step Functions manually, include `"enableFinServ": "true"` in the `StartExecution` input +- Check the Step Functions execution for the `FinServ Enabled?` choice state and `FinServ Security Assessment` task + +### 7. TargetRegions Validation or Unexpected Region Coverage + +**Symptoms:** CodeBuild fails with a `TARGET_REGIONS` validation error, or the report scans fewer or different regions than expected. + +**Solutions:** +- Leave `TargetRegions` empty to scan only the deployment region +- Use `all` to scan the union of regions returned by boto3 for Amazon Bedrock, Amazon SageMaker AI, and Amazon Bedrock AgentCore +- Use a comma-separated list with no spaces, such as `us-east-1,us-west-2,eu-west-1`. CloudFormation validation rejects spaces and trailing commas +- Confirm the services being assessed are available in each target region. If a service is unavailable or has no resources in a region, the report can show `N/A` or no resource-specific findings for that service and region +- Confirm the account is opted in to any opt-in regions you include + +### 8. CodeBuild Source or GitHub Branch Failures + +**Symptoms:** CodeBuild fails before SAM build, or the logs show repository clone/source errors. + +**Solutions:** +- Confirm `GitHubRepoUrl` is reachable by CodeBuild +- Confirm `GitHubBranch` is a valid branch, tag, or commit +- If you use a fork or feature branch, make sure the infrastructure stack points at that branch before starting CodeBuild +- For private repositories, configure CodeBuild source credentials before deployment or use a source location CodeBuild can access + +### 9. CodeBuild Timeout or Out-of-Memory with Many Accounts **Symptoms:** CodeBuild job times out or runs slowly when scanning a large number of accounts concurrently. @@ -100,30 +150,31 @@ aws s3 rb s3:// - If builds timeout even at "Twelve," increase the `CodeBuildTimeout` parameter (default is 300 minutes for multi-account) - For very large organizations (100+ accounts), consider using `MultiAccountListOverride` to split assessments into batches -### 7. No Reports in S3 Bucket +### 10. No Reports in S3 Bucket **Symptoms:** Assessment completes but no HTML/CSV files appear. **Solutions:** 1. **Wrong bucket**: Use the bucket from the **Infrastructure Stack** outputs, not the assessment stack -2. **Still running**: Check CodeBuild console - assessment typically takes 5-10 minutes -3. **Wrong prefix**: Look under `{account_id}/` for single-account, `consolidated-reports/` for multi-account -4. **Permissions**: Check CloudWatch Logs for Lambda execution errors +2. **Still running**: Check CodeBuild console. Multi-region, multi-account, or FinServ-enabled assessments can take longer than a single-region run +3. **Wrong prefix**: Look under `{account_id}/` for per-account reports and `consolidated-reports/` for the multi-account consolidated HTML report +4. **Post-build copy failed**: In multi-account mode, CodeBuild copies CSV/HTML files from each account's SAM assessment bucket into the central infrastructure bucket. Search CodeBuild logs for `Copying files from`, `Failed to list bucket contents`, or `No files to copy` +5. **Permissions**: Check CloudWatch Logs for Lambda execution errors and CodeBuild logs for S3 sync errors -### 8. Confused by Multiple CloudFormation Stacks +### 11. Confused by Multiple CloudFormation Stacks **Symptoms:** You see multiple stacks and aren't sure which one has your results. -**Explanation:** The deployment creates TWO stacks. Only the infrastructure stack has the `AssessmentBucket` output. +**Explanation:** The deployment creates an infrastructure stack and one or more SAM assessment stacks. The infrastructure stack is the user-facing stack for report access. | Stack Type | How to Identify | What to Do | |------------|-----------------|------------| | **Infrastructure Stack** (yours) | The name you chose (for example, `aiml-security-single-account`) | Use this — go to Outputs tab, copy `AssessmentBucket` | -| **Assessment Stack** (auto-generated) | `aiml-sec-{account_id}` (single) or `aiml-security-{account_id}` (multi) | Ignore — internal operations only | +| **Assessment Stack** (auto-generated) | `aiml-sec-{account_id}` (single), `aiml-security-{account_id}` (multi), or `aiml-security-mgmt` | Internal execution stack. Its `AssessmentBucketName` output is useful for debugging raw per-account reports and retained buckets | **Quick Check**: If a stack name starts with `aiml-sec-` or `aiml-security-` followed by numbers (or `aiml-security-mgmt`), it's auto-generated. Look for the name you chose during deployment. -### 9. Upgrading an Existing Deployment to Multi-Region +### 12. Upgrading an Existing Deployment to Multi-Region **Symptoms:** You have an existing single-region deployment and want to enable multi-region scanning. @@ -137,8 +188,9 @@ aws s3 rb s3:// 6. The next assessment run will scan the specified regions in parallel **What happens during the upgrade:** -- CloudFormation updates the `TARGET_REGIONS` environment variable on the existing Lambdas (the `ResolveRegionsFunction`, state machine, and assessment Lambdas already exist in all deployments — when `TargetRegions` is empty they simply scan a single region) -- No new resources are created — the value of the environment variable changes from empty to your specified regions +- CloudFormation updates the `TARGET_REGIONS` environment variable on the CodeBuild project in the infrastructure stack +- On the next CodeBuild run, SAM deploy updates the assessment stack and passes the new `TargetRegions` value into the assessment Lambdas and state machine +- The Step Functions `Resolve Target Regions` state resolves the region list, then the Map state scans those regions in parallel - No data is lost — the S3 bucket retains all previous reports - Fully backward compatible — leaving `TargetRegions` empty preserves single-region behavior @@ -178,12 +230,16 @@ The trust policy should allow the central CodeBuild role: ### Check S3 Bucket Permissions -Verify the bucket policy allows cross-account writes for multi-account deployments: +The central infrastructure bucket is the user-facing report bucket. In multi-account mode, member accounts do not write directly to this bucket. CodeBuild assumes into each account, copies report files from that account's SAM assessment bucket, then uploads them to the central infrastructure bucket. + +Verify the central bucket policy and CodeBuild S3 permissions if report upload fails: ```bash -aws s3api get-bucket-policy --bucket +aws s3api get-bucket-policy --bucket ``` +If per-account reports are missing, also check the SAM assessment stack's `AssessmentBucketName` output for that account and confirm the files were created there. + ### Monitor AWS Step Functions Executions 1. Navigate to **AWS Step Functions** in the target account @@ -199,7 +255,9 @@ aws s3api get-bucket-policy --bucket **Q: Does this assessment make any changes to my AWS resources?** -A: No. All security checks are **read-only**. The framework only queries your resources to evaluate their configurations. It does not create, modify, or delete any of your AI/ML workloads or data. +A: The security checks do not modify your AI/ML workloads or data. They query resource configuration and write assessment artifacts to framework-owned S3 buckets. + +The framework itself does create and manage its own deployment resources, including CloudFormation stacks, IAM roles, Lambda functions, Step Functions state machines, CodeBuild projects, S3 buckets, EventBridge rules, and optional SNS notifications. At the start of each assessment run, it also cleans old objects from its own SAM assessment bucket before writing the new report artifacts. **Q: How long does an assessment take to run?** @@ -222,7 +280,7 @@ You can automate regular assessments using Amazon EventBridge scheduled rules. **Q: What AWS regions are supported?** -A: The framework supports all standard AWS commercial regions where Amazon Bedrock, Amazon SageMaker AI, or Amazon Bedrock AgentCore are available. AWS GovCloud and AWS China regions may require template modifications. +A: The framework is designed for standard AWS commercial regions where Amazon Bedrock, Amazon SageMaker AI, or Amazon Bedrock AgentCore are available. Leave `TargetRegions` empty for the deployment region, set it to `all` to resolve the union of assessed-service regions, or provide an explicit comma-separated list. AWS GovCloud and AWS China regions may require template modifications. **Q: Does this work if I don't have any AI/ML resources deployed yet?** @@ -291,9 +349,15 @@ aws events put-rule \ aws events put-targets \ --rule "WeeklyAIMLAssessment" \ - --targets "Id"="1","Arn"="arn:aws:codebuild:region:account:project/your-project" + --targets '[{ + "Id": "1", + "Arn": "arn:aws:codebuild:::project/", + "RoleArn": "arn:aws:iam:::role/" + }]' ``` +The target role must trust `events.amazonaws.com` and allow `codebuild:StartBuild` on the assessment CodeBuild project. For new schedules, Amazon EventBridge Scheduler is also a good option. + --- ### Troubleshooting Questions @@ -302,9 +366,10 @@ aws events put-targets \ A: Common causes: 1. **Wrong bucket**: Verify you're looking at the bucket from the **Infrastructure Stack** outputs (not the assessment stack) -2. **Still running**: Check AWS CodeBuild console - the assessment may still be in progress (typically takes 5-10 minutes) -3. **Permissions issue**: Check AWS CloudWatch Logs for AWS Lambda execution errors -4. **Wrong prefix**: Look under `{account_id}/` prefix for single-account, `consolidated-reports/` for multi-account +2. **Still running**: Check AWS CodeBuild console. Multi-region, multi-account, or FinServ-enabled assessments can take longer than a single-region run +3. **Wrong prefix**: Look under `{account_id}/` for per-account reports and `consolidated-reports/` for the multi-account consolidated HTML report +4. **Post-build copy failed**: Search CodeBuild logs for `Copying files from`, `Failed to list bucket contents`, `No files to copy`, or S3 sync errors +5. **Permissions issue**: Check AWS CloudWatch Logs for Lambda errors and CodeBuild logs for S3 access errors **Q: I see "Access Denied" errors in the AWS CodeBuild logs.** @@ -321,8 +386,10 @@ A: Performance factors: - **Number of resources**: Accounts with hundreds of Amazon SageMaker notebooks or Amazon Bedrock models take longer - **API throttling**: AWS API rate limits may slow down assessments in large environments - **Concurrent executions**: Multi-account assessments run in parallel (configurable through the `ConcurrentAccountScans` parameter) +- **Region scope**: Multi-region scans multiply the amount of service inventory collected +- **Financial Services checks**: Enabling `EnableFinServAssessment` adds the optional `FS-` checks and can increase run time -If assessments consistently timeout, increase the AWS Lambda timeout in the AWS SAM template or reduce concurrent account scans. +If assessments consistently timeout, increase `CodeBuildTimeout`, reduce `TargetRegions`, reduce the account batch size with `MultiAccountListOverride`, or lower concurrency if throttling is the bottleneck. Lambda timeout changes require editing the SAM templates. --- From f07d845aa2df37e18e38c9283163d9e839759f75 Mon Sep 17 00:00:00 2001 From: Agasthi Kothurkar Date: Sat, 20 Jun 2026 20:38:10 -0400 Subject: [PATCH 23/30] Fix README reference links --- README.md | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 6d1cb4c..c8870cf 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ [![License: MIT-0](https://img.shields.io/badge/License-MIT--0-yellow.svg)](https://opensource.org/licenses/MIT-0) [![Python 3.12+](https://img.shields.io/badge/Python-3.12+-blue.svg)](https://www.python.org/downloads/) [![AWS SAM](https://img.shields.io/badge/AWS-SAM-orange.svg)](https://aws.amazon.com/serverless/sam/) [![Serverless](https://img.shields.io/badge/Architecture-Serverless-green.svg)](https://aws.amazon.com/serverless/) -**Open-source automated security scanner for Amazon Bedrock, Amazon SageMaker AI, Amazon Bedrock AgentCore, and Financial Services GenAI Risk** — Built on the [AWS Well-Architected Framework (Generative AI Lens)](https://docs.aws.amazon.com/wellarchitected/latest/generative-ai-lens/generative-ai-lens.html) and optional Financial Services GenAI risk guidance. +**Open-source automated security scanner for Amazon Bedrock, Amazon SageMaker AI, Amazon Bedrock AgentCore, and Financial Services GenAI Risk** — Built on the [AWS Well-Architected Framework (Generative AI Lens)](https://docs.aws.amazon.com/wellarchitected/latest/generative-ai-lens/generative-ai-lens.html) and optional [AWS User Guide to Governance, Risk, and Compliance for Responsible AI Adoption within Financial Services Industries](https://d1.awsstatic.com/onedam/marketing-channels/website/aws/en_US/whitepapers/compliance/AWS-User-Guide-Governance-Risk-Compliance-for-Responsible-AI-Adoption-Financial-Services.pdf) guidance. Cloud security automation with **[116 security checks](docs/SECURITY_CHECKS.md)** for your generative AI and machine learning workloads. Identify IAM misconfigurations, encryption gaps, network isolation issues, and potential governance or compliance gaps with interactive HTML reports and actionable remediation guidance. @@ -81,7 +81,7 @@ Designed for workloads using [Amazon Bedrock](https://aws.amazon.com/bedrock/), | Challenge | How This Framework Helps | |-----------|-------------------------| | **Manual security audits are time-consuming** | Fully automated scanning with one-click CloudFormation deployment | -| **Inconsistent security checks across teams** | Standardized 116-check assessment based on AWS Well-Architected best practices and AWS Responsible AI governance, risk, and compliance guidance | +| **Inconsistent security checks across teams** | Standardized 116-check assessment based on AWS Well-Architected Generative AI Lens best practices and AWS Responsible AI governance, risk, and compliance guidance for financial services | | **Difficulty tracking AI/ML security posture** | Interactive HTML dashboards with severity breakdown and per-account visibility | | **Multi-account complexity** | Consolidated reporting across AWS Organizations with cross-account role assumption | | **Compliance and audit support** | Exportable reports to supplement your compliance program, with remediation guidance linked to AWS documentation | @@ -91,7 +91,7 @@ Designed for workloads using [Amazon Bedrock](https://aws.amazon.com/bedrock/), - **[Amazon Bedrock](docs/SECURITY_CHECKS.md#amazon-bedrock-security-checks-14)** (14 checks) - Guardrails, encryption, Amazon VPC endpoints, AWS IAM permissions, model invocation logging - **[Amazon SageMaker AI](docs/SECURITY_CHECKS.md#amazon-sagemaker-ai-security-checks-25)** (25 checks) - AWS Security Hub controls (SageMaker.1-5), encryption, network isolation, AWS IAM, MLOps - **[Amazon Bedrock AgentCore](docs/SECURITY_CHECKS.md#amazon-bedrock-agentcore-security-checks-13)** (13 checks) - Amazon VPC configuration, encryption, observability, resource policies -- **[Financial Services GenAI Risk](docs/SECURITY_CHECKS.md#financial-services-genai-risk-checks-64-additional-5-upstream-extensions)** (64 checks) - Unbounded consumption, excessive agency, supply chain, training data poisoning, hallucination, prompt injection, PII disclosure, and 8 more FinServ-specific risk categories derived from the [AWS User Guide to Governance, Risk, and Compliance for Responsible AI Adoption](https://aws.amazon.com/blogs/security/introducing-the-updated-aws-user-guide-to-governance-risk-and-compliance-for-responsible-ai-adoption/) +- **[Financial Services GenAI Risk](docs/SECURITY_CHECKS.md#financial-services-genai-risk-checks-64-additional-5-upstream-extensions)** (64 checks) - Unbounded consumption, excessive agency, supply chain, training data poisoning, hallucination, prompt injection, PII disclosure, and 8 more FinServ-specific risk categories derived from the [AWS User Guide to Governance, Risk, and Compliance for Responsible AI Adoption within Financial Services Industries](https://d1.awsstatic.com/onedam/marketing-channels/website/aws/en_US/whitepapers/compliance/AWS-User-Guide-Governance-Risk-Compliance-for-Responsible-AI-Adoption-Financial-Services.pdf). See the [AWS Security Blog announcement](https://aws.amazon.com/blogs/security/introducing-the-updated-aws-user-guide-to-governance-risk-and-compliance-for-responsible-ai-adoption/) for context on the updated guide. **Deployment Options:** - **Single-Account**: Assess security in one AWS account @@ -276,7 +276,7 @@ You can check the AWS CodeBuild console to confirm the assessment completed succ 2. **Navigate to the Amazon S3 Bucket**: - Go to **Amazon S3** in the AWS Console - Search for and open your assessment bucket - - For single-account deployments, open the `security_assessment_XXXXX.html` report + - For single-account deployments, open the `{account_id}/` folder and then open the `security_assessment_single_account_YYYYMMDD_HHMMSS.html` report - For multi-account deployments, follow the [Report Structure](#report-structure) guidance below ### Report Structure @@ -372,11 +372,11 @@ The deployment uses multiple IAM roles with different trust and permission bound If you need to reduce scope, review the role policies in: -- [deployment/aiml-security-single-account.yaml](/Users/akothurk/Documents/Code/Github/aws-samples/sample-aiml-security-assessment/deployment/aiml-security-single-account.yaml) -- [deployment/1-aiml-security-member-roles.yaml](/Users/akothurk/Documents/Code/Github/aws-samples/sample-aiml-security-assessment/deployment/1-aiml-security-member-roles.yaml) -- [deployment/2-aiml-security-codebuild.yaml](/Users/akothurk/Documents/Code/Github/aws-samples/sample-aiml-security-assessment/deployment/2-aiml-security-codebuild.yaml) -- [aiml-security-assessment/template.yaml](/Users/akothurk/Documents/Code/Github/aws-samples/sample-aiml-security-assessment/aiml-security-assessment/template.yaml) -- [aiml-security-assessment/template-multi-account.yaml](/Users/akothurk/Documents/Code/Github/aws-samples/sample-aiml-security-assessment/aiml-security-assessment/template-multi-account.yaml) +- [deployment/aiml-security-single-account.yaml](deployment/aiml-security-single-account.yaml) +- [deployment/1-aiml-security-member-roles.yaml](deployment/1-aiml-security-member-roles.yaml) +- [deployment/2-aiml-security-codebuild.yaml](deployment/2-aiml-security-codebuild.yaml) +- [aiml-security-assessment/template.yaml](aiml-security-assessment/template.yaml) +- [aiml-security-assessment/template-multi-account.yaml](aiml-security-assessment/template-multi-account.yaml) --- From 3e70eed8823f682d6502a04c8fdd3726378d0a9e Mon Sep 17 00:00:00 2001 From: Agasthi Kothurkar Date: Mon, 22 Jun 2026 20:49:35 -0400 Subject: [PATCH 24/30] Fix duplicate Bedrock test name --- tests/test_bedrock_checks.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_bedrock_checks.py b/tests/test_bedrock_checks.py index 4f0695a..31c7242 100644 --- a/tests/test_bedrock_checks.py +++ b/tests/test_bedrock_checks.py @@ -693,7 +693,7 @@ def test_br09_kb_exists_returns_findings(self, mock_client): assert findings[0]["Check_ID"] == "BR-09" @patch("boto3.client") - def test_br09_access_denied_returns_na(self, mock_client): + def test_br09_access_denied_in_region_returns_na(self, mock_client): check = bedrock_app.check_bedrock_knowledge_base_encryption mock_agent = MagicMock() mock_client.return_value = mock_agent From 61d48db8240f723eded2a844bba910bd2d7b0957 Mon Sep 17 00:00:00 2001 From: Agasthi Kothurkar Date: Mon, 22 Jun 2026 21:13:35 -0400 Subject: [PATCH 25/30] Fix AgentCore pagination test hang --- .../functions/security/agentcore_assessments/app.py | 12 ++++++++++++ tests/test_bedrock_checks.py | 6 +++++- 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/aiml-security-assessment/functions/security/agentcore_assessments/app.py b/aiml-security-assessment/functions/security/agentcore_assessments/app.py index 3c3d85b..d670109 100644 --- a/aiml-security-assessment/functions/security/agentcore_assessments/app.py +++ b/aiml-security-assessment/functions/security/agentcore_assessments/app.py @@ -140,6 +140,12 @@ def _agentcore_list_all( kwargs["nextToken"] = next_token response = list_method(**kwargs) + if not isinstance(response, dict): + logger.warning( + f"{list_method_name} returned unexpected response type: " + f"{type(response).__name__}" + ) + break for result_key in result_keys: page_items = response.get(result_key) @@ -148,6 +154,12 @@ def _agentcore_list_all( break next_token = response.get("nextToken") + if next_token is not None and not isinstance(next_token, str): + logger.warning( + f"{list_method_name} returned non-string nextToken: " + f"{type(next_token).__name__}" + ) + break if not next_token: break diff --git a/tests/test_bedrock_checks.py b/tests/test_bedrock_checks.py index 31c7242..cd909c7 100644 --- a/tests/test_bedrock_checks.py +++ b/tests/test_bedrock_checks.py @@ -712,7 +712,11 @@ def test_br09_access_denied_in_region_returns_na(self, mock_client): findings = extract_csv_data(result) assert len(findings) >= 1 assert findings[0]["Status"] == "N/A" - assert "Bedrock Knowledge Base API" in findings[0]["Finding_Details"] + assert ( + "access to Knowledge Base metadata was denied" + in findings[0]["Finding_Details"] + ) + assert findings[0]["Region"] == "eu-west-1" @patch("boto3.client") def test_br09_exception_returns_error_finding(self, mock_client): From 9b41a7eaf38ea39130c875ec174f6713d5c9cc30 Mon Sep 17 00:00:00 2001 From: Agasthi Kothurkar Date: Mon, 22 Jun 2026 21:29:35 -0400 Subject: [PATCH 26/30] Fix FinServ test patch targets --- .../functions/security/finserv_tests/test_checks.py | 12 ++++++------ .../security/finserv_tests/test_lambda_handler.py | 10 +++++----- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/aiml-security-assessment/functions/security/finserv_tests/test_checks.py b/aiml-security-assessment/functions/security/finserv_tests/test_checks.py index bc0e846..3b693d3 100644 --- a/aiml-security-assessment/functions/security/finserv_tests/test_checks.py +++ b/aiml-security-assessment/functions/security/finserv_tests/test_checks.py @@ -3507,7 +3507,7 @@ def factory(service_name, **kwargs): return factory - @patch("app.boto3.client") + @patch("finserv_app.boto3.client") def test_detect_returns_true_when_any_genai_resource_exists(self, mock_client): bedrock = MagicMock() bedrock.list_guardrails.return_value = {"guardrails": [{"id": "gr-1"}]} @@ -3524,7 +3524,7 @@ def test_detect_returns_true_when_any_genai_resource_exists(self, mock_client): "bedrock", config=app.boto3_config, region_name="us-east-1" ) - @patch("app.boto3.client") + @patch("finserv_app.boto3.client") def test_detect_returns_false_when_all_supported_probes_are_empty( self, mock_client ): @@ -3550,7 +3550,7 @@ def test_detect_returns_false_when_all_supported_probes_are_empty( assert app.detect_finserv_regional_footprint("us-west-2") is False - @patch("app.boto3.client") + @patch("finserv_app.boto3.client") def test_detect_returns_none_when_footprint_is_indeterminate(self, mock_client): bedrock = MagicMock() bedrock.list_guardrails.side_effect = _client_error("AccessDeniedException") @@ -3580,7 +3580,7 @@ def test_detect_returns_none_when_footprint_is_indeterminate(self, mock_client): assert app.detect_finserv_regional_footprint("eu-west-1") is None - @patch("app.detect_finserv_regional_footprint") + @patch("finserv_app.detect_finserv_regional_footprint") def test_partition_keeps_indeterminate_regions_in_scope(self, mock_detect): mock_detect.side_effect = [None, False, True] @@ -3694,7 +3694,7 @@ def test_apply_region_scope_does_not_copy_failures_to_empty_regions(self): ] with patch( - "app._partition_regions_by_finserv_footprint", + "finserv_app._partition_regions_by_finserv_footprint", return_value=(["region-with-resources"], ["region-without-resources"]), ): app._apply_region_scope( @@ -3729,7 +3729,7 @@ def test_apply_region_scope_suppresses_unscoped_rows_when_all_regions_empty(self ] with patch( - "app._partition_regions_by_finserv_footprint", + "finserv_app._partition_regions_by_finserv_footprint", return_value=([], ["region-a", "region-b"]), ): app._apply_region_scope(findings, ["region-a", "region-b"]) diff --git a/aiml-security-assessment/functions/security/finserv_tests/test_lambda_handler.py b/aiml-security-assessment/functions/security/finserv_tests/test_lambda_handler.py index 54b29b0..264904e 100644 --- a/aiml-security-assessment/functions/security/finserv_tests/test_lambda_handler.py +++ b/aiml-security-assessment/functions/security/finserv_tests/test_lambda_handler.py @@ -274,11 +274,11 @@ def test_handler_passes_inventory_to_build_finserv_checks( "lambda_handler must never pass None as the inventory argument" ) - @patch("app.write_to_s3") - @patch("app.get_permissions_cache") - @patch("app._apply_region_scope") - @patch("app.build_finserv_checks") - @patch("app.collect_resource_inventory") + @patch("finserv_app.write_to_s3") + @patch("finserv_app.get_permissions_cache") + @patch("finserv_app._apply_region_scope") + @patch("finserv_app.build_finserv_checks") + @patch("finserv_app.collect_resource_inventory") def test_handler_scopes_findings_with_target_regions_from_event( self, mock_collect, From 5c36c296b8b858d8994c09129412d5f4d1abc418 Mon Sep 17 00:00:00 2001 From: Agasthi Kothurkar Date: Tue, 23 Jun 2026 17:47:12 -0400 Subject: [PATCH 27/30] Fix empty TargetRegions SAM deploy override --- buildspec.yml | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/buildspec.yml b/buildspec.yml index 9e8ac31..417eb4b 100644 --- a/buildspec.yml +++ b/buildspec.yml @@ -59,6 +59,11 @@ phases: exit 1 fi echo "Using TARGET_REGIONS: '${TARGET_REGIONS:-}'" + + # Use the long-form SAM parameter syntax so an intentionally empty + # TargetRegions value is accepted and clears any previous stack value. + SAM_TARGET_REGIONS_PARAMETER="ParameterKey=TargetRegions,ParameterValue=${TARGET_REGIONS}" + export SAM_TARGET_REGIONS_PARAMETER - echo "Multi-account scan is $MULTI_ACCOUNT_SCAN" - cd aiml-security-assessment - ls -la @@ -134,7 +139,7 @@ phases: fi echo "Deploying to account $accountId" - if ! sam deploy --template-file .aws-sam/build/template.yaml --stack-name aiml-security-$accountId --capabilities CAPABILITY_IAM --no-confirm-changeset --no-fail-on-empty-changeset --resolve-s3 --region $AWS_DEFAULT_REGION --parameter-overrides TargetRegions="${TARGET_REGIONS:-}"; then + if ! sam deploy --template-file .aws-sam/build/template.yaml --stack-name "aiml-security-$accountId" --capabilities CAPABILITY_IAM --no-confirm-changeset --no-fail-on-empty-changeset --resolve-s3 --region "$AWS_DEFAULT_REGION" --parameter-overrides "$SAM_TARGET_REGIONS_PARAMETER"; then echo "ERROR: Deploy failed for account $accountId" failed_accounts+=($accountId) unset AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY AWS_SESSION_TOKEN @@ -165,7 +170,7 @@ phases: aws cloudformation delete-stack --stack-name aws-sam-cli-managed-default aws cloudformation wait stack-delete-complete --stack-name aws-sam-cli-managed-default 2>/dev/null || true fi - if ! sam deploy --template-file .aws-sam/build/template.yaml --stack-name aiml-security-mgmt --capabilities CAPABILITY_IAM --no-confirm-changeset --no-fail-on-empty-changeset --resolve-s3 --region $AWS_DEFAULT_REGION --parameter-overrides TargetRegions="${TARGET_REGIONS:-}"; then + if ! sam deploy --template-file .aws-sam/build/template.yaml --stack-name aiml-security-mgmt --capabilities CAPABILITY_IAM --no-confirm-changeset --no-fail-on-empty-changeset --resolve-s3 --region "$AWS_DEFAULT_REGION" --parameter-overrides "$SAM_TARGET_REGIONS_PARAMETER"; then echo "ERROR: Deploy failed for management account" failed_accounts+=($AWS_ACCOUNT_ID) else @@ -190,7 +195,7 @@ phases: else echo "Single account deployment" STACK_NAME="aiml-sec-${AWS_ACCOUNT_ID}" - if ! sam deploy --template-file .aws-sam/build/template.yaml --stack-name $STACK_NAME --capabilities CAPABILITY_IAM --no-confirm-changeset --no-fail-on-empty-changeset --resolve-s3 --parameter-overrides TargetRegions="${TARGET_REGIONS:-}"; then + if ! sam deploy --template-file .aws-sam/build/template.yaml --stack-name "$STACK_NAME" --capabilities CAPABILITY_IAM --no-confirm-changeset --no-fail-on-empty-changeset --resolve-s3 --region "$AWS_DEFAULT_REGION" --parameter-overrides "$SAM_TARGET_REGIONS_PARAMETER"; then echo "ERROR: SAM deploy failed for single account" exit 1 fi From 20d71be7f9ded63c5ea8742aa7fbfd2c76ea06b5 Mon Sep 17 00:00:00 2001 From: Agasthi Kothurkar Date: Tue, 23 Jun 2026 18:39:09 -0400 Subject: [PATCH 28/30] Normalize multi-value deployment inputs --- README.md | 10 +++--- .../security/finserv_assessments/app.py | 3 +- .../security/finserv_tests/test_checks.py | 10 ++++++ .../functions/security/resolve_regions/app.py | 3 +- .../template-multi-account.yaml | 11 +++--- aiml-security-assessment/template.yaml | 11 +++--- buildspec.yml | 35 ++++++++++++++----- deployment/1-aiml-security-member-roles.yaml | 7 ++-- deployment/2-aiml-security-codebuild.yaml | 17 ++++----- deployment/aiml-security-single-account.yaml | 11 +++--- docs/DEVELOPER_GUIDE.md | 4 +-- docs/TROUBLESHOOTING.md | 18 +++++----- 12 files changed, 89 insertions(+), 51 deletions(-) diff --git a/README.md b/README.md index c8870cf..3508f66 100644 --- a/README.md +++ b/README.md @@ -142,7 +142,7 @@ This tool operates within the [AWS Shared Responsibility Model](https://aws.amaz 4. Optionally specify your email address to receive notifications. 5. **(Optional) Multi-Region**: Set `TargetRegions` to scan multiple regions: - Leave empty to scan only the deployment region (default) - - Comma-separated list (for example, `us-east-1,us-west-2,eu-west-1`) + - Comma- or space-separated list (for example, `us-east-1,us-west-2,eu-west-1` or `us-east-1 us-west-2 eu-west-1`) - `all` to scan all regions where the services are available 6. Acknowledge IAM capabilities and click **Submit**. 7. Once complete, CodeBuild automatically runs the assessment. @@ -158,14 +158,14 @@ This tool operates within the [AWS Shared Responsibility Model](https://aws.amaz Deploy [1-aiml-security-member-roles.yaml](deployment/1-aiml-security-member-roles.yaml) to all target accounts using CloudFormation StackSets with service-managed permissions. -1. Navigate to **CloudFormation** > **StackSets** in the management account -2. Upload the template and set `ManagementAccountID` to your management account +1. Navigate to **CloudFormation** > **StackSets** in the AWS Organizations management account or delegated administrator account +2. Upload the template and set `ManagementAccountID` to the account ID where the central multi-account CodeBuild project runs 3. Select **Service-managed permissions** and target your OUs 4. Select your target region and submit ### Step 2: Deploy Central Infrastructure -Deploy [2-aiml-security-codebuild.yaml](deployment/2-aiml-security-codebuild.yaml) in your management account. +Deploy [2-aiml-security-codebuild.yaml](deployment/2-aiml-security-codebuild.yaml) in your central assessment account. This can be your AWS Organizations management account or a delegated administrator/central tooling account. 1. Upload the template and set `MultiAccountScan` to `true` 2. Optionally set `TargetRegions` for multi-region scanning @@ -182,7 +182,7 @@ Both deployment modes support scanning multiple AWS regions in parallel via the | Value | Behavior | |-------|----------| | Empty (default) | Scans deployment region only — fully backward compatible | -| Comma-separated (for example, `us-east-1,us-west-2`) | Scans those regions in parallel | +| Comma- or space-separated (for example, `us-east-1,us-west-2` or `us-east-1 us-west-2`) | Scans those regions in parallel | | `all` | Discovers and scans all regions where assessed services are available | Scanning uses a Step Functions Map state, so multiple regions execute in parallel with no additional time cost. Services unavailable in a region produce an informational N/A finding. diff --git a/aiml-security-assessment/functions/security/finserv_assessments/app.py b/aiml-security-assessment/functions/security/finserv_assessments/app.py index d7d42ac..abfcd39 100644 --- a/aiml-security-assessment/functions/security/finserv_assessments/app.py +++ b/aiml-security-assessment/functions/security/finserv_assessments/app.py @@ -57,6 +57,7 @@ import json import logging import os +import re from dataclasses import dataclass from datetime import datetime, timezone from io import StringIO @@ -6107,7 +6108,7 @@ def _normalized_target_regions(value: str) -> List[str]: value = (value or "").strip() if not value or value.lower() == "all": return [] - return [region.strip() for region in value.split(",") if region.strip()] + return [region.strip() for region in re.split(r"[,\s]+", value) if region.strip()] def _get_region_scopes(event: Dict[str, Any]) -> List[str]: diff --git a/aiml-security-assessment/functions/security/finserv_tests/test_checks.py b/aiml-security-assessment/functions/security/finserv_tests/test_checks.py index 3b693d3..bd5fb37 100644 --- a/aiml-security-assessment/functions/security/finserv_tests/test_checks.py +++ b/aiml-security-assessment/functions/security/finserv_tests/test_checks.py @@ -3640,6 +3640,16 @@ def test_region_scopes_use_target_regions_env_when_event_list_absent( "region-b", ] + def test_region_scopes_accept_whitespace_delimited_target_regions( + self, monkeypatch + ): + monkeypatch.setenv("TARGET_REGIONS", "region-a region-b") + + assert app._get_region_scopes({"Region": "fallback-region"}) == [ + "region-a", + "region-b", + ] + def test_stamp_regions_expands_missing_csv_regions(self): findings = [ { diff --git a/aiml-security-assessment/functions/security/resolve_regions/app.py b/aiml-security-assessment/functions/security/resolve_regions/app.py index 027b863..4dd9297 100644 --- a/aiml-security-assessment/functions/security/resolve_regions/app.py +++ b/aiml-security-assessment/functions/security/resolve_regions/app.py @@ -8,6 +8,7 @@ import os import logging +import re import boto3 logger = logging.getLogger() @@ -50,7 +51,7 @@ def resolve_regions(): return [current_region] return regions - return [r.strip() for r in target_regions.split(",") if r.strip()] + return [r.strip() for r in re.split(r"[,\s]+", target_regions) if r.strip()] def lambda_handler(event, context): diff --git a/aiml-security-assessment/template-multi-account.yaml b/aiml-security-assessment/template-multi-account.yaml index f7f135b..34b054e 100644 --- a/aiml-security-assessment/template-multi-account.yaml +++ b/aiml-security-assessment/template-multi-account.yaml @@ -23,13 +23,14 @@ Parameters: TargetRegions: Type: String Default: "" - AllowedPattern: "^$|^all$|^[a-z]{2}(-gov)?-[a-z]+-[0-9](,[a-z]{2}(-gov)?-[a-z]+-[0-9])*$" + AllowedPattern: "^$|^all$|^[a-z]{2}(-gov)?-[a-z]+-[0-9]([,\\s]+[a-z]{2}(-gov)?-[a-z]+-[0-9])*$" ConstraintDescription: > - TargetRegions must be empty, 'all', or a comma-separated list of AWS - region names with no spaces or trailing commas (for example, - us-east-1,us-west-2,eu-west-1). + TargetRegions must be empty, 'all', or a comma- or space-separated list of AWS + region names (for example, us-east-1,us-west-2,eu-west-1 or + us-east-1 us-west-2 eu-west-1). Description: > - Comma-separated list of AWS regions to scan (e.g., us-east-1,us-west-2,eu-west-1). + Comma- or space-separated list of AWS regions to scan (e.g., + us-east-1,us-west-2,eu-west-1 or us-east-1 us-west-2 eu-west-1). Use 'all' to scan all regions where the service is available. Leave empty to scan only the deployment region. MaxRegionConcurrency: diff --git a/aiml-security-assessment/template.yaml b/aiml-security-assessment/template.yaml index c6bf1a4..c6bd63d 100644 --- a/aiml-security-assessment/template.yaml +++ b/aiml-security-assessment/template.yaml @@ -23,13 +23,14 @@ Parameters: TargetRegions: Type: String Default: "" - AllowedPattern: "^$|^all$|^[a-z]{2}(-gov)?-[a-z]+-[0-9](,[a-z]{2}(-gov)?-[a-z]+-[0-9])*$" + AllowedPattern: "^$|^all$|^[a-z]{2}(-gov)?-[a-z]+-[0-9]([,\\s]+[a-z]{2}(-gov)?-[a-z]+-[0-9])*$" ConstraintDescription: > - TargetRegions must be empty, 'all', or a comma-separated list of AWS - region names with no spaces or trailing commas (for example, - us-east-1,us-west-2,eu-west-1). + TargetRegions must be empty, 'all', or a comma- or space-separated list of AWS + region names (for example, us-east-1,us-west-2,eu-west-1 or + us-east-1 us-west-2 eu-west-1). Description: > - Comma-separated list of AWS regions to scan (e.g., us-east-1,us-west-2,eu-west-1). + Comma- or space-separated list of AWS regions to scan (e.g., + us-east-1,us-west-2,eu-west-1 or us-east-1 us-west-2 eu-west-1). Use 'all' to scan all regions where the service is available. Leave empty to scan only the deployment region. MaxRegionConcurrency: diff --git a/buildspec.yml b/buildspec.yml index 417eb4b..5c7eb46 100644 --- a/buildspec.yml +++ b/buildspec.yml @@ -46,20 +46,39 @@ phases: exit 1 fi - | - # Normalize TARGET_REGIONS: strip all whitespace so the comma-separated - # value survives 'sam deploy --parameter-overrides', which re-splits its - # argument on spaces and would otherwise drop every region after the first. - TARGET_REGIONS=$(echo "${TARGET_REGIONS:-}" | tr -d '[:space:]') + normalize_delimited_list() { + local value="$1" + value=$(printf '%s' "$value" | sed -E 's/[[:space:]]*,[[:space:]]*/,/g; s/^[[:space:]]+//; s/[[:space:]]+$//; s/[[:space:]]+/,/g; s/,+/,/g; s/^,//; s/,$//') + printf '%s' "$value" + } + + # Accept comma- or whitespace-delimited input from CloudFormation, then + # normalize to comma-delimited values for SAM and Lambda configuration. + TARGET_REGIONS=$(normalize_delimited_list "${TARGET_REGIONS:-}") + if [[ "$(printf '%s' "$TARGET_REGIONS" | tr '[:upper:]' '[:lower:]')" == "all" ]]; then + TARGET_REGIONS="all" + fi export TARGET_REGIONS + MULTI_ACCOUNT_LIST_OVERRIDE=$(normalize_delimited_list "${MULTI_ACCOUNT_LIST_OVERRIDE:-}") + export MULTI_ACCOUNT_LIST_OVERRIDE + # Validate the normalized value matches the CloudFormation AllowedPattern. - if [[ -n "$TARGET_REGIONS" && ! "$TARGET_REGIONS" =~ ^[a-zA-Z0-9,-]+$ ]]; then + region_pattern='^all$|^[a-z]{2}(-gov)?-[a-z]+-[0-9](,[a-z]{2}(-gov)?-[a-z]+-[0-9])*$' + if [[ -n "$TARGET_REGIONS" && ! "$TARGET_REGIONS" =~ $region_pattern ]]; then echo "ERROR: TARGET_REGIONS contains invalid characters: '$TARGET_REGIONS'" - echo "Expected a comma-separated list of region names (e.g., us-east-1,us-west-2), 'all', or empty." + echo "Expected a comma- or space-separated list of region names (e.g., us-east-1,us-west-2), 'all', or empty." exit 1 fi echo "Using TARGET_REGIONS: '${TARGET_REGIONS:-}'" + account_list_pattern='^[0-9]{12}(,[0-9]{12})*$' + if [[ -n "$MULTI_ACCOUNT_LIST_OVERRIDE" && ! "$MULTI_ACCOUNT_LIST_OVERRIDE" =~ $account_list_pattern ]]; then + echo "ERROR: MULTI_ACCOUNT_LIST_OVERRIDE contains invalid account IDs: '$MULTI_ACCOUNT_LIST_OVERRIDE'" + echo "Expected a comma- or space-separated list of 12-digit AWS account IDs, or empty." + exit 1 + fi + # Use the long-form SAM parameter syntax so an intentionally empty # TargetRegions value is accepted and clears any previous stack value. SAM_TARGET_REGIONS_PARAMETER="ParameterKey=TargetRegions,ParameterValue=${TARGET_REGIONS}" @@ -88,7 +107,7 @@ phases: echo "Getting list of accounts to scan" if [[ $MULTI_ACCOUNT_LIST_OVERRIDE != '' ]]; then echo "Using account overrides." - account_list=$MULTI_ACCOUNT_LIST_OVERRIDE + account_list=${MULTI_ACCOUNT_LIST_OVERRIDE//,/ } else echo "Using accounts from AWS Organizations." if ! account_list=$(aws organizations list-accounts --query 'Accounts[?Status==`ACTIVE`].Id' --output text); then @@ -245,7 +264,7 @@ phases: if [[ $MULTI_ACCOUNT_SCAN = 'true' ]]; then echo "Getting list of accounts for post-build processing" if [[ $MULTI_ACCOUNT_LIST_OVERRIDE != '' ]]; then - account_list=$MULTI_ACCOUNT_LIST_OVERRIDE + account_list=${MULTI_ACCOUNT_LIST_OVERRIDE//,/ } else if ! account_list=$(aws organizations list-accounts --query 'Accounts[?Status==`ACTIVE`].Id' --output text); then echo "ERROR: Failed to retrieve account list from AWS Organizations" diff --git a/deployment/1-aiml-security-member-roles.yaml b/deployment/1-aiml-security-member-roles.yaml index 6d35b87..66b8516 100644 --- a/deployment/1-aiml-security-member-roles.yaml +++ b/deployment/1-aiml-security-member-roles.yaml @@ -10,11 +10,14 @@ Metadata: - ManagementAccountID ParameterLabels: ManagementAccountID: - default: Management account ID + default: Central assessment account ID Parameters: ManagementAccountID: - Description: "The 12-digit AWS account ID where the multi-account AIML Security Assessment CodeBuild project runs." + Description: > + The 12-digit AWS account ID where the multi-account AIML Security + Assessment CodeBuild project runs. This can be the AWS Organizations + management account or a delegated administrator/central tooling account. Type: String AllowedPattern: "^\\d{12}$" ConstraintDescription: Enter a 12-digit AWS account ID with no spaces. diff --git a/deployment/2-aiml-security-codebuild.yaml b/deployment/2-aiml-security-codebuild.yaml index 7ec63dc..f4a1ee8 100644 --- a/deployment/2-aiml-security-codebuild.yaml +++ b/deployment/2-aiml-security-codebuild.yaml @@ -77,13 +77,13 @@ Parameters: MultiAccountListOverride: Description: > - Optional space-delimited list of 12-digit AWS account IDs to scan. Leave + Optional comma- or space-separated list of 12-digit AWS account IDs to scan. Leave blank to scan all active accounts in your organization when MultiAccountScan is true. Type: String Default: "" - AllowedPattern: "^$|^\\d{12}( \\d{12})*$" - ConstraintDescription: Must be empty or a space-delimited list of 12-digit AWS account IDs. + AllowedPattern: "^$|^\\d{12}([,\\s]+\\d{12})*$" + ConstraintDescription: Must be empty or a comma- or space-separated list of 12-digit AWS account IDs. EmailAddress: Description: "Specify an address if you want to receive an email when the @@ -106,13 +106,14 @@ Parameters: TargetRegions: Type: String - AllowedPattern: "^$|^all$|^[a-z]{2}(-gov)?-[a-z]+-[0-9](,[a-z]{2}(-gov)?-[a-z]+-[0-9])*$" + AllowedPattern: "^$|^all$|^[a-z]{2}(-gov)?-[a-z]+-[0-9]([,\\s]+[a-z]{2}(-gov)?-[a-z]+-[0-9])*$" ConstraintDescription: > - TargetRegions must be empty, 'all', or a comma-separated list of AWS - region names with no spaces or trailing commas (for example, - us-east-1,us-west-2,eu-west-1). + TargetRegions must be empty, 'all', or a comma- or space-separated list of AWS + region names (for example, us-east-1,us-west-2,eu-west-1 or + us-east-1 us-west-2 eu-west-1). Description: > - Comma-separated list of AWS regions to scan (e.g., us-east-1,us-west-2,eu-west-1). + Comma- or space-separated list of AWS regions to scan (e.g., + us-east-1,us-west-2,eu-west-1 or us-east-1 us-west-2 eu-west-1). Use 'all' to scan all regions where the service is available. Leave empty to scan only the deployment region. Default: "" diff --git a/deployment/aiml-security-single-account.yaml b/deployment/aiml-security-single-account.yaml index fd761f7..0997c56 100644 --- a/deployment/aiml-security-single-account.yaml +++ b/deployment/aiml-security-single-account.yaml @@ -54,13 +54,14 @@ Parameters: TargetRegions: Type: String - AllowedPattern: "^$|^all$|^[a-z]{2}(-gov)?-[a-z]+-[0-9](,[a-z]{2}(-gov)?-[a-z]+-[0-9])*$" + AllowedPattern: "^$|^all$|^[a-z]{2}(-gov)?-[a-z]+-[0-9]([,\\s]+[a-z]{2}(-gov)?-[a-z]+-[0-9])*$" ConstraintDescription: > - TargetRegions must be empty, 'all', or a comma-separated list of AWS - region names with no spaces or trailing commas (for example, - us-east-1,us-west-2,eu-west-1). + TargetRegions must be empty, 'all', or a comma- or space-separated list of AWS + region names (for example, us-east-1,us-west-2,eu-west-1 or + us-east-1 us-west-2 eu-west-1). Description: > - Comma-separated list of AWS regions to scan (e.g., us-east-1,us-west-2,eu-west-1). + Comma- or space-separated list of AWS regions to scan (e.g., + us-east-1,us-west-2,eu-west-1 or us-east-1 us-west-2 eu-west-1). Use 'all' to scan all regions where the service is available. Leave empty to scan only the deployment region. Default: "" diff --git a/docs/DEVELOPER_GUIDE.md b/docs/DEVELOPER_GUIDE.md index 330c9ed..010be26 100644 --- a/docs/DEVELOPER_GUIDE.md +++ b/docs/DEVELOPER_GUIDE.md @@ -50,7 +50,7 @@ The AI/ML Security Assessment Framework is a serverless, multi-account security - Runtime assessment Lambda roles are read-oriented and scoped to the APIs needed by each assessment - AWS CodeBuild and member-account roles require deployment permissions because they create or update the SAM assessment stacks before running checks -- Cross-account trust is limited to the specific AWS CodeBuild role in the management account +- Cross-account trust is limited to the specific AWS CodeBuild role in the central assessment account - Amazon S3 buckets enforce SSL-only access - Assessment data is encrypted in transit and at rest - No persistent credentials are stored in AWS CodeBuild @@ -72,7 +72,7 @@ The AI/ML Security Assessment Framework is a serverless, multi-account security #### Step 1: Member Account Roles (`1-aiml-security-member-roles.yaml`) - **AWS CloudFormation StackSets Deployment**: Deploys `AIMLSecurityMemberRole` to all target accounts -- **Cross-Account Trust**: Establishes trust relationship with central management account +- **Cross-Account Trust**: Establishes trust relationship with the central assessment account - **Assessment and Deployment Permissions**: Grants read-oriented service permissions for assessment checks and deployment permissions needed for CodeBuild to create or update per-account SAM stacks #### Step 2: Central Infrastructure (`2-aiml-security-codebuild.yaml`) diff --git a/docs/TROUBLESHOOTING.md b/docs/TROUBLESHOOTING.md index 5128e2c..09e29bb 100644 --- a/docs/TROUBLESHOOTING.md +++ b/docs/TROUBLESHOOTING.md @@ -23,7 +23,7 @@ This guide covers common issues, debugging tips, and frequently asked questions **Solutions:** - Check that service-linked roles exist for AWS CloudFormation StackSets -- Verify the management account has AWS Organizations permissions +- Verify the account running the central CodeBuild project has AWS Organizations permissions, if it is discovering accounts automatically - Verify target OUs contain active accounts - Review the StackSet operation history for specific error messages @@ -34,7 +34,7 @@ This guide covers common issues, debugging tips, and frequently asked questions **Solutions:** - Verify the `AIMLSecurityMemberRole` exists in target accounts - Check the trust relationship allows the central CodeBuild role -- Confirm the `ManagementAccountID` parameter matches your management account +- Confirm the `ManagementAccountID` parameter matches the account where the central `MultiAccountCodeBuildRole` runs - Verify the StackSet deployment completed successfully in all accounts ### 3. AWS SAM Deployment Failures @@ -47,7 +47,7 @@ This guide covers common issues, debugging tips, and frequently asked questions - Verify the S3 bucket for SAM artifacts exists and is accessible. If the `aws-sam-cli-managed-default` stack is stuck in `ROLLBACK_COMPLETE` or `DELETE_FAILED`, delete it and re-run CodeBuild - Look for IAM permission errors in the logs - Check if a previous deployment left orphaned resources -- Check whether `TARGET_REGIONS` failed validation. It must be empty, `all`, or a comma-separated list such as `us-east-1,us-west-2` +- Check whether `TARGET_REGIONS` failed validation. It must be empty, `all`, or a comma- or space-separated list such as `us-east-1,us-west-2` or `us-east-1 us-west-2` ### 4. AWS Step Functions Execution Failures @@ -118,7 +118,7 @@ For full bucket cleanup guidance, see [Cleanup Guide](CLEANUP.md#emptying-and-de **Solutions:** - Leave `TargetRegions` empty to scan only the deployment region - Use `all` to scan the union of regions returned by boto3 for Amazon Bedrock, Amazon SageMaker AI, and Amazon Bedrock AgentCore -- Use a comma-separated list with no spaces, such as `us-east-1,us-west-2,eu-west-1`. CloudFormation validation rejects spaces and trailing commas +- Use a comma- or space-separated list, such as `us-east-1,us-west-2,eu-west-1` or `us-east-1 us-west-2 eu-west-1`. The deployment normalizes the value before passing it to SAM - Confirm the services being assessed are available in each target region. If a service is unavailable or has no resources in a region, the report can show `N/A` or no resource-specific findings for that service and region - Confirm the account is opted in to any opt-in regions you include @@ -148,7 +148,7 @@ For full bucket cleanup guidance, see [Cleanup Guide](CLEANUP.md#emptying-and-de - For organizations with fewer than 10 accounts, the default "Three" is usually sufficient - If builds timeout, increase `ConcurrentAccountScans` to process more accounts in parallel -- but be aware this also increases the per-minute CodeBuild cost - If builds timeout even at "Twelve," increase the `CodeBuildTimeout` parameter (default is 300 minutes for multi-account) -- For very large organizations (100+ accounts), consider using `MultiAccountListOverride` to split assessments into batches +- For very large organizations (100+ accounts), consider using `MultiAccountListOverride` to split assessments into batches. It accepts comma- or space-separated account IDs ### 10. No Reports in S3 Bucket @@ -183,7 +183,7 @@ For full bucket cleanup guidance, see [Cleanup Guide](CLEANUP.md#emptying-and-de 1. Navigate to **AWS CloudFormation** > **Stacks** 2. Select your infrastructure stack (for example, `aiml-security-single-account` or `aiml-security-multi-account`) 3. Click **Update** > **Use current template** -4. Set the `TargetRegions` parameter (for example, `us-east-1,us-west-2,eu-west-1` or `all`) +4. Set the `TargetRegions` parameter (for example, `us-east-1,us-west-2,eu-west-1`, `us-east-1 us-west-2 eu-west-1`, or `all`) 5. Click through to **Submit** 6. The next assessment run will scan the specified regions in parallel @@ -217,12 +217,12 @@ The trust policy should allow the central CodeBuild role: { "Effect": "Allow", "Principal": { - "AWS": "arn:aws:iam:::root" + "AWS": "arn:aws:iam:::root" }, "Action": "sts:AssumeRole", "Condition": { "ArnEquals": { - "aws:PrincipalArn": "arn:aws:iam:::role/service-role/MultiAccountCodeBuildRole" + "aws:PrincipalArn": "arn:aws:iam:::role/service-role/MultiAccountCodeBuildRole" } } } @@ -280,7 +280,7 @@ You can automate regular assessments using Amazon EventBridge scheduled rules. **Q: What AWS regions are supported?** -A: The framework is designed for standard AWS commercial regions where Amazon Bedrock, Amazon SageMaker AI, or Amazon Bedrock AgentCore are available. Leave `TargetRegions` empty for the deployment region, set it to `all` to resolve the union of assessed-service regions, or provide an explicit comma-separated list. AWS GovCloud and AWS China regions may require template modifications. +A: The framework is designed for standard AWS commercial regions where Amazon Bedrock, Amazon SageMaker AI, or Amazon Bedrock AgentCore are available. Leave `TargetRegions` empty for the deployment region, set it to `all` to resolve the union of assessed-service regions, or provide an explicit comma- or space-separated list. AWS GovCloud and AWS China regions may require template modifications. **Q: Does this work if I don't have any AI/ML resources deployed yet?** From 0072e0273126c5336a77e4bcc36723427d564bcd Mon Sep 17 00:00:00 2001 From: Agasthi Kothurkar Date: Wed, 24 Jun 2026 13:44:35 -0400 Subject: [PATCH 29/30] Fix AgentCore gateway lookup parameter --- .../security/agentcore_assessments/app.py | 8 +++++-- tests/test_agentcore_checks.py | 24 +++++++++++++++++++ 2 files changed, 30 insertions(+), 2 deletions(-) diff --git a/aiml-security-assessment/functions/security/agentcore_assessments/app.py b/aiml-security-assessment/functions/security/agentcore_assessments/app.py index d670109..595f9be 100644 --- a/aiml-security-assessment/functions/security/agentcore_assessments/app.py +++ b/aiml-security-assessment/functions/security/agentcore_assessments/app.py @@ -1868,7 +1868,9 @@ def check_agentcore_resource_based_policies() -> List[Dict[str, Any]]: gateway_name = gateway.get("name", gateway_id) try: - gateway_details = agentcore_client.get_gateway(gatewayId=gateway_id) + gateway_details = agentcore_client.get_gateway( + gatewayIdentifier=gateway_id + ) gateway_arn = gateway_details.get("gatewayArn") if not gateway_arn: @@ -2233,7 +2235,9 @@ def check_agentcore_gateway_encryption() -> List[Dict[str, Any]]: gateway_name = gateway.get("name", gateway_id) try: - gateway_details = agentcore_client.get_gateway(gatewayId=gateway_id) + gateway_details = agentcore_client.get_gateway( + gatewayIdentifier=gateway_id + ) # Check for customer-managed KMS key encryption_key_arn = gateway_details.get( diff --git a/tests/test_agentcore_checks.py b/tests/test_agentcore_checks.py index 655fc69..7e42c26 100644 --- a/tests/test_agentcore_checks.py +++ b/tests/test_agentcore_checks.py @@ -552,6 +552,29 @@ def test_ac10_uses_generic_resource_policy_api(self, mock_ac): resourceArn="arn:aws:bedrock-agentcore:us-east-1:123456789012:runtime/rt-1" ) + @patch("agentcore_app.agentcore_client") + def test_ac10_gets_gateway_by_gateway_identifier(self, mock_ac): + mock_ac.list_agent_runtimes.return_value = {"agentRuntimes": []} + mock_ac.list_gateways.return_value = { + "items": [{"gatewayId": "gw-1", "name": "TestGateway"}] + } + mock_ac.get_gateway.return_value = { + "gatewayArn": "arn:aws:bedrock-agentcore:us-east-1:123456789012:gateway/gw-1" + } + mock_ac.get_resource_policy.return_value = { + "policy": '{"Version":"2012-10-17"}' + } + + result = agentcore_app.check_agentcore_resource_based_policies() + findings = extract_csv_data(result) + + assert len(findings) >= 1 + assert findings[0]["Status"] == "Passed" + mock_ac.get_gateway.assert_called_once_with(gatewayIdentifier="gw-1") + mock_ac.get_resource_policy.assert_called_once_with( + resourceArn="arn:aws:bedrock-agentcore:us-east-1:123456789012:gateway/gw-1" + ) + @patch("agentcore_app.agentcore_client") def test_ac10_access_denied_policy_read_returns_na_finding(self, mock_ac): mock_ac.list_agent_runtimes.return_value = { @@ -688,6 +711,7 @@ def test_ac12_gateway_with_kms_key_returns_passed(self, mock_ac): findings = extract_csv_data(result) assert len(findings) >= 1 assert findings[0]["Status"] == "Passed" + mock_ac.get_gateway.assert_called_once_with(gatewayIdentifier="gw-1") @patch("agentcore_app.agentcore_client") def test_ac12_exception_returns_error_finding(self, mock_ac): From 71b591affcd0c1724d1175b2a99824b23db77c90 Mon Sep 17 00:00:00 2001 From: Agasthi Kothurkar Date: Wed, 24 Jun 2026 14:26:34 -0400 Subject: [PATCH 30/30] Disable stale Bedrock access check --- .../functions/security/bedrock_assessments/app.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/aiml-security-assessment/functions/security/bedrock_assessments/app.py b/aiml-security-assessment/functions/security/bedrock_assessments/app.py index becd597..264dd42 100644 --- a/aiml-security-assessment/functions/security/bedrock_assessments/app.py +++ b/aiml-security-assessment/functions/security/bedrock_assessments/app.py @@ -2759,10 +2759,10 @@ def lambda_handler(event, context): ) ) - logger.info("Running global stale Bedrock access check (BR-14)") - all_findings.append( - check_stale_bedrock_access(permission_cache, region=GLOBAL_REGION_LABEL) - ) + # logger.info("Running global stale Bedrock access check (BR-14)") + # all_findings.append( + # check_stale_bedrock_access(permission_cache, region=GLOBAL_REGION_LABEL) + # ) # Verify Bedrock is available in this region. A ValidationException here # (logging simply not configured) still means the service is reachable,

Account IDRegionCheck IDFindingDetailsResolutionReferenceSeverityStatus
111111111111 us-east-1 FS-00 FinServ Regional Scope Not ApplicableN/A
007564470903
111111111111 eu-west-1 FS-00 FinServ Regional Scope Not ApplicableN/A
007564470903
111111111111 ap-southeast-2 FS-00 FinServ Regional Scope Not Applicable