From 06c0be83143b6e08308e0803de76b66054312ab9 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Mon, 28 Sep 2026 00:47:16 +0000 Subject: [PATCH 1/2] fix(iam): shrink shared lambda boundary to the four-statement floor (PLAT-52) PermissionsBoundaryUsageCount is 0 in prod and dev, so the shared seahaven-lambda-execution-boundary drops the packed IsProdAccount data-plane statements and keeps CloudWatchLogsWrite, CloudWatchLogsDescribe, XRay, and Ec2Eni. Co-authored-by: Adam Moussa --- .../deploy-substrate.template.yaml | 228 +----------------- 1 file changed, 9 insertions(+), 219 deletions(-) diff --git a/lib/deploy-substrate/deploy-substrate.template.yaml b/lib/deploy-substrate/deploy-substrate.template.yaml index e6e6d18..e5c3c9f 100644 --- a/lib/deploy-substrate/deploy-substrate.template.yaml +++ b/lib/deploy-substrate/deploy-substrate.template.yaml @@ -134,9 +134,11 @@ Description: >- # on the synthesized PolicyDocument with ${AWS::AccountId} resolved, and UPDATE # THE NUMBERS in the same edit. # -# Shared LambdaExecutionBoundary (legacy ceiling; do not widen): 5986 -# characters / 15 statements as of 2026-08-10 (PLAT-100). Headroom 158. -# Leave it unchanged until live roles retarget (PLAT-52 phase 2). +# Shared LambdaExecutionBoundary (floor only; do not widen): 708 +# characters / 4 statements as of 2026-09-28 (PLAT-52). Headroom 5436. +# Statements: CloudWatchLogsWrite, CloudWatchLogsDescribe, XRay, Ec2Eni. +# Packed IsProdAccount data-plane statements removed once +# PermissionsBoundaryUsageCount was 0 in prod and dev. # # Per-workload policies (floor + own data plane). Compact sizes recorded # after synth (prod, ${AWS::AccountId}=011934824531): @@ -523,223 +525,11 @@ Resources: - ec2:DescribeVpcs Resource: "*" - # ── Shared workload data-plane (PLAT-93 PolicySize consolidation) ─── - # Per-workload secret/DDB/S3 SIDs were merged so meal-order-manager - # (PLAT-70) can fit under the 6,144-character managed-policy cap - # without introducing new action wildcards. Exact secret ARNs only. - # End-state isolation remains PLAT-52 / INFRA-187. - # - # WorkloadSecrets covers: afi-backup-monitor (PLAT-56), - # front-integrations (PLAT-72), procurement-ingest (PLAT-86), - # meal-order-manager (PLAT-70). - - !If - - IsProdAccount - - Sid: WorkloadSecrets - Effect: Allow - Action: - - secretsmanager:GetSecretValue - Resource: - - arn:aws:secretsmanager:us-east-1:011934824531:secret:afi-api-key-w0E02a - - arn:aws:secretsmanager:us-east-1:011934824531:secret:afi-slack-webhook-T4oR3G - - arn:aws:secretsmanager:us-east-1:011934824531:secret:front-integrations/front-api-token-UXKv0U - - arn:aws:secretsmanager:us-east-1:011934824531:secret:front-integrations/slack-bot-token-giGfA7 - - arn:aws:secretsmanager:us-east-1:011934824531:secret:front-integrations/google-service-account-dqv3Bo - - arn:aws:secretsmanager:us-east-1:011934824531:secret:procurement-ingest/web-ui-auth-token-ApAMmr - - arn:aws:secretsmanager:us-east-1:011934824531:secret:workorder-ingest/shoc-webhook-hmac-puYTcB - - arn:aws:secretsmanager:us-east-1:011934824531:secret:meal-order-manager/slack-bot-token-ZGmSGw - - !Ref AWS::NoValue - - # WorkloadDynamoDB: enumerated union of front-integrations CRUD + - # procurement-ingest CRUD/stream actions. Meal-order table ARNs appended. - # Stream actions on non-stream tables are inert at the ceiling. - - !If - - IsProdAccount - - Sid: WorkloadDynamoDB - Effect: Allow - Action: - - dynamodb:GetItem - - dynamodb:PutItem - - dynamodb:UpdateItem - - dynamodb:DeleteItem - - dynamodb:Query - - dynamodb:Scan - - dynamodb:BatchGetItem - - dynamodb:BatchWriteItem - - dynamodb:DescribeTable - - dynamodb:ConditionCheckItem - - dynamodb:GetRecords - - dynamodb:GetShardIterator - - dynamodb:DescribeStream - # ListStreams is a collection API (Resource "*"); ARN-scoping - # it is a silent no-op. Runtime stream consumers use the - # stream ARN via DescribeStream/GetRecords above. - Resource: - - !Sub "arn:aws:dynamodb:us-east-1:${AWS::AccountId}:table/front-sla-alerts" - - !Sub "arn:aws:dynamodb:us-east-1:${AWS::AccountId}:table/front-sla-alerts/index/*" - - !Sub "arn:aws:dynamodb:us-east-1:${AWS::AccountId}:table/purchase-orders" - - !Sub "arn:aws:dynamodb:us-east-1:${AWS::AccountId}:table/purchase-orders/*" - - !Sub "arn:aws:dynamodb:us-east-1:${AWS::AccountId}:table/verified-sites" - - !Sub "arn:aws:dynamodb:us-east-1:${AWS::AccountId}:table/verified-sites/*" - - !Sub "arn:aws:dynamodb:us-east-1:${AWS::AccountId}:table/pending-site-review" - - !Sub "arn:aws:dynamodb:us-east-1:${AWS::AccountId}:table/pending-site-review/*" - - !Sub "arn:aws:dynamodb:us-east-1:${AWS::AccountId}:table/work-orders" - - !Sub "arn:aws:dynamodb:us-east-1:${AWS::AccountId}:table/work-orders/*" - - !Sub "arn:aws:dynamodb:us-east-1:${AWS::AccountId}:table/work-order-comments" - - !Sub "arn:aws:dynamodb:us-east-1:${AWS::AccountId}:table/work-order-comments/*" - - !Sub "arn:aws:dynamodb:us-east-1:${AWS::AccountId}:table/meal-order-manager-orders" - - !Sub "arn:aws:dynamodb:us-east-1:${AWS::AccountId}:table/meal-order-manager-orders/index/*" - - !Ref AWS::NoValue - - # ── procurement-ingest remaining data plane + meal-order S3 (PLAT-86/70) ─ - # WorkloadS3 keeps the prior ProcurementIngestS3 action list and appends - # meal-order form/reports bucket ARNs (same object CRUD shape). - - !If - - IsProdAccount - - Sid: WorkloadS3 - Effect: Allow - Action: - - s3:GetObject* - - s3:GetBucket* - - s3:List* - - s3:PutObject* - - s3:DeleteObject* - - s3:AbortMultipartUpload - Resource: - - !Sub "arn:aws:s3:::po-ingest-emails-${AWS::AccountId}" - - !Sub "arn:aws:s3:::po-ingest-emails-${AWS::AccountId}/*" - - !Sub "arn:aws:s3:::workorder-ingest-emails-${AWS::AccountId}" - - !Sub "arn:aws:s3:::workorder-ingest-emails-${AWS::AccountId}/*" - - !Sub "arn:aws:s3:::meal-order-manager-form-${AWS::AccountId}" - - !Sub "arn:aws:s3:::meal-order-manager-form-${AWS::AccountId}/*" - - !Sub "arn:aws:s3:::meal-order-manager-reports-${AWS::AccountId}" - - !Sub "arn:aws:s3:::meal-order-manager-reports-${AWS::AccountId}/*" - - !Ref AWS::NoValue - - !If - - IsProdAccount - - Sid: ProcurementIngestSqs - Effect: Allow - Action: - - sqs:SendMessage - - sqs:ReceiveMessage - - sqs:DeleteMessage - - sqs:GetQueueAttributes - - sqs:GetQueueUrl - - sqs:ChangeMessageVisibility - Resource: - - !Sub "arn:aws:sqs:us-east-1:${AWS::AccountId}:po-ingest-*" - - !Sub "arn:aws:sqs:us-east-1:${AWS::AccountId}:WorkorderIngestStack-*" - - !Sub "arn:aws:sqs:us-east-1:${AWS::AccountId}:workorder-shoc-emitter-*" - - !Ref AWS::NoValue - - !If - - IsProdAccount - - Sid: ProcurementIngestKms - Effect: Allow - Action: - - kms:Decrypt - - kms:DescribeKey - - kms:Encrypt - - kms:GenerateDataKey* - - kms:ReEncrypt* - Resource: - - arn:aws:kms:us-east-1:011934824531:key/be5fa4cb-c546-40fe-a13d-c7bec79f5d12 - - arn:aws:kms:us-east-1:011934824531:key/d10fd1f0-a61a-4405-8568-85e9fd11ba18 - - !Ref AWS::NoValue - - !If - - IsProdAccount - - Sid: ProcurementIngestBedrock - Effect: Allow - Action: - - bedrock:InvokeModel - - bedrock:InvokeModelWithResponseStream - Resource: - - !Sub "arn:aws:bedrock:us-east-1:${AWS::AccountId}:inference-profile/us.anthropic.claude-haiku-4-5-20251001-v1:0" - - arn:aws:bedrock:us-east-1::foundation-model/anthropic.claude-haiku-4-5-20251001-v1:0 - - arn:aws:bedrock:us-east-2::foundation-model/anthropic.claude-haiku-4-5-20251001-v1:0 - - arn:aws:bedrock:us-west-2::foundation-model/anthropic.claude-haiku-4-5-20251001-v1:0 - - !Ref AWS::NoValue - # site-alerts publish shared by procurement-ingest alarms and - # meal-order-manager (PLAT-70); no separate MealOrder SNS statement. - - !If - - IsProdAccount - - Sid: ProcurementIngestSns - Effect: Allow - Action: - - sns:Publish - Resource: - - !Sub "arn:aws:sns:us-east-1:${AWS::AccountId}:site-alerts" - - !Ref AWS::NoValue - - # ── seahaven-site (PLAT-91) — content-deploy role data plane ──────── - # TF creates githubdeploy-seahaven-site under /tf-managed/ with this - # boundary as ceiling. Role policy is S3 sync + CloudFront invalidate - # only; no Lambda. Exact origin bucket + distribution-scoped invalidate. - - !If - - IsProdAccount - - Sid: SeahavenSiteOriginS3 - Effect: Allow - Action: - - s3:GetObject - - s3:PutObject - - s3:DeleteObject - - s3:GetObjectTagging - - s3:PutObjectTagging - - s3:ListBucket - - s3:GetBucketLocation - Resource: - - arn:aws:s3:::seahaven-site-prod - - arn:aws:s3:::seahaven-site-prod/* - - !Ref AWS::NoValue - - !If - - IsProdAccount - - Sid: SeahavenSiteCloudFrontInvalidate - Effect: Allow - Action: - - cloudfront:CreateInvalidation - - cloudfront:GetInvalidation - Resource: - - !Sub "arn:aws:cloudfront::${AWS::AccountId}:distribution/*" - - !Ref AWS::NoValue - - # ── meal-order-manager (PLAT-70 / PLAT-100) — not covered above ───── - # Secrets → WorkloadSecrets; DynamoDB → WorkloadDynamoDB; S3 → WorkloadS3; - # SNS → ProcurementIngestSns. SSM + Lambda Invoke + execute-api share - # one Sid (scoped Resources only) so PolicySize stays under 6,144 — - # a standalone execute-api Sid is ~243 chars against 241 headroom and - # would fail UPDATE with LimitExceeded. SES stays in its own Sid: - # Resource:"*" must not share a statement with execute-api:Invoke - # (that would allow Invoke on every API in the account). - # Weekly-menu OIDC identity policy pins the API id; boundary pins - # method/path only. - - !If - - IsProdAccount - - Sid: MealOrderManager - Effect: Allow - Action: - - ssm:GetParameter - - lambda:InvokeFunction - - execute-api:Invoke - Resource: - - !Sub "arn:aws:ssm:us-east-1:${AWS::AccountId}:parameter/meal-order-manager/*" - - !Sub "arn:aws:lambda:us-east-1:${AWS::AccountId}:function:meal-order-manager-*" - - !Sub "arn:aws:execute-api:us-east-1:${AWS::AccountId}:*/*/GET/api/publish/settings" - - !Sub "arn:aws:execute-api:us-east-1:${AWS::AccountId}:*/*/POST/api/publish/menu" - - !Ref AWS::NoValue - - !If - - IsProdAccount - - Sid: MealOrderManagerSes - Effect: Allow - Action: - - ses:SendRawEmail - Resource: "*" - - !Ref AWS::NoValue - # ── FURTHER PER-WORKLOAD DATA-PLANE ──────────────────────────────── - # Do not add statements here. Remaining SAM stacks: create - # seahaven-lambda-execution-boundary- below and append its ARN - # to SamCfnIamManagementPolicy only (WIDENING PATH). New HCP stacks - # do not append here. This shared document stays unchanged until live - # roles retarget (PLAT-52 phase 2) and PermissionsBoundaryUsageCount - # reaches 0. + # Do not add statements here. The shared document is the four-statement + # floor (PLAT-52). Remaining SAM stacks use + # seahaven-lambda-execution-boundary- below. New HCP stacks + # do not append here. # --------------------------------------------------------------------------- # Per-workload Lambda execution boundaries (PLAT-52 phase 1) # From 59645952ef477dfe48702a6550578fc653c49894 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Mon, 28 Sep 2026 01:06:48 +0000 Subject: [PATCH 2/2] docs(iam): correct shared boundary size and scoping notes (PLAT-52) The dev floor is the same 708-character document as the shared policy. 691 was stale. The scoping note now says the shared document is the four-statement floor, and allow-list retirement is a follow-up. Co-authored-by: Adam Moussa --- .../deploy-substrate.template.yaml | 38 +++++++++---------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/lib/deploy-substrate/deploy-substrate.template.yaml b/lib/deploy-substrate/deploy-substrate.template.yaml index e5c3c9f..638277c 100644 --- a/lib/deploy-substrate/deploy-substrate.template.yaml +++ b/lib/deploy-substrate/deploy-substrate.template.yaml @@ -149,9 +149,10 @@ Description: >- # seahaven-lambda-execution-boundary-seahaven-site: 1159 / 6 statements # seahaven-lambda-execution-boundary-seahaven-door-unlock-api: measure after deploy (PLAT-76) # seahaven-lambda-execution-boundary-paychex-integrations: 3250 / 10 statements (PLAT-228) -# Dev copies are floor-only (691 / 4) via IsProdAccount, except -# meal-order-manager (PLAT-210): DynamoDB/S3/SSM/invoke plus the -# seahaven-dev slack-bot-token ARN. SNS, SQS (Paychex), and SES stay prod. +# The same four floor statements measure 708 / 4 on the dev policies once +# IsProdAccount drops the prod-only statements. meal-order-manager +# (PLAT-210) also keeps DynamoDB/S3/SSM/invoke plus the seahaven-dev +# slack-bot-token ARN. SNS, SQS (Paychex), and SES stay prod. # # Guardrail PolicyDocuments also cap at 6,144. Each extra allow-list ARN # is copied into four Sids in EACH of SamCfnIamManagementPolicy and @@ -216,16 +217,15 @@ Resources: # intersection of the role's own policies and this boundary, so a misconfigured # SAM role can never exceed what is listed here. # - # SCOPING RULE (PLAT-52 phase 1, 2026-08-13). New workloads get their own - # ManagedPolicy seahaven-lambda-execution-boundary-: the fleet-wide - # floor (CloudWatchLogsWrite / CloudWatchLogsDescribe / XRay / Ec2Eni) plus - # that workload's data plane, derived from ITS OWN template. Do not add new - # data-plane statements to the shared seahaven-lambda-execution-boundary - # document — it is the legacy ceiling for roles not yet retargeted and stays - # unchanged until PermissionsBoundaryUsageCount is 0. INFRA-186 reduced this - # copy to a floor and later migrations packed data plane back into it under - # the 6,144-character cap (PLAT-93 / PLAT-100). Per-workload policies are - # the escape hatch from that cap and from the shared-ceiling residual. + # SCOPING RULE (PLAT-52). New workloads get their own ManagedPolicy + # seahaven-lambda-execution-boundary-: the four-statement floor + # (CloudWatchLogsWrite / CloudWatchLogsDescribe / XRay / Ec2Eni) plus that + # workload's data plane, derived from its own template. The shared + # seahaven-lambda-execution-boundary document is that same four-statement + # floor. Do not add data-plane statements to it. PermissionsBoundaryUsageCount + # is 0 in prod and dev, so the packed IsProdAccount statements are removed. + # Retiring the SAM allow-list of boundary ARNs in SamCfnIamManagementPolicy + # is a follow-up pull request. # # A resource pattern that genuinely CANNOT be scoped keeps its wildcard WITH a # written justification on the statement: CloudWatchLogsDescribe, XRay and @@ -533,12 +533,12 @@ Resources: # --------------------------------------------------------------------------- # Per-workload Lambda execution boundaries (PLAT-52 phase 1) # - # Each policy is the fleet floor plus that workload's data plane, split from - # the shared document above without editing it. Live roles keep the shared - # ARN until app-repo retargets. Guardrail StringEquals lists include both. - # Floor statements are YAML-anchored on AfiBackupMonitorBoundary; later - # policies alias them. Floor rationale lives on LambdaExecutionBoundary. - # Prod-only data plane stays behind IsProdAccount (same as the shared copy). + # Each policy is the fleet floor plus that workload's data plane. + # Guardrail StringEquals lists still include the shared ARN and each + # per-workload ARN until the allow-list follow-up. Floor statements are + # YAML-anchored on AfiBackupMonitorBoundary; later policies alias them. + # Floor rationale lives on LambdaExecutionBoundary. + # Prod-only data plane on these policies stays behind IsProdAccount. # --------------------------------------------------------------------------- AfiBackupMonitorBoundary: Type: AWS::IAM::ManagedPolicy