tenant email spec: the SNS trust anchor is the pinned HTTPS fetch, not a chain build
Second correction to the same paragraph, and this one reverses the first. Requiring the fetched certificate to verify against system roots is impossible against the real wire format: AWS serves exactly one cert in SimpleNotificationService-*.pem — the bare leaf — and its issuer is an intermediate distributed via AIA, which Go's x509.Verify never fetches. Review proved it against a real captured PEM; the requirement as written would have refused every genuine SNS message. The anchor is https to a region-pinned sns.<region>.amazonaws.com host with redirects refused, plus a subject assertion as defence in depth. The HTTPS client validates AWS's TLS chain against system roots at the layer where that check actually works. Also records that the host pin must not leave the second label free — sns.s3.amazonaws.com is the published sns-validator bypass shape.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 file changed,
+22
−5
docs/superpowers/specs/2026-08-19-tenant-email-design.md+22 −5
diff --git a/docs/superpowers/specs/2026-08-19-tenant-email-design.md b/docs/superpowers/specs/2026-08-19-tenant-email-design.md| index f2eda5eeb..2a3d05a25 100644 |
| --- a/docs/superpowers/specs/2026-08-19-tenant-email-design.md |
| +++ b/docs/superpowers/specs/2026-08-19-tenant-email-design.md |
| @@ -449,11 +449,28 @@ Two security requirements, both non-negotiable: |
| the topic ARN — an identifier that appears in IaC and logs, not a |
| secret. |
| -- **The fetched certificate must be verified against system roots and its |
| - subject checked.** Pinning the host without validating the chain still |
| - trusts whatever PEM comes back, and an open redirect under the allowed |
| - host would walk the fetch off AWS entirely. Neither check is sufficient |
| - alone; both are required. |
| +- **The trust anchor is the pinned HTTPS fetch, NOT a chain build over the |
| + returned PEM.** **CORRECTED 2026-08-19, second time**: an earlier version |
| + of this section required verifying the fetched certificate against system |
| + roots. That is impossible against the real wire format and would have |
| + refused every genuine message. AWS serves exactly ONE certificate in |
| + `SimpleNotificationService-*.pem` — the bare leaf, no intermediate — and |
| + its issuer (`Amazon RSA 2048 M0x`) is an intermediate distributed through |
| + the leaf's AIA extension, which Go's `x509.Verify` never fetches. Proven |
| + against a real captured PEM; every reference validator (aws-sdk-ruby, |
| + go.sns, AWS's own documented `openssl` procedure) decodes one block and |
| + builds no chain. |
| + |
| + So the security comes from: **https** to a **region-pinned |
| + `sns.<region>.amazonaws.com` host**, with **redirects refused**, and the |
| + certificate's subject asserted to be `sns.amazonaws.com` as defence in |
| + depth. The HTTPS client already validates AWS's TLS chain against system |
| + roots — that is the chain check, and it happens at the right layer. |
| + |
| +- **The host pin must not leave the second label free.** `sns.<anything>. |
| + amazonaws.com` accepts `sns.s3.amazonaws.com`, which is the published |
| + `sns-validator` bypass shape and a bucket that exists. Pin to the |
| + configured region exactly; fall back only to a region-SHAPED pattern. |
| - **`SubscribeURL` must be constrained to the same host shape.** It is |
| fetched after verification, so ordering is not the issue — an |