Security & Hardening
Infrastructure, IAM, tenant-isolation, and operational hardening applied throughout the platform.
Infrastructure Hardening
- S3 enforce_ssl -- Both the frontend and artifacts S3 buckets require HTTPS (
enforce_ssl=True), auto-generating a bucket policy that denies non-TLS requests. - CloudFront security headers -- A custom
ResponseHeadersPolicyon all CloudFront behaviors sets:Strict-Transport-Security: max-age=63072000; includeSubDomains; preload(HSTS, 2 years)X-Frame-Options: DENYX-Content-Type-Options: nosniffX-XSS-Protection: 1; mode=blockReferrer-Policy: strict-origin-when-cross-origin
- Least-privilege IAM -- Bedrock permissions scoped to
bedrock:InvokeModelandbedrock:InvokeModelWithResponseStream(notbedrock:*). Each Step Functions step Lambda has its own role with only the IAM actions it needs (no shared kitchen-sink policy). CDK feature flag@aws-cdk/aws-iam:minimizePoliciesmerges overlapping statements. - Tenant isolation -- Workflows, flows, and deployments are owner-scoped to the Cognito JWT
subclaim. Cross-tenant reads return 404 (not 403) to avoid leaking record existence. Enforced inbackend/src/app/services/auth.py::assert_owner. - Shared runtime execution role -- The platform pre-creates one
AgentCoreRuntime-{project}-{env}-sharedrole at stack-init withs3:GetObjecton the artifacts bucket + Bedrock + tool permissions, rather than minting a fresh per-deploy role. This avoids the IAM-cache propagation race that otherwise blocks first-timeCreateAgentRuntimecalls. TheDELETE /api/runtime/{id}path explicitly skips deleting this shared role. - Cognito hardening --
prevent_user_existence_errors=ENABLED,USER_PASSWORD_AUTHdisabled (SRP only),ADMIN_NO_SRP_AUTHdisabled. - TLS 1.2 minimum -- CDK feature flag
@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021enforces TLS 1.2+ on CloudFront. - CORS -- API Gateway allows
http://localhost:5173only (for local dev). In production, the frontend and API are served from the same CloudFront domain, making requests same-origin — no CORS headers needed.
CDK-NAG (AWS Solutions Checks)
CDK-NAG (cdk_nag.AwsSolutionsChecks) runs during every cdk synth to flag security best-practice violations. Suppressions are scoped per-construct via NagSuppressions.add_resource_suppressions(<construct>, [...], apply_to_children=True) in PlatformStack._apply_nag_suppressions() — never stack-wide. Each suppression names the specific construct that legitimately needs the exception (e.g. shared runtime exec role for IAM4/IAM5, Cognito user pool for COG2/COG4/COG8, the State Machine for SF1) so a future contributor adding a wildcard policy to an unrelated construct fails the build instead of silently absorbing the finding. Unsuppressed violations cause synthesis to fail.
Reliability & Operational Hardening
- RemovalPolicy gating — DynamoDB tables, S3 buckets, and the Cognito user pool default to
RETAINin production. SetENVIRONMENT_NAMEto one ofdev|test|sandbox|preview|ephemeral(or exportAGENTCORE_ALLOW_DESTROY=true) to switch toDESTROY+auto_delete_objects=Truefor fast iteration. Guards against accidental data loss oncdk destroyagainst a long-running prod stack. - runtime_id-index GSI —
DeploymentsTablehas a GSI keyed onruntime_idsoDELETE /api/runtime/{id}andPOST /api/test-runtimeresolve via O(1) Query instead of O(N) Scan. Falls back to paginated Scan when the GSI is absent (covers stacks deployed before the GSI was added). - Cleanup-failure aggregation —
handle_delete_runtimetracks per-resource cleanup failures (mcp_server_runtime,policy_engine,memory,guardrail,gateway,kb_lambda,knowledge_base) and only returnssuccess=truewhen all cleanups succeed. Prevents reporting success when a Cognito pool / KB / guardrail leaks. - Idempotent guardrail creation —
guardrails_stepcatchesResourceAlreadyExistsExceptionfromcreate_guardrail, then either updates the existing guardrail in place or retries with a UUID-suffixed name. Step Functions retries no longer break a partially-deployed flow. - Gateway deploy rollback —
deploy_gatewaytracks partial state (Cognito client info, gateway ID, tool Lambdas, custom-tool roles) and runscleanup_gateway_resourceson any mid-flow exception before re-raising. No more orphan Cognito pools / Lambdas after a transient AgentCore error. - SSRF guard on Gateway URL fetches — Any URL the gateway deployer follows is validated before
urlopenagainst a 21-network IPv4/IPv6 denylist (loopback, link-local incl. IMDS169.254.169.254and Lambda creds169.254.170.2, RFC1918, CGNAT, multicast, ULA, IPv4-mapped IPv6). DNS resolution is performed up-front so hostname rebinding (evil.com → 169.254.169.254) cannot bypass the check. OptionalOIDC_DISCOVERY_HOST_ALLOWLISTenv var pins discovery hosts to an operator-approved set. Same defense applied to the embedded_do_fetch_webpagetool Lambda. - Two separable outbound allowlists — The same validator also guards non-OIDC fetches (connector spec URLs, a LiteLLM gateway or registry base URL), so pinning your identity provider used to silently pin those too, and rejections cited OIDC config the operator had set for an unrelated reason. Non-discovery fetches now prefer
OUTBOUND_HOST_ALLOWLISTand fall back toOIDC_DISCOVERY_HOST_ALLOWLISTwhen it is unset — so an existing deployment is never loosened, and an operator who wants the two policies stated separately now can. OIDC discovery reads only its own variable: a general outbound allowlist must not widen which identity providers the platform will fetch metadata from. Either way an allowlist is a narrowing control — the private-IP denylist runs regardless of any allowlist match. provider_base_urlvalidation — The customer-supplied model-provider base URL (injected asPROVIDER_BASE_URL, and the destination the runtime sendsPROVIDER_API_KEYto as a bearer credential) is validated: https-only, a host is required, nouser:pass@userinfo, no whitespace or control characters (a newline would forge a second runtime environment variable), and no link-local literal (IMDS). Deliberately not routed through the private-CIDR SSRF guard above, because the dialer here is the AgentCore Runtime — which supports VPC egress — not the control plane, so a self-hosted proxy on a private address is the intended configuration for this field. Pure string validation, no network I/O, safe on every request.- OTEL secret namespace lock — User-supplied
auth_header_secret_arn(per-canvas Observability node) is validated against^arn:aws:secretsmanager:.*:secret:agentcore-otel/.*before being granted to the runtime IAM role. Foreign ARNs are rejected at the API boundary; tenant cannot trick the runtime into reading + exfiltrating arbitrary secrets via OTLP headers. Secrets created viaPOST /api/observability/credentialsare tagged withowner_sub(Cognito sub) so cross-tenant ownership is auditable. - Tenant isolation hardening — The
X-Test-Subheader bypass is removed fromservices/auth.py; tests inject sub via FastAPIdependency_overrides.assert_ownerreturns 404 for None-owner records (no legacy-data bypass). Flow/workflow listing uses strictowner_sub == caller_subequality (no None-coalescing fallback that previously surfaced legacy rows in every tenant's list). - MCPClient wiring proof gate — When a runtime is configured with
GATEWAY_URLbutMCPClient.list_tools_sync()returns an empty list, the runtime raisesRuntimeError("Gateway MCPClient returned 0 tools…")at first invocation rather than letting the agent bluff a canary out of the system prompt. This makes silent gateway-wiring failures (an agent that "passes" tests without ever reaching its tools) structurally impossible. - Cedar ENFORCE policy enforcement — Fail-closed, converge-in-place Cedar policy enforcement on Gateway tools. See the full write-up in Enterprise Capabilities — Cedar ENFORCE.
- DDB GSI NULL-key safety —
DeploymentStateserializer omits None-valued optional fields (runtime_id,gateway_url,completed_at, etc.) viamodel_dump(mode="json", exclude_none=True)so theruntime_id-indexGSI accepts the initial intake write. Pairs with the runtime_id-index GSI for cost-bounded delete/test/invoke lookups. - Frontend ErrorBoundary —
frontend/src/components/ErrorBoundary.tsxwraps the app root. A render-time exception shows a recoverable banner with reset/reload buttons instead of a blank screen. - Auto-save error toast —
useAutoSaveexposeslastSaveErrorso a transient save failure renders a dismissable toast instead of being clobbered by a subsequent successful read.
Pre-commit Hooks
.pre-commit-config.yaml includes:
detect-secrets-- Prevents accidental secret commits (API keys, passwords) with a baseline filedetect-private-key-- Blocks commits containing private keyscheck-added-large-files-- Rejects files over 1MBno-commit-to-branch-- Prevents direct commits tomainruff-- Python linting and formatting checks- Standard checks: trailing whitespace, end-of-file fixer, YAML/JSON validation, merge conflict markers
Install with pip install pre-commit && pre-commit install. Run manually with pre-commit run --all-files.
Static analysis
The codebase is scanned with static-analysis security tooling; known hardening items are tracked as issues.