- Agentic Development
- Architecture Overview
- Two-Phase Architecture
- Assessment Execution Workflow
- Assessment Structure
- Assessment History (Changes Since Last Assessment)
- Report Generation Architecture
- Adding a New Check Inside an Existing Service
- Adding New AI/ML Service Assessments
- Extending or Adding Lenses
- Adding a Compliance Standard (OWASP-style)
- 1. Choose a Check ID prefix
- 2. Create the Lambda package
- 3. Wire the opt-in flag through deployment and execution
- 4. Add IAM grants for APIs and CSV artifacts
- 5. Add the Step Functions Choice state
- 6. Register the standard in the report
- 7. Check routing and artifact completeness
- 8. Update documentation
- 9. Add tests
- 10. Generate and verify the HTML report
- Variant: a derived standard (AISF-style)
- Testing Your Extensions
- Documentation and Screenshots
- Contributing and Releases
- CI/CD Workflows
- Support and Resources
AI coding agents should load the repository-root AGENTS.md first; it is the canonical source for repository conventions and review gates. CLAUDE.md only delegates to it. This guide covers the implementation workflows and architecture behind those conventions.
The AI/ML Security Assessment Framework is a serverless, multi-account security assessment solution for AWS AI/ML workloads. It performs 162 core security checks across Amazon Bedrock, Amazon SageMaker AI, Amazon Bedrock AgentCore, and AWS Agent Registry. It also synthesizes up to 39 Agentic AI Security checks from the selected Amazon Bedrock, Amazon Bedrock AgentCore, and AWS Agent Registry assessments, and offers an optional 64-check Responsible AI GRC assessment and an optional 12-check OWASP Top 10 for LLM assessment. Results are rendered as interactive HTML reports with findings and remediation guidance.
The current deployment is validated only in the standard AWS commercial
partition (aws). Partition-aware implementation details must not be treated
as support for AWS GovCloud (US) (aws-us-gov) or AWS China (aws-cn).
Adding either partition requires a service-availability review, partition-safe
Step Functions integrations and generated URLs, and end-to-end validation of
both single-account and multi-account deployment modes.
- Runtime assessment Lambda roles are read-oriented and scoped per function to the APIs that function calls.
- AWS CodeBuild and member-account roles hold deployment permissions only: they create or update the SAM assessment stacks, poll executions, and retrieve reports.
- Cross-account trust is limited to the AWS CodeBuild role in the central assessment account.
- Amazon S3 buckets enforce SSL-only access, and assessment data is encrypted in transit and at rest.
- No persistent credentials are stored in AWS CodeBuild.
Phase 1 deploys roles and central infrastructure with AWS CloudFormation.
Phase 2 runs buildspec.yml in AWS CodeBuild, which deploys the AWS SAM
assessment stack into each account and starts its AWS Step Functions
execution. Single-account mode uses one template,
deployment/aiml-security-single-account.yaml, whose CodeBuildRole deploys
directly in the same account. Multi-account mode uses the two templates
described below.
- AWS CloudFormation StackSets deployment: Deploys
AIMLSecurityMemberRoleto all target accounts. - Cross-account trust: Trusts the central assessment account's CodeBuild role.
- Deployment permissions: Grants only the cross-account deployment, Step Functions execution-polling, and report-retrieval permissions CodeBuild needs to create or update per-account SAM stacks. The SAM-created Lambda execution roles hold the assessment-service API permissions.
- AWS CodeBuild project: Orchestrates multi-account deployments and assessments.
- Amazon S3 bucket: Central storage for consolidated assessment results.
- AWS IAM role:
MultiAccountCodeBuildRole, which assumes the member role in each account. - Amazon SNS topic: Optional email notifications for build state changes.
- Amazon EventBridge rule: Forwards CodeBuild state changes to the SNS topic.
- AWS Lambda trigger: Starts the CodeBuild project automatically after stack creation.
- Account discovery: In multi-account mode, lists active accounts from AWS Organizations, or uses
MultiAccountListOverride. - Role assumption: In multi-account mode, assumes
AIMLSecurityMemberRolein each target account. - AWS SAM deployment: Deploys or updates the assessment stack (
aiml-security-<account-id>for member accounts,aiml-security-mgmtfor the central account that runs CodeBuild, which multi-account runs always assess, oraiml-sec-<account-id>in single-account mode). The stack contains these AWS Lambda functions:- Amazon Bedrock assessment (
BedrockSecurityAssessmentFunction) - Amazon SageMaker AI assessment (
SagemakerSecurityAssessmentFunction) - Amazon Bedrock AgentCore assessment (
AgentCoreSecurityAssessmentFunction) - AWS Agent Registry assessment (
AgentRegistrySecurityAssessmentFunction) - Responsible AI GRC assessment (
ResponsibleAIGRCAssessmentFunction) - OWASP Top 10 for LLM assessment (
OWASPSecurityAssessmentFunction) - Amazon S3 cleanup (
CleanupBucketFunction) - AWS IAM permission caching (
IAMPermissionCachingFunction) - Region resolution (
ResolveRegionsFunction) - Consolidated report generation (
GenerateConsolidatedReportFunction)
- Amazon Bedrock assessment (
- Assessment execution: Starts the AWS Step Functions workflow in each account, passing
enableResponsibleAIGRCandenableOWASPfrom the deployment parameters. Service selection is baked into the stack (see Service Selection). - Results collection: Each assessment Lambda writes its CSV to the account's local Amazon S3 bucket, and the report Lambda writes the per-account HTML report.
- Consolidation: CodeBuild copies each account's artifacts to the central bucket and, for multi-account runs with no recorded account failures, builds the consolidated HTML report (
consolidated-reports/security_assessment_multi_account_<timestamp>.html) withconsolidate_html_reports.py. - Changes report: In the post-build phase,
run_changes_reportcompares each account with its previous usable run (see Assessment History). - Notification: Sends a completion notification through Amazon SNS, if configured.
- AWS SAM application: The AI/ML security assessment stack.
- AWS Step Functions: A single workflow orchestrating all assessments.
- AWS Lambda functions: The functions listed above. The Responsible AI GRC Lambda is invoked only when Responsible AI GRC is enabled or OWASP needs its
FS-*source rows; the OWASP Lambda is invoked only when OWASP is enabled. - Local Amazon S3 bucket: Storage for account-specific results.
Repository layout
sample-aiml-security-assessment/
├── README.md # Project overview and deployment guide
├── AGENTS.md # Canonical AI coding-agent guidance
├── CLAUDE.md # Compatibility shim that loads AGENTS.md
├── CHANGELOG.md # Release history and deployment impact
├── CONTRIBUTING.md # Contribution guidelines
├── CODE_OF_CONDUCT.md # Code of conduct
├── LICENSE # License
├── aiml-security-assessment/
│ ├── functions/security/
│ │ ├── bedrock_assessments/ # Amazon Bedrock checks (57)
│ │ ├── sagemaker_assessments/ # Amazon SageMaker AI checks (42; SM-29 reserved)
│ │ ├── agentcore_assessments/ # Amazon Bedrock AgentCore checks (53)
│ │ ├── agent_registry_assessments/ # AWS Agent Registry checks (10)
│ │ ├── responsible_ai_grc_assessments/ # Optional Responsible AI GRC checks (64)
│ │ ├── responsible_ai_grc_tests/ # Responsible AI GRC test session (own conftest)
│ │ ├── owasp_assessments/ # Optional OWASP Top 10 for LLM checks (12)
│ │ ├── iam_permission_caching/ # AWS IAM permissions cache
│ │ ├── cleanup_bucket/ # Amazon S3 cleanup
│ │ ├── resolve_regions/ # Target-region resolution for the Map state
│ │ └── generate_consolidated_report/ # HTML/CSV report generation
│ ├── statemachine/ # AWS Step Functions definition
│ ├── images/ # SAM application images
│ ├── template.yaml # AWS SAM template (single-account)
│ ├── template-multi-account.yaml # AWS SAM template (multi-account)
│ ├── samconfig.toml # SAM deployment configuration
│ ├── envvars.json # Environment variables for local testing
│ └── testfile.json # Test event for local invocation
├── assessment_history/ # Changes-since-last-assessment report (CodeBuild post-build)
├── deployment/ # Top-level AWS CloudFormation templates
├── docs/
│ ├── ASSESSMENT_HISTORY.md # Changes since last assessment report
│ ├── CLEANUP.md # Resource removal guide
│ ├── DEVELOPER_GUIDE.md # This guide
│ ├── FAQ.md # Frequently asked questions
│ ├── RESPONSIBLE_AI_GRC_SCOPE.md # Responsible AI GRC scope, sources, and alias migration
│ ├── SECURITY_CHECKS.md # Security checks reference (core + Agentic AI)
│ ├── SECURITY_CHECKS_OWASP.md # OWASP Top 10 for LLM checks reference
│ ├── SECURITY_CHECKS_RESPONSIBLE_AI_GRC.md # Responsible AI GRC checks reference
│ ├── SECURITY_CHECKS_RESPONSIBLE_AI_GRC_SEVERITY_METHODOLOGY.md # Severity model and register
│ ├── TROUBLESHOOTING.md # Troubleshooting guide
│ ├── UPGRADING.md # Upgrading an existing deployment
│ ├── diagrams/ # Architecture diagrams
│ └── icons/ # AWS service icons
├── sample-reports/ # Sample reports, screenshots, and scripts/
├── tests/ # Main pytest session (requirements.txt, fixtures/)
├── .github/ # Workflows, labeler configuration, and templates
├── .cfnlintrc # cfn-lint suppressions
├── buildspec.yml # AWS CodeBuild orchestration
├── consolidate_html_reports.py # Multi-account report consolidation
├── generate_provenance.py # Responsible AI GRC provenance generator
└── ruff.toml # Ruff lint and format configuration
The definition is aiml-security-assessment/statemachine/assessments.asl.json.
Both SAM templates substitute ${...} placeholders (function ARNs, the four
Enable*Assessment values, and MaxRegionConcurrency) through
DefinitionSubstitutions. The condensed state list:
Configure Service Assessments (Pass, ResultPath $.ServiceSelection)
└─ Cleanup S3 Bucket (Task, ResultPath null)
└─ IAM Permission Caching (Task, ResultPath null, global, runs once)
└─ Resolve Target Regions (Task, ResultPath $.ResolvedRegions)
└─ Scan Regions (Map over $.ResolvedRegions.regions, MaxConcurrency = MaxRegionConcurrency, ResultPath null)
│ ItemSelector: Region, RegionIndex, Execution, StateMachine, OriginalInput
├─ Run Security Assessments (Parallel, ResultPath null)
│ ├─ Bedrock Assessment Enabled? (Choice on OriginalInput.ServiceSelection.bedrock)
│ │ → Bedrock Security Assessment (Task; Catch → Bedrock Assessment Incomplete)
│ │ → default: Bedrock Assessment Not Selected (Pass)
│ ├─ Sagemaker Assessment Enabled? (same three-state pattern)
│ ├─ AgentCore Assessment Enabled? (same three-state pattern)
│ ├─ AWS Agent Registry Assessment Enabled? (same three-state pattern)
│ └─ Responsible AI GRC Enabled? (Choice)
│ → Legacy enableFinServ Input Rejected (Fail, LegacyEnableFinServInputRejected)
│ → Responsible AI GRC Security Assessment (Task, RegionIndex 0 only, when
│ enableResponsibleAIGRC or enableOWASP is "true"; Catch → ... Incomplete)
│ → default: Responsible AI GRC Assessment Skipped (Pass)
└─ OWASP Enabled? (Choice on OriginalInput.enableOWASP)
→ OWASP Security Assessment (Task; Catch → OWASP Assessment Incomplete)
→ default: OWASP Assessment Skipped (Pass)
After Scan Regions completes → Generate Consolidated Report (Task)
Each assessment Task has a Catch on States.ALL with an explicit
ResultPath ($.assessmentError for the four direct services,
$.responsibleAIGRCError and $.owaspError for the optional assessments) that
routes to an Incomplete Pass state (statusCode: 207, incomplete: true),
so one failed Lambda cannot abort the other branches. Not Selected and
Skipped Pass states return a small marker object. Because the Parallel state
uses ResultPath: null, its branch outputs are discarded and OWASP Enabled?
still sees OriginalInput, Region, and RegionIndex.
User-facing behavior (the Not selected labeling, score denominators, and Responsible AI GRC independence) is documented in Selecting Service Assessments. Historical CSV selection is documented in Assessment History. This section covers the implementation wiring.
- Parameters.
EnableBedrockAssessment,EnableSageMakerAssessment,EnableAgentCoreAssessment, andEnableAgentRegistryAssessmentare string parameters (true/false, defaulttrue) in both SAM templates and both top-level deployment templates. - Pass-state wiring. Both SAM templates substitute the values into the
Configure Service AssessmentsPass state, which writes$.ServiceSelection(keysbedrock,sagemaker,agentcore,agent-registry) before cleanup and region resolution. The regional Choice states read$.OriginalInput.ServiceSelection[...]. Because the Pass state overwrites$.ServiceSelection, execution input cannot override the deployment's selected scope. - CodeBuild forwarding. The top-level deployment templates expose the
switches as the
ENABLE_BEDROCK,ENABLE_SAGEMAKER,ENABLE_AGENTCORE, andENABLE_AGENT_REGISTRYCodeBuild environment variables.buildspec.ymlvalidates them and forwards them asSAM_ENABLE_*_PARAMETERoverrides to all threesam deploysites (member account, management account, and single account). The feature adds no API calls or IAM permissions, so the member-role StackSet is unaffected. - Artifact validation. The report Lambda
(
generate_consolidated_report/app.py) buildsper_region_categoriesandexpected_artifactsfrom the selection and requires every selected service's CSV for every resolved region; missing selected artifacts still fail report generation, while deselected artifacts are not required. An all-disabled selection may produce an HTML report without service CSVs. The CodeBuild artifact checks inbuildspec.ymllikewise require CSV prefixes only for enabled services. - OWASP coverage rows. The OWASP Task receives
$.OriginalInput.ServiceSelection. It skips reads of deselected Amazon Bedrock, Amazon SageMaker AI, and Amazon Bedrock AgentCore CSVs (AWS Agent Registry CSVs are never read), andbuild_selection_coverage_findings()emits oneN/A/Informationalselection-coverage row per regional invocation for each OW ID whose mapped sources include a deselected service. Missing selected artifacts still useOW-00and are not conflated with intentional deselection. - Upgrade-path defaulting. The post-build phase reapplies default-enabled
flags (
${ENABLE_BEDROCK:-true}and so on) for older CodeBuild projects that lack the service-selection environment variables. Artifact validation must still reject missing selected-service CSVs on that path.
Regression coverage in tests/test_service_selection.py exercises all 16
direct-service combinations, both deployment paths, artifact requirements,
OWASP source selection, and single- and multi-account reporting.
The assessment areas and current check counts are summarized in Architecture Overview. For individual checks, see the Security Checks Reference.
Each direct service assessment Lambda (Amazon Bedrock, Amazon SageMaker AI, Amazon Bedrock AgentCore, and AWS Agent Registry):
- Receives
Execution,Region, andRegionIndexfrom the Step Functions Map state. - Probes service availability in the target region and emits an
N/A/Informationalfinding if the service is unavailable. - Reads cached AWS IAM permissions from Amazon S3 where a check needs them.
- Creates regional boto3 clients with an explicit
region_name. - Runs its checks, passing
region=to everycreate_finding()call. - Writes a region-suffixed CSV,
<service>_security_report_<execution_id>_<region>.csv, keyed onExecution.Name. - Returns a summary to AWS Step Functions.
AWS Agent Registry uses its own agent-registry-control client and runs
independently of Amazon Bedrock AgentCore availability because the services
have separate endpoints.
The Responsible AI GRC Lambda is deployed by both SAM templates, but Step
Functions invokes it only from the first region iteration (RegionIndex == 0)
when the execution input includes "enableResponsibleAIGRC": "true" or
"enableOWASP": "true". It receives the full TargetRegions list, emits
findings with Region values, and writes the unsuffixed
responsible_ai_grc_security_report_<execution_id>.csv. When only OWASP is
enabled, its FS-* rows are hidden source rows. For the legacy
EnableFinServAssessment alias and the rejected enableFinServ execution
input, see
Migrating from EnableFinServAssessment.
Supporting functions:
- AWS IAM Permission Caching: Pre-fetches AWS IAM policies once per execution (global).
- Cleanup Bucket: Removes current assessment objects before each run. Version history and delete markers remain until removed manually; see the Cleanup Guide.
- Resolve Regions: Resolves the
TargetRegionsparameter into the Map state's region list. - Generate Consolidated Report: Validates selected artifacts and creates the HTML report with region filtering.
Scanner behavior for each baseline is documented in
Optional Security Policy Baselines.
Active organization-specific baselines are CloudFormation parameters rather than
hard-coded scanner assumptions. Wire every layer: both SAM templates, both
top-level deployment templates, the corresponding CodeBuild environment
variable, and every sam deploy path in buildspec.yml.
| CloudFormation parameter | Lambda environment variable | Check |
|---|---|---|
RequireBedrockZeroDataRetention |
REQUIRE_BEDROCK_ZERO_DATA_RETENTION |
BR-37 (deprecated; not read by the scanner) |
RequireMarketplaceEndpointCMK |
REQUIRE_MARKETPLACE_ENDPOINT_CMK |
BR-40 |
RequireAgentCoreOnlineEvaluation |
REQUIRE_AGENTCORE_ONLINE_EVALUATION |
AC-17 |
RequireAgentRegistryCMK |
REQUIRE_AGENT_REGISTRY_CMK |
AR-05 |
AgentCoreTokenVaultId |
AGENTCORE_TOKEN_VAULT_ID |
AC-14 |
EnableAgentCoreArtifactObjectReads |
ENABLE_AGENTCORE_ARTIFACT_OBJECT_READS |
AC-34, AC-35 |
ApprovedExternalAccountIds |
AIML_APPROVED_EXTERNAL_ACCOUNT_IDS |
SM-30 |
ApprovedOrganizationIds |
AIML_APPROVED_ORG_IDS |
SM-30 |
EnableSageMakerArtifactObjectReads |
ENABLE_SAGEMAKER_ARTIFACT_OBJECT_READS |
SM-43 |
RequireBedrockZeroDataRetention remains wired for stack compatibility, but
BR-37 does not read it.
The top-level deployment templates expose the approved-account and
approved-organization values to CodeBuild as APPROVED_EXTERNAL_ACCOUNT_IDS
and APPROVED_ORGANIZATION_IDS, and passes EnableSageMakerArtifactObjectReads
as ENABLE_SAGEMAKER_ARTIFACT_OBJECT_READS; buildspec.yml maps them to the SAM
parameters above. Add or rename a baseline only when all layers and the public
deployment documentation are updated together.
tests/test_optional_policy_baseline_wiring.py checks the wiring.
The assessment_history/ package at the repository root compares each
account's current run with its previous usable run and writes the changes
report. buildspec.yml runs it in the post-build phase (run_changes_report)
after the existing reports, with warnings only, a time limit, and a skip when
little build time is left. It reads only the findings CSVs in the central
bucket (and, in single-account mode, the run records written by
record_assessment_run), only reads the multi-account failure ledger, and
never fails the build. User-facing behavior is in
Changes Since Last Assessment.
| Path | Role |
|---|---|
assessment_history/models.py |
Record shapes, change states, area routing, CSV columns |
assessment_history/normalize.py |
Day counts and dates blanked out before matching, each tied to the scanner code that writes it |
assessment_history/compare.py |
Pairs two runs' rows and labels each; no AWS calls |
assessment_history/derived.py |
Derives AISF rows from each run's compared findings with the main report's aisf_mappings.py; records mapping and unreadable-row notes without losing other controls |
assessment_history/discover.py |
Groups an account folder's CSVs into runs (S3 or local folder), picks the previous usable run (complete files, and a run record that does not say it failed), and reads the CSVs |
assessment_history/render_common.py, render_changes.py |
The HTML page, reusing report_template.py's CSS, escaping, names, and icons |
assessment_history/__main__.py |
python3 -m assessment_history compare: S3 mode for the build, local-folder mode for people |
tests/test_assessment_history_*.py |
Tests; tests/assessment_history_helpers.py builds test data |
tests/fixtures/assessment_history/ |
A hand-written example account folder and the golden saved answers (golden/expected_*.json) |
sample-reports/scripts/build_changes_sample.py |
Builds the sample changes page and CSV and the golden saved answers |
sample-reports/scripts/capture_changes_screenshot.py |
Captures sample-reports/changes-overview.png from the sample changes page |
- A check that writes a day count or date into
Finding_Details: add a pattern toVOLATILE_PATTERNSinnormalize.py, or anIGNORED_SOURCESentry with a reason.tests/test_assessment_history_normalize.pyfails until you do. - A new findings CSV prefix or assessment area: update
PREFIX_TO_MODULEindiscover.pyand the module and area lists inmodels.py. Files with an unknown prefix are logged and not read. - A new compliance standard:
COMPLIANCE_PREFIX_TO_AREAinmodels.pymust matchCOMPLIANCE_STANDARDSinreport_template.py; a test checks it. - An AISF mapping change: the changes report loads
aisf_mappings.pyby file path and derives both runs with the current mapping. PreserveGlobalpairing, selected-service coverage, and visible notes for skipped mappings and unreadable derived rows. report_template.py: the changes page reuses its styling and helpers; guard tests fail if something it relies on moves.- A sample report or a comparison rule: rebuild the changes sample (below) and review the diff.
The package keeps 100% line and branch coverage; CI runs the same check:
.venv/bin/python -m pytest tests/test_assessment_history_*.py \
--cov=assessment_history --cov-branch --cov-report=term-missing \
--cov-fail-under=100The golden tests use both sample reports. Each is turned back into the findings
CSVs the scanners write (in a temporary folder; only the saved answers are
committed), and a current run is made by applying a short list of edits
(EDITS in the script) that covers every change state and each matching step.
Values that AWS generated in the sample reports (resource IDs and the random
parts of resource names) are first replaced with synthetic values of the same
shape, and a test fails if any of them reaches a committed file.
After changing a sample report or a comparison rule:
-
Regenerate the sample and the golden answers:
.venv/bin/python sample-reports/scripts/build_changes_sample.py
--checkmakes no changes and exits 1 if anything is out of date. -
Review the diff under
sample-reports/andtests/fixtures/assessment_history/golden/. -
Refresh the screenshot. The script uses the same browser setup as
capture_screenshots.py, captures only the changes page intosample-reports/changes-overview.png, and does not rewrite any report:./sample-reports/scripts/capture_changes_screenshot.py
Report generation uses a single shared template (report_template.py) for both
deployment modes:
aiml-security-assessment/functions/security/generate_consolidated_report/
├── app.py # Lambda handler (single-account)
├── report_template.py # Shared HTML/CSS/JS template
└── ...
consolidate_html_reports.py # CodeBuild script (multi-account)
| Component | Mode | Description |
|---|---|---|
app.py (AWS Lambda) |
mode='single' |
Generates per-account HTML reports during the AWS Step Functions execution |
consolidate_html_reports.py |
mode='multi' |
Builds one report from every account's CSV files in the AWS CodeBuild post-build phase |
Both call generate_html_report() from report_template.py with different
parameters. The report routes rows by Check_ID prefix, and
COMPLIANCE_STANDARDS in report_template.py drives every compliance-standard
section.
- Edit
report_template.pyonly; changes apply to both single- and multi-account reports. - Key functions:
get_html_template(): HTML/CSS/JS structure.generate_table_rows(): finding row generation.generate_html_report(): main entry point with themodeparameter ('single'or'multi').
- The Assessment Scope block is patched after rendering with exact
str.replace()anchors. If you change the markup an anchor targets, update the anchor too; a missed match fails silently. - Run the report-pipeline tests from the report package directory:
../../../../.venv/bin/python -m pytest test_generate_report.py -v. - Generate and inspect both report modes (see Report Verification).
Most contributions add or update individual checks inside an existing assessment package (Amazon Bedrock, Amazon SageMaker AI, Amazon Bedrock AgentCore, or AWS Agent Registry) rather than creating a new package.
-
Define the check contract before coding. Record the
Check_ID, the exact resource and its regional or account-global scope, the AWS client and operation, the response field and allowed values, the successful empty-response behavior, and the evidence that producesPassed,Failed, orN/A. Verify request/response shapes and enum values against the installed botocore service model (data/<service>/*/service-2.json) and AWS documentation. An HTTP 200 or an empty response does not establish compliance; distinguish account-level policies from resource-level settings. Compare the predicate with the catalog description and remediation before writing tests. -
Locate the target file:
bedrock_assessments/app.py,sagemaker_assessments/app.py,agentcore_assessments/app.py, oragent_registry_assessments/app.py. Follow the existing function structure and naming in that file, and check the service string each client was constructed with rather than its variable name.IAM policy
Statementaccepts either a single object or a list. Normalize both forms before evaluating policy statements;agent_registry_assessments/app.pyuses_policy_statements()for this. Test both forms so a single-statement policy cannot be silently skipped. -
Implement the check. Follow the package's existing return shape (for example, a dict with a
csv_datalist in Amazon Bedrock, or a list of findings in AWS Agent Registry). Passregion=region(or the loop variable) to everycreate_finding()call, and use the sameCheck_IDin normal, fallback, and error findings. Use cached IAM permissions instead of re-listing policies, and keep each call to the minimum IAM permissions it needs. -
Status and severity semantics (critical):
- Access-denied, region-unavailable, and "service not present" paths return
N/A. Unsupported regional APIs and features areInformational; follow the package's convention for access-denied and other could-not-assess results (Responsible AI GRC usesLow). - Reuse the package helpers instead of raw string matching:
is_region_unsupported()anddescribe_api_error()inbedrock_assessments/app.py, or_is_unavailable(),_is_access_denied(),_error_detail(),_error_resolution(), and_na()inagent_registry_assessments/app.py. Do not classify credential failures or programming errors as region unavailability. - Never emit
Failedwithresolution="No action required". Tooling conditions, such as an empty permission cache, areN/A/Informational. - A completed empty inventory needs an explicit
N/Arow for each affected check. An access-denied or partial inventory must not claim that no resources exist. KeepPassedresolution text free of remediation instructions. - For optional policy baselines (for example,
REQUIRE_MARKETPLACE_ENDPOINT_CMK=false), emitN/A/Informationalwhen the hardening gap is observed; reservePassedfor controls that were checked and satisfied.
- Access-denied, region-unavailable, and "service not present" paths return
-
Inventory scope, pagination, and error isolation. Use
get_paginator()or an explicit next-token helper such as_agentcore_list_all, and test a non-compliant item on a later page. If a safety cap or deadline truncates the list, keep the findings collected and add anN/Aincomplete-inventory notice. Wrap per-resource detail calls individually so one throttle or delete race does not erase other resources' findings. Run account-global inventories once, or de-duplicate them with a truthfulRegionvalue. Make sure the handler wraps the check (for example,_run_check_safely()inagent_registry_assessments/app.py) so an unexpected exception becomes one visibleN/A"Incomplete" row under the sameCheck_IDwhile the CSV is still written. Let an artifact-write failure raise after logging so the Step FunctionsCatchfires; returning{"statusCode": 500}without raising hides the failure. -
Synthesized mappings (if applicable). If the check should also appear under the Agentic AI lens (
AG-) or an OWASP category, update the mapping dictionary. Values inOWASP_CHECK_MAPPINGSare lists because one source check can emit multipleOW-rows. Allocate new AG numbers by hand across the Amazon Bedrock, Amazon Bedrock AgentCore, and AWS Agent Registry mapping modules and the native checks (the catalog currently ends at AG-39). Confirm every mapped source ID exists, every emitted OW ID appears indocs/SECURITY_CHECKS_OWASP.md, and anN/Asource produces anOW-row withInformationalseverity. -
Add IAM grants. Every new boto3 operation needs its exact IAM action on the owning Lambda's policy in both SAM templates; see Step 5: Update AWS IAM Permissions.
-
Add tests. Use SDK-shaped responses for compliant/pass, non-compliant/fail (or advisory
N/Awhere defined), no-resource, access-denied, region-unavailable, and unexpected-error cases, including a successful empty response where the API can return one. Empty-inventory tests alone do not exercise a check's predicate. Shared inventories need later-page, list-error, and per-resource detail-error cases; account-global inventories need a two-region case. Assert the exactCheck_IDon fallback rows and that a failed check leaves the other checks' rows in the CSV. Seetests/test_bedrock_checks.py,tests/test_sagemaker_checks.py, andtests/test_agent_registry_checks.py. -
Update documentation. Align the check's evidence, scope, status behavior, severity, and remediation with
docs/SECURITY_CHECKS.mdand the applicabledocs/SECURITY_CHECKS_*.mdcatalog. Update published counts and ranges inREADME.md, this guide,docs/SECURITY_CHECKS.md(intro, per-service table, and headings),docs/SECURITY_CHECKS_RESPONSIBLE_AI_GRC.md(relationship and consolidation summaries), anddocs/SECURITY_CHECKS_AISF.md(derived-row accounting). Section headings carry their count (for example,## AWS Agent Registry Security Checks (10)), so changing a count changes the anchor; re-check everySECURITY_CHECKS.md#...deep link and table of contents. Also reviewdocs/TROUBLESHOOTING.md,CHANGELOG.md, and relevant scope, sample-report, and diagram documents. -
Run the gates before committing (commands in Running Checks Locally):
- Whole-repository
ruff checkandruff format --check, which include CI's changed-file scope and uncommitted work. - The pytest sessions:
tests/,responsible_ai_grc_tests/, the report-pipeline session, and theassessment_historycoverage gate. cfn-linton any edited templates.- The repository-wide delivery gate in AGENTS.md, including API evidence, IAM and artifact coverage, status semantics, mappings, and CSV schema.
- Whole-repository
-
Generate and verify the HTML report (mandatory before opening a PR). Follow Report Verification and confirm the check renders correctly in the table, sidebar, filters, and both light and dark modes.
Check ID prefixes drive report routing and match ^[A-Z]{2,3}-\d{2}$:
| Prefix | Assessment area |
|---|---|
BR- |
Amazon Bedrock |
SM- |
Amazon SageMaker AI |
AC- |
Amazon Bedrock AgentCore |
AR- |
AWS Agent Registry |
AG- |
Agentic AI Security (synthesized lens plus native AG-24..27 and AG-39) |
FS- |
Responsible AI GRC (legacy identifier retained for compatibility) |
OW- |
OWASP Top 10 for LLM |
AISF- |
AWS AI Security Framework (derived report view; not an additional check) |
Status values:
Passed: resources were checked and met the assessed best practice.Failed: resources were checked and found non-compliant.N/A: nothing to check, the API or region is unavailable, manual review is needed, or no result could be determined.
Severity values:
High: critical security issues requiring immediate attention.Medium: important security improvements.Low: minor improvements (also Responsible AI GRC's could-not-assess results).Informational: advisory information, no-resource results, and unavailable-featureN/Aresults.
Adding a service (for example, Amazon Comprehend) means a new Lambda package
plus wiring through both SAM templates, the state machine, both top-level
deployment templates, buildspec.yml, and the report. The examples below use
comprehend as a placeholder.
Copy aiml-security-assessment/functions/security/agent_registry_assessments/
as the template. It is the most recent package and shows every current
pattern:
schema.py: the pydanticFindingmodel. It validatesCheck_IDagainst^[A-Z]{2,3}-\d{2}$, requires anhttps://Reference, and restrictsSeverityandStatusto the enum values. Copy it verbatim.requirements.txt: pinnedboto3/botocorepluspydantic>=2.0andtyping-extensions.app.py:- Availability helpers (
_is_unavailable(),_is_access_denied(),_error_detail(),_error_resolution()) and_na()forN/A/Informationalrows. _run_check_safely()around every check, so one check's exception becomes a visibleIncompleterow._execution_name()to readExecution.Name, andwrite_to_s3()to write<service>_security_report_<execution_id>_<region>.csv.- A handler that raises on unexpected top-level errors after logging, so the Step Functions
Catchroutes to theIncompletestate.
- Availability helpers (
A structural handler sketch follows. Implement get_inventory() and
assess_inventory() for the chosen service: distinguish unavailable, denied,
partial, and empty inventories; emit explicit N/A findings where assessment
is incomplete or there is nothing to check; and wrap each verified control
with _run_check_safely(). The example does not define a Comprehend security
control. In particular, Comprehend DescribeEndpoint
does not return an endpoint encryption setting, so its response cannot prove
an endpoint-encryption check.
def lambda_handler(event, context):
execution_id = _execution_name(event)
region = event.get("Region") or os.environ.get("AWS_DEFAULT_REGION", "us-east-1")
try:
client = boto3.client("comprehend", config=boto3_config, region_name=region)
inventory = get_inventory(client) # paginate and retain partial results
findings = assess_inventory(client, inventory, region)
for finding in findings:
finding["Region"] = finding.get("Region") or region
return {"statusCode": 200, "body": json.dumps(
{"s3_url": write_to_s3(execution_id, generate_csv_report(findings), region)})}
except Exception:
logger.exception("Amazon Comprehend assessment failed")
raiseChecklist for the new package:
- A unique two- or three-letter
Check_IDprefix, added to the report routing and the prefix table. - Every regional check handles unsupported regions and features with an
N/A/Informationalrow, distinct from access-denied and unexpected errors. - Every list API paginates; per-resource detail calls are isolated; empty, denied, and partial inventories are distinguishable.
- The handler always writes the CSV when it returns and raises when it cannot.
- Tests load the package through
importlib.util.spec_from_file_locationunder a distinct module name (for example,comprehend_app), not a bareimport app.
Add the function to both aiml-security-assessment/template.yaml and
aiml-security-assessment/template-multi-account.yaml:
- The
AWS::Serverless::Functionresource, withAIML_ASSESSMENT_BUCKET_NAMEandTARGET_REGIONSenvironment variables. - A
DefinitionSubstitutionsentry for its ARN and aLambdaInvokePolicyon the state machine. - An
Enable<Service>Assessmentparameter (AllowedValues: ["true", "false"], defaulttrue), its parameter group entry, and aDefinitionSubstitutionsentry.
Example function resource
ComprehendSecurityAssessmentFunction:
Type: AWS::Serverless::Function
Properties:
FunctionName: !Sub 'aiml-security-${AWS::StackName}-ComprehendAssessment'
CodeUri: functions/security/comprehend_assessments/
Handler: app.lambda_handler
Runtime: python3.12
Architectures:
- x86_64
Timeout: 600
MemorySize: 1024
Environment:
Variables:
AIML_ASSESSMENT_BUCKET_NAME: !Ref AIMLAssessmentBucket
TARGET_REGIONS: !Ref TargetRegions
Policies:
- Statement:
- Sid: ComprehendReportWrite
Effect: Allow
Action:
- s3:PutObject
Resource: !Sub '${AIMLAssessmentBucket.Arn}/comprehend_security_report_*.csv'
- Sid: ComprehendListEndpoints
Effect: Allow
Action: comprehend:ListEndpoints
Resource: '*'
- Sid: ComprehendDescribeEndpoints
Effect: Allow
Action: comprehend:DescribeEndpoint
Resource:
- !Sub 'arn:${AWS::Partition}:comprehend:*:${AWS::AccountId}:document-classifier-endpoint/*'
- !Sub 'arn:${AWS::Partition}:comprehend:*:${AWS::AccountId}:entity-recognizer-endpoint/*'These permissions cover an illustrative endpoint inventory using
ListEndpoints and DescribeEndpoint; they do not define a complete
assessment. The region wildcard covers the configured target regions, which
can differ from the Lambda's deployment region. Check each implemented
operation against the
Comprehend service authorization reference
and grant only the resources it needs in both SAM templates.
Add a branch to the Run Security Assessments Parallel state inside the
Scan Regions Map state in
aiml-security-assessment/statemachine/assessments.asl.json. Mirror an
existing branch exactly:
<Service> Assessment Enabled?(Choice) reading$.OriginalInput.ServiceSelection['<key>'], withDefaultset to<Service> Assessment Not Selected.<Service> Security Assessment(Task) passingExecution,StateMachine,Region, andRegionIndexin its payload, with the standard LambdaRetryblock.- A
CatchonStates.ALLwith"ResultPath": "$.assessmentError"routing to<Service> Assessment Incomplete. <Service> Assessment Incompleteand<Service> Assessment Not SelectedPass states that end the branch.
Branch skeleton
{
"StartAt": "Comprehend Assessment Enabled?",
"States": {
"Comprehend Assessment Enabled?": {
"Type": "Choice",
"Choices": [
{
"Variable": "$.OriginalInput.ServiceSelection['comprehend']",
"StringEquals": "true",
"Next": "Comprehend Security Assessment"
}
],
"Default": "Comprehend Assessment Not Selected"
},
"Comprehend Security Assessment": {
"Type": "Task",
"Resource": "arn:aws:states:::lambda:invoke",
"Parameters": {
"FunctionName": "${ComprehendSecurityAssessmentFunction}",
"Payload": {
"Execution.$": "$.Execution",
"StateMachine.$": "$.StateMachine",
"Region.$": "$.Region",
"RegionIndex.$": "$.RegionIndex"
}
},
"Catch": [
{
"ErrorEquals": ["States.ALL"],
"ResultPath": "$.assessmentError",
"Next": "Comprehend Assessment Incomplete"
}
],
"End": true
},
"Comprehend Assessment Incomplete": {
"Type": "Pass",
"Result": {"statusCode": 207, "incomplete": true, "body": {"findings": []}},
"End": true
},
"Comprehend Assessment Not Selected": {
"Type": "Pass",
"Result": {"statusCode": 200, "skipped": true, "reason": "not-selected", "service": "comprehend"},
"End": true
}
}
}The Retry block is omitted here for brevity; copy it from an existing branch.
- Add the
<key>: "${Enable<Service>Assessment}"entry to theConfigure Service AssessmentsPass state'sResult. - Add
Enable<Service>Assessmenttodeployment/aiml-security-single-account.yamlanddeployment/2-aiml-security-codebuild.yaml(parameter, parameter group, and anENABLE_<SERVICE>CodeBuild environment variable). - In
buildspec.yml, default and validateENABLE_<SERVICE>in the build phase, reapply its default in the post-build phase, export aSAM_ENABLE_<SERVICE>_PARAMETER, and pass it to all threesam deploysites (member account, management account, and single account). - Add the service's CSV prefix to the selected-artifact checks in
buildspec.ymland toper_region_categoriesingenerate_consolidated_report/app.py, so a missing selected CSV is reported as incomplete. - Add report routing for the new prefix and a service section in
report_template.pyandconsolidate_html_reports.py, including the Not selected state. - Add the CSV prefix to
PREFIX_TO_MODULEinassessment_history/discover.pyand the module lists inmodels.py. - Extend
tests/test_service_selection.pyfor the new combinations.
Add every exact assessment-service action used by the new function to that
function's Policies block in both SAM templates. Do not use service-wide
wildcards such as comprehend:List*. Do not add assessment-service actions to
deployment/1-aiml-security-member-roles.yaml,
deployment/2-aiml-security-codebuild.yaml, or
deployment/aiml-security-single-account.yaml; those templates hold
deployment and orchestration roles, not assessment runtime roles.
- Validate every new boto3 operation and its IAM action against the AWS Knowledge MCP documentation tools before merging. Confirm the client (control plane versus data plane), the IAM prefix, and any resource or condition-key constraints.
- Grant the producer
s3:PutObjecton its exact CSV key pattern, and grant every consumer (including the report Lambda)s3:ListBucketon the prefix it lists ands3:GetObjecton the objects it reads. - Add the new Lambda to the IAM coverage tests (
tests/test_iam_coverage.py,tests/test_core_iam_coverage.py).
The boto3 client name, IAM prefix, and Python variable name can differ. Check
the actual service string passed to boto3.client() and the installed botocore
model before using an operation or paginator:
| boto3 client | IAM action prefix | Review detail |
|---|---|---|
bedrock-agent |
bedrock: |
Agent, knowledge-base, flow, and prompt operations use this prefix; some functions call this client bedrock_client. |
bedrock-agentcore-control |
bedrock-agentcore: |
AgentCore list, describe, and configuration operations are on the control-plane client, not bedrock-agentcore. |
agent-registry-control |
agent-registry: |
ProvenanceSummary fields are relation, sourceId, and sourceType, not source. |
Follow Local Testing using
ComprehendSecurityAssessmentFunction as the function name.
testfile.json contains only an Execution.Name. Add "Region" and
"RegionIndex" to a copy of it to exercise a specific region.
The Agentic AI Security lens (AG-01 through AG-39) is synthesized at runtime, not produced by a separate scanner. It reuses findings from the selected Amazon Bedrock, Amazon Bedrock AgentCore, and AWS Agent Registry assessments plus a small number of native gateway checks.
- Mapping dictionaries live in three places:
bedrock_assessments/app.py→AGENTIC_BEDROCK_CHECK_MAPPINGSagentcore_assessments/app.py→AGENTIC_AGENTCORE_CHECK_MAPPINGSagent_registry_assessments/app.py→AGENTIC_AGENT_REGISTRY_CHECK_MAPPINGS
- Native checks (currently AG-24 through AG-27 and AG-39) live in the AgentCore package because they need the
bedrock-agentcore-controlclient. - Allocate new AG numbers by hand to avoid collisions across all three mapping dictionaries and the native checks. The current high-water mark is AG-39.
- The HTML report routes the lens through the
AG-prefix as its own assessment area.COMPLIANCE_STANDARDSis the separate registry for OWASP and future compliance standards. - Follow the same cross-file execution, artifact, and report checks as a new compliance standard. The Agentic AI lens needs no CloudFormation parameter of its own, but new native checks still need IAM grants in both SAM templates.
- Update
docs/SECURITY_CHECKS.mdand run the mapping-drift, test-coverage, and gate checklist before merging. - Generate and verify the HTML report (mandatory before opening a PR): follow Report Verification and confirm AG-* findings appear under the lens section with the right severity and routing.
The HTML presentation under "By Compliance Standard" is data-driven. Adding a standard such as NIST AI RMF or the EU AI Act follows the OWASP pattern, but the execution flag, artifact access, and required-artifact checks must also be wired.
Details
Use two or three letters that satisfy ^[A-Z]{2,3}-\d{2}$:
- OWASP →
OW-(implemented) - NIST AI RMF → suggested
NR- - EU AI Act → suggested
EU-
Details
Create aiml-security-assessment/functions/security/<slug>_assessments/:
- Copy
owasp_assessments/schema.pyverbatim andowasp_assessments/requirements.txt. - Author
app.pyfollowing the OWASP pattern: read per-service CSVs from S3, apply your<STANDARD>_CHECK_MAPPINGSdict, run any native checks, and write<slug>_security_report_<execution>_<region>.csv. - For every native control, complete the check contract and test cases. A rendered row does not prove that its AWS API predicate is correct.
Details
Follow the EnableOWASPAssessment pattern:
deployment/aiml-security-single-account.yamlanddeployment/2-aiml-security-codebuild.yaml: parameter definition, parameter group, and CodeBuild environment variable.buildspec.yml: theENABLE_<STANDARD>export and every applicableaws stepfunctions start-executioninput.aiml-security-assessment/template.yamlandtemplate-multi-account.yaml: the function resource,LambdaInvokePolicy, definition-substitution entry, and any SAM-level parameter for direct deployments.aiml-security-assessment/statemachine/assessments.asl.json: the flag reference and enabled/skipped branches (step 5).
Verify that every declared SAM parameter is consumed by the state machine. A
direct StartExecution must supply the flag in its input when the state
machine expects it; a CodeBuild environment variable alone does not set
direct-execution input.
Details
Follow AGENTS.md and scope each grant correctly:
- In both SAM templates, add the grant to the specific function's
Policiesblock that makes the call. - Grant the new Lambda write access to its exact CSV key pattern. For each consumer, including the report Lambda, add
s3:ListBucketon the prefix it lists ands3:GetObjecton the objects it reads. - Do not add assessment-service read actions to the three top-level deployment templates. Those roles only create or update the SAM stack, poll executions, and retrieve reports; a new deployment-time AWS operation needs a separate least-privilege review.
- The canonical multi-account member role uses one customer-managed policy document. Keep it within the 5,500-character rendered budget enforced by
tests/test_member_role_policy_size.py.
Details
In aiml-security-assessment/statemachine/assessments.asl.json:
- Add a
<Standard> Enabled?Choice state reading$.OriginalInput.enable<Standard>, and a<Standard> Security AssessmentTask that invokes the new function, with aCatchto a<Standard> Assessment Incompletestate and a<Standard> Assessment Skippeddefault. - Chain it after
OWASP Enabled?inside the region Map: replaceEnd: truewithNexton each OWASP success, skipped, and incomplete route that must reach the new Choice. - Preserve
OriginalInput,Execution,Region,RegionIndex, and other downstream fields on success, skipped, and caught-error paths. SetResultPathexplicitly on result-producing Task and Pass states as well as onCatch; aCatchResultPathdoes not protect the success path. Test each path through the next Choice and the report-generation state.
Details
Append a dict to COMPLIANCE_STANDARDS in report_template.py with slug,
name, prefix, icon, icon_small, reference_url, section_title, and
truthful scope_text. The slug and prefix must be unique; choose an icon
color distinct from the existing navigation colors.
- The shared loop in
generate_html_report()builds the sidebar entry, filter option, dashboard card, findings section and summary, Assessment Scope chip, and source link when the standard has rows. Extend the registry instead of copying OWASP-specific HTML. - If the HTML structure must change, edit the shared loop and the affected
get_html_template()placeholders together. Keep oneslugacross the sectionid, finding-rowdata-service, filter option value, summarydata-filter-service, and scope-chipdata-scope-service, and review affected CSS/JavaScript selectors. Update any Assessment Scopestr.replace()anchors whose source markup changes.
Details
The report Lambda (generate_consolidated_report/app.py) and the multi-account
consolidator (consolidate_html_reports.py) both iterate COMPLIANCE_STANDARDS
to initialize service_stats and service_findings and to route rows by
prefix. That handles presentation and routing, but it does not add the new
CSV to per_region_categories/expected_artifacts or grant permission to list
and read it. Extend those selected-artifact checks, map any CSV filename
fragment that differs from the slug, and verify CodeBuild stages the CSV for
the consolidator. A selected standard with a missing, unreadable, or
header-only CSV must be reported as incomplete, not shown with zero findings.
Also update COMPLIANCE_PREFIX_TO_AREA in assessment_history/models.py.
The report Lambda turns each non-derived registry slug into an S3 list prefix.
Add a new standard's CSV prefix to the s3:prefix condition on its
s3:ListBucket grant, and add its objects to the s3:GetObject grant, in
both SAM templates. Without the list prefix, S3 returns AccessDenied; the
report Lambda re-raises it, so report generation fails for every category.
Details
Add a docs/SECURITY_CHECKS_<STANDARD>.md in the OWASP style and update every
check-count location, table of contents, and deep link listed in
Adding a New Check Inside an Existing Service.
Details
Cover mapping emission, native-check behavior, enabled and skipped Step
Functions paths, artifact completeness, routing, and report rendering. Use
representative synthetic CSVs with positive, negative, N/A, and
empty-inventory findings, and test missing and unreadable artifacts
separately. Include IAM coverage assertions for the new Lambda and the report
reader in both SAM templates. Use tests/test_owasp_checks.py and
tests/test_report_template_owasp.py as templates.
Details
This is mandatory before opening a PR. Follow Report Verification. In single- and multi-account output, check sidebar navigation, filtering, card and section counts, severity and status, Assessment Scope chips and source text, and desktop/mobile and light/dark rendering. Test enabled, disabled, and empty-result cases. When changing the Assessment Scope markup, confirm the post-render insertion is present in the generated HTML.
A derived standard publishes a framework view over checks that already
ship, so it has no Lambda, no CSV, no Step Functions branch and no IAM change.
AISF- rows are produced by derive_aisf_findings() in
generate_consolidated_report/aisf_mappings.py, called from both consolidators
after their source rows are collected. Steps 1 through 5 and the S3 wiring in
step 7 do not apply; instead:
- Add
"derived": Trueto the registry entry. That key is read in exactly one place, thecategory_slugscomprehension inapp.py, to keep the slug out of the S3 prefix list. Every other consumer keeps the slug so the section still renders and routes. - When the scope text counts the standard's own controls, or a deselected
service removes some of them, give the entry a
scope_text_forcallable and passcompliance_scopefrom both consolidators, so the text counts what the map holds and names what was left out.report_template.pyis loaded by path and cannot import the mapping module, so the figures travel through the callers. AISF usesaisf_mappings.aisf_scope()and_aisf_scope_text(). - Register the standard and its first rows in the same change. A registered
standard with zero rows renders nothing at all (
if _total <= 0: continue), which looks exactly like a wiring bug. - Exclude the derived rows from the check-count total, and say so where the count is published. They restate verdicts that are already counted.
- Map only a control its named incumbents assert in full, and pin the map in
tests/test_aisf_derived_standard.py. See SECURITY_CHECKS_AISF.md.
Run the pytest sessions in Running Checks Locally, including the SAM build, then invoke the changed function with AWS SAM:
(cd aiml-security-assessment \
&& sam local invoke BedrockSecurityAssessmentFunction --event testfile.json)Deploy to a non-production test account and start an execution. Service selection comes from the stack parameters; the execution input carries only the optional-assessment flags.
sam deploy --stack-name aiml-security-test --capabilities CAPABILITY_IAM --resolve-s3
aws stepfunctions start-execution \
--state-machine-arn arn:aws:states:us-east-1:123456789012:stateMachine:ExampleStateMachine \
--input '{"enableResponsibleAIGRC":"false","enableOWASP":"false"}'- Deploy the member roles to test accounts with AWS CloudFormation StackSets.
- Deploy the central infrastructure with test parameters.
- Monitor the AWS CodeBuild logs for deployment and execution status.
- Verify the results in the central Amazon S3 bucket.
When you add a check, extend a lens (AG-*), or introduce a compliance
standard (OW-*, NR-*, EU-*, and so on), you must generate the HTML
reports from the test fixtures and inspect them before creating a pull
request. This catches routing, template, and rendering issues that unit tests
can miss.
The command below renders only the test's fixed synthetic findings; it does
not collect rows from a newly added scanner. First add representative findings
for the new Check_ID, lens, or standard to the relevant report test fixtures
and assert that the generated HTML contains them. For a new standard, also
cover enabled, disabled, and empty-result reports in the applicable mode.
(cd aiml-security-assessment/functions/security/generate_consolidated_report \
&& ../../../../.venv/bin/python -m pytest test_generate_report.py \
-k "generate_viewable_report or generate_multi_account_report" -s --tb=no)The reports are written under
aiml-security-assessment/functions/security/generate_consolidated_report/test_reports/
(git-ignored). Open the single- and multi-account HTML files in a browser at
desktop and mobile widths and verify:
- The new
Check_ID(for example, BR-41, SM-31, AR-09, AG-39, OW-13, or NR-01) appears with the expected Finding name, Severity badge, Status, Region, and Resolution text. - The row is routed into the right sidebar group:
- Core service checks (
BR-*,SM-*,AC-*,AR-*) appear under "By Service". AG-*findings appear under "By Lens" → Agentic AI Security.OW-*and any newNR-*/EU-*standards appear under "By Compliance Standard".
- Core service checks (
- A new compliance standard's sidebar link, filter option, dashboard card, findings section, Assessment Scope chip, and source link use the same slug and show the right counts and scope. A disabled standard does not appear as an empty or clean assessment.
- Filters (region, severity, status, and search) work for existing and new rows.
- Long finding details or remediation text do not break the table layout.
- Dark mode, responsive layout, and the executive dashboard reflect the new content accurately.
When you change the report template or add report features, update the sample reports and screenshots.
Regenerate sample reports from a fresh assessment run or from the local report
fixtures with the command in
Report Verification.
test_generate_report.py is a test module, not a standalone CLI. Use the
fixture output to validate rendering before refreshing the canonical files in
sample-reports/, and keep every identifier synthetic.
# Install or verify dependencies and Chromium; leave sample reports untouched
./sample-reports/scripts/capture_screenshots.py --check-dependencies
# Capture and optimize screenshots
./sample-reports/scripts/capture_screenshots.pyThe repository-root .venv must already exist and use Python 3.12. The script
re-launches itself with .venv/bin/python, installs
sample-reports/dev-requirements.txt into that environment when needed, and
installs Chromium under .venv/playwright-browsers. It opens the sample
reports in a headless browser, expands the viewport to include every
left-navigation section, captures each view, and optimizes the images (target
200-300 KB each, converting large PNGs to JPEG if needed). It writes four
screenshots to sample-reports/:
dashboard-overview-light.png: executive dashboard in light modedashboard-overview-dark.png: executive dashboard in dark modefindings-table.png: findings table with filtersmulti-account-summary.png: multi-account consolidated view
The changes-page screenshot, changes-overview.png, comes from
capture_changes_screenshot.py; see
Rebuilding the Changes Sample and Golden Files.
Customizing the screenshot script
Edit sample-reports/scripts/capture_screenshots.py:
# Viewport size
VIEWPORT_WIDTH = 1440
VIEWPORT_HEIGHT = 900
# Image quality
JPEG_QUALITY = 85 # Range: 1-100
PNG_OPTIMIZE = True
# Add new screenshots to the SCREENSHOTS list
SCREENSHOTS = [
{
"name": "my-screenshot",
"file": "security_assessment_single_account.html",
"description": "My Custom View",
"actions": [
{"type": "wait", "selector": ".element", "timeout": 2000},
{"type": "click", "selector": ".button"},
{"type": "scroll", "position": 500},
],
"clip": {"x": 0, "y": 0, "width": 1440, "height": 800},
}
]Available action types:
wait: wait for a selector, for example{"type": "wait", "selector": ".metrics", "timeout": 2000}click: click an element, for example{"type": "click", "selector": ".theme-toggle"}scroll: scroll to a position, for example{"type": "scroll", "position": 500}wait_time: wait a number of milliseconds, for example{"type": "wait_time", "ms": 300}
Screenshot troubleshooting
| Issue | Solution |
|---|---|
.venv not found |
Create it with python3.12 -m venv .venv, then bootstrap the repository dependencies |
| Playwright or Chromium missing | Run ./sample-reports/scripts/capture_screenshots.py --check-dependencies |
| Sample reports not found | Run from the repository root |
| Screenshots too large | Lower JPEG_QUALITY or reduce the viewport size |
| Browser launch fails | Run playwright install-deps (Linux only) |
After generating new screenshots, confirm that README.md references them
(for example, sample-reports/dashboard-overview-light.png and
sample-reports/findings-table.png) with accurate captions.
After regenerating a sample report, rebuild the changes sample and golden files with the procedure in Rebuilding the Changes Sample and Golden Files.
- Keep screenshots optimized: target 200-300 KB per image.
- Use descriptive filenames:
dashboard-overview-light.png, notscreenshot1.png. - Update HTML and screenshots together when making UI changes.
- Check rendering in GitHub's Markdown preview.
- Keep all screenshot tooling in
sample-reports/.
Every releasable behavior or deployment change must update the root
CHANGELOG.md under ## Unreleased; a release need not be created for every
merged change. Consolidate multiple commits for one logical change into one
user-focused entry under the applicable Added, Changed, Fixed,
Deprecated, Removed, or Security subsection.
Whenever deployable files change, the Unreleased section needs a
Deployment impact subsection. State exactly which actions users must take
(no deployment, a CodeBuild run, a single-account infrastructure update, a
multi-account member-role StackSet update, and/or a multi-account central
infrastructure update), name every changed top-level template, and give the
required order; the member-role StackSet update must complete before CodeBuild
runs. For the mapping from changed paths to required actions, see
Determine what changed. When a release is
pinned by tag or commit, remind users to update the GitHubBranch stack
parameter.
When a version is tagged, move the accumulated entries into a dated
## <version> - YYYY-MM-DD section and recreate an empty Unreleased
section. Do not rewrite released entries except to correct an error.
GitHub Actions workflows validate code quality and security on pull requests.
| Workflow | File | What it checks |
|---|---|---|
| Python Code Quality | .github/workflows/python-lint.yml |
ruff check and ruff format --check on the PR's changed .py files |
| AI/ML Security Assessment Tests | .github/workflows/python-tests.yml |
Four pytest steps: tests/ with coverage; responsible_ai_grc_tests/; the report pipeline (tests/test_consolidate_responsible_ai_grc.py, then test_generate_report.py from the report package directory); and the assessment_history 100% line and branch coverage gate |
| CloudFormation Lint | .github/workflows/cfn-lint.yml |
cfn-lint on the deployment and SAM templates |
| SAM Validate & Build | .github/workflows/sam-validate.yml |
sam validate --lint on both SAM templates and sam build on the single-account template |
| ASH Security Scan | .github/workflows/ash-security-scan.yml |
Secrets, dependency vulnerabilities, and IaC misconfigurations in changed files |
PR checks target main. The test workflow runs only when its watched paths
change: aiml-security-assessment/functions/security/**, tests/**,
assessment_history/**, buildspec.yml, deployment/**, sample-reports/**,
consolidate_html_reports.py, or the workflow file itself. It also runs on
pushes to main and develop that touch those paths.
cfn-lint suppressions for IAM actions not yet in its database (for example,
bedrock-agentcore actions) are configured in .cfnlintrc at the repository
root.
| Workflow | File | Trigger |
|---|---|---|
| ASH Full Repository Scan | .github/workflows/ash-full-repository-scan.yml |
Push to main, monthly schedule, and manual dispatch |
| Labeler | .github/workflows/label.yml |
Every pull request (pull_request_target); applies labels from .github/labeler.yml by changed path: bedrock, sagemaker, agentcore, report, deployment, documentation, and infrastructure |
This is the canonical command list linked from AGENTS.md.
Run Python, pip, pytest, Ruff, and cfn-lint through .venv/bin/ so a stale
shell activation cannot reach the system interpreter. Use git and the
system-installed sam normally.
# Bootstrap or refresh the repository-local virtual environment
# (create it first with `python3.12 -m venv .venv`; ruff and cfn-lint are not
# in any requirements file, so they are installed explicitly)
.venv/bin/pip install ruff cfn-lint -r tests/requirements.txt \
-r aiml-security-assessment/functions/security/agentcore_assessments/requirements.txt \
-r aiml-security-assessment/functions/security/agent_registry_assessments/requirements.txt \
-r aiml-security-assessment/functions/security/bedrock_assessments/requirements.txt \
-r aiml-security-assessment/functions/security/cleanup_bucket/requirements.txt \
-r aiml-security-assessment/functions/security/responsible_ai_grc_assessments/requirements.txt \
-r aiml-security-assessment/functions/security/generate_consolidated_report/requirements.txt \
-r aiml-security-assessment/functions/security/iam_permission_caching/requirements.txt \
-r aiml-security-assessment/functions/security/owasp_assessments/requirements.txt \
-r aiml-security-assessment/functions/security/resolve_regions/requirements.txt \
-r aiml-security-assessment/functions/security/sagemaker_assessments/requirements.txt
.venv/bin/pip check
# Lint and format the whole repository, including uncommitted work.
# This covers the changed Python files that CI checks.
# Ruff automatically loads the repository's ruff.toml.
.venv/bin/ruff check .
.venv/bin/ruff format --check .
# Environment for every pytest session
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
# 1. Main suite: one session covers every package under tests/
.venv/bin/python -m pytest tests/ -v --tb=short
# 2. assessment_history: 100% line and branch coverage
.venv/bin/python -m pytest tests/test_assessment_history_*.py \
--cov=assessment_history --cov-branch --cov-report=term-missing \
--cov-fail-under=100
# 3. Responsible AI GRC suite: separate session with its own conftest
.venv/bin/python -m pytest aiml-security-assessment/functions/security/responsible_ai_grc_tests/ -v --tb=short
# 4. Report pipeline: runs from the report package directory
(cd aiml-security-assessment/functions/security/generate_consolidated_report \
&& ../../../../.venv/bin/python -m pytest test_generate_report.py -v --tb=short)
# Responsible AI GRC provenance (when changing its app.py or catalog)
.venv/bin/python generate_provenance.py --check
# CloudFormation lint
.venv/bin/cfn-lint deployment/*.yaml \
aiml-security-assessment/template.yaml \
aiml-security-assessment/template-multi-account.yaml
# SAM validate and build (sam is installed system-wide)
(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)If generate_provenance.py --check reports stale provenance, run
.venv/bin/python generate_provenance.py and commit the regenerated
responsible_ai_grc_assessments/provenance.json; do not edit it by hand.
For common issues, CodeBuild and Step Functions debugging, and incomplete findings, see Debugging in the Troubleshooting Guide.
As you add services, checks, lenses, or compliance standards, update this guide so future contributors can build on your work.


