From 2dbe99ef0bed0c5c4f4bebc25b105daad0d8142e Mon Sep 17 00:00:00 2001 From: Adam Moussa <166072409+amoussa1229@users.noreply.github.com> Date: Thu, 10 Sep 2026 19:30:00 +0000 Subject: [PATCH] chore(pay): disable weekly-post payroll SES email (PLAT-135) (#248) Empty PAYROLL_RECIPIENTS and drop ses:SendEmail so Monday Slack posts stay, SES pay mail stops. --- template.yaml | 26 +------------------------- tests/weekly_post/test_handler.py | 22 ++++++++++++++++++++++ 2 files changed, 23 insertions(+), 25 deletions(-) diff --git a/template.yaml b/template.yaml index c8266f1..9799205 100644 --- a/template.yaml +++ b/template.yaml @@ -166,7 +166,7 @@ Resources: SLACK_BOT_TOKEN_SECRET: afterhours-shift-manager/slack-bot-token SHIFT_CHANNEL: !Ref ShiftChannel SES_SENDER: noreply@seahaven.com - PAYROLL_RECIPIENTS: payroll@seahaven.com + PAYROLL_RECIPIENTS: "" PAY_REPORT_USER: U0A3SC48T47 TZ: !Ref Timezone CHECKCOMPONENTS_QUEUE_URL: !Ref CheckcomponentsQueueUrl @@ -180,30 +180,6 @@ Resources: - secretsmanager:GetSecretValue Resource: - !Sub "arn:aws:secretsmanager:${AWS::Region}:${AWS::AccountId}:secret:afterhours-shift-manager/slack-bot-token-*" - - Effect: Allow - Action: - - ses:SendEmail - Resource: - # The From address (noreply@seahaven.com) is NOT a standalone - # verified SES identity — it is covered by the verified DOMAIN - # identity seahaven.com, which is the identity SES authorizes - # SendEmail against. Granting identity/noreply@seahaven.com (a - # non-existent identity) caused AccessDenied; the domain ARN is - # the correct least-privilege resource. Scoped to this one domain, - # not identity/* (every identity in the account). - - !Sub "arn:aws:ses:${AWS::Region}:${AWS::AccountId}:identity/seahaven.com" - # The sending identity has a default configuration set - # (seahaven-email-events); SES authorizes SendEmail against the - # config-set resource too, so it must be granted alongside the - # identity or the send is denied. Scoped to the known set name. - - !Sub "arn:aws:ses:${AWS::Region}:${AWS::AccountId}:configuration-set/seahaven-email-events" - # The domain identity covers every address under seahaven.com, so - # without this the role could send-as any @seahaven.com mailbox. - # Pin the From to the one legitimate sender (matches SES_SENDER) so - # a compromised function can't spoof internal senders (BEC). - Condition: - StringEquals: - ses:FromAddress: noreply@seahaven.com - Effect: Allow Action: - sqs:SendMessage diff --git a/tests/weekly_post/test_handler.py b/tests/weekly_post/test_handler.py index b69f73d..f718ff4 100644 --- a/tests/weekly_post/test_handler.py +++ b/tests/weekly_post/test_handler.py @@ -1,6 +1,7 @@ """Tests for the weekly-post Lambda handler orchestration.""" import json +from pathlib import Path from unittest.mock import MagicMock import pytest @@ -253,3 +254,24 @@ def test_payload_skips_fallback_and_zero(weeklypost_app): assert payload["windowEnd"] == "2026-06-07" assert payload["lines"] == [{"extension": "114", "amount": "75.50"}] assert "payPeriodId" not in payload + + +def test_send_pay_email_skips_when_recipients_empty(weeklypost_app, monkeypatch): + monkeypatch.setenv("PAYROLL_RECIPIENTS", "") + ses = MagicMock(name="ses") + + def client(svc, **kw): + if svc == "ses": + return ses + return MagicMock(name=svc) + + monkeypatch.setattr(weeklypost_app.boto3, "client", client) + weeklypost_app._send_pay_email("Jun 1 to Jun 7", {"totals": {}, "breakdown": []}) + ses.send_email.assert_not_called() + + +def test_weekly_post_payroll_recipients_empty_and_no_ses_grant(): + text = (Path(__file__).resolve().parents[2] / "template.yaml").read_text() + assert 'PAYROLL_RECIPIENTS: ""' in text + assert "PAYROLL_RECIPIENTS: payroll@seahaven.com" not in text + assert "ses:SendEmail" not in text