Sub-scope 06 (notification) platform/provider/* 8 implementations — manifest and probes
revision=a24ece9cf797f7ea647e33bf846b115208ed1ba5
generatedAt=2026-08-30T06:57:49+00:00

=== OWNED FILES (main) ===
c23f207dc664855fc5ae2b9f5c3b11f8295817ee provider/apns/ApnsFailureClassifier.java 67
b16d78e1e85e5975e0b90a3ec6de966f616fe0da provider/apns/ApnsNotificationProviderAdapter.java 100
3f3571796033547a94742482070d56e3bc7b1d07 provider/apns/ApnsProviderProperties.java 38
55dfbbfe1380f3d1b60820f4ef35bf5ef729c76d provider/apns/ApnsRequestMapper.java 122
1752774c9c3f3fa4a5e8d6f4e9b01642f5b0fb35 provider/fcm/FcmBatchCoordinator.java 98
2a8a4b1c89fd6429368221156dc538de827ec481 provider/fcm/FcmBatchResult.java 35
5c21bd712bb3019af9e99c35ab290ac6a85ff3ed provider/fcm/FcmContactPointUpdater.java 39
d6bf2e8b1077710b927507bcaab1a2fd462c47cc provider/fcm/FcmFailureClassifier.java 76
79c6cb0920dbb90d0a7cbd957f1c84bf1592ba96 provider/fcm/FcmGateway.java 12
638b06e7e57cc49276e0981e13b3f86ec8c84c1c provider/fcm/FcmMessageMapper.java 120
1d3722c15e282eaea2c82b4b10a4acd6fae479f4 provider/fcm/FcmNotificationProviderAdapter.java 73
a6bd37f13414859a42568b27c8c450fc4bba8d9f provider/fcm/FcmProviderProperties.java 35
aa8fe01c77f211aadd49e146230730ef086fb893 provider/fcm/FcmTargetMapper.java 18
878fbf7f6f74ef512e7a86a5e937e269cab2edec provider/fcm/FcmWireTarget.java 20
aa385f4db908f5050207d2a278287ef30597c03b provider/http/JdkNotificationHttpGateway.java 137
c905e0436393e1dffe4e7797d7c435e368162d7b provider/http/NotificationEndpoints.java 129
a0c1926513fc76eed5ac68b4358eceb3ada87ae6 provider/http/NotificationHttpGateway.java 20
1123dfbfb0333f69429a17cb1ea467be868c908b provider/http/NotificationHttpRequest.java 64
de43312c2f11ff3d87eeab48b321ffb476e4791c provider/http/NotificationHttpResponse.java 66
750d6511a5e35844de5bbe68f5e2b740f70e5b6d provider/http/NotificationHttpTransportException.java 35
91f5f16db31ebf5d749ead218a14d3e852e139d0 provider/ses/AwsSignatureV4Signer.java 147
68f80fa7cb8a90ecf3ce54d1b2cbd74d0b923515 provider/ses/SesCallbackAdapter.java 82
e0c7ac2c040f3242b7641a51ec669d782e503156 provider/ses/SesDeliveryProjector.java 18
eba30d54bc10f5f76df87e77cc3e3ff0a85693cf provider/ses/SesEventNormalizer.java 101
cfe49e507fc2011e20efe2d6ff1c417389388a68 provider/ses/SesFailureClassifier.java 51
0d252ca50b4212affc5bdbfbe27217eb0414c5ef provider/ses/SesNotificationProviderAdapter.java 169
05290f36def157f82daac120037f8cc9f6579f12 provider/ses/SesProviderProperties.java 35
26747b6b1635980ed2bbba39d54d32317a179802 provider/ses/SesRequestMapper.java 209
9a3d02df2f5435bbd0cf52ade1c7b0166bf456b2 provider/ses/SesSuppressionUpdater.java 42
7791eda1c549371e5923109a6f6b5977bf01adb2 provider/ses/SnsCertificateProvider.java 16
a1fa6e1013380103852b79a55fea7079ce8677b7 provider/ses/SnsSignatureVerifier.java 143
933ed55b68e1e012fff4af28c34fdb7f383d12ac provider/smtp/JavaMailSenderSmtpDispatch.java 54
d68bd1c088112a95ec40c6dcd15badc6dc30c6c6 provider/smtp/SmtpDispatch.java 21
919be2cf7179dbeb862b13b8b688d8d481f791cb provider/smtp/SmtpDispatchException.java 30
4aacc57a7a4d9fda14131de28fdb003488a79b43 provider/smtp/SmtpFailureClassifier.java 81
cebb5f96fe1a346086e2cd701bac2aba1de44c2c provider/smtp/SmtpMimeMessageFactory.java 147
9d057c521ed6efa4cc40d9a9f1c1be0dcffecb19 provider/smtp/SmtpNotificationProviderAdapter.java 114
5a499e7dd0f4188946ea45ace5455593f61d1e6f provider/smtp/SmtpProviderProperties.java 57
d56993d33aec93be577fcf09c9c3fea0e4679adc provider/twilio/TwilioCallbackAdapter.java 109
cb5edccce1517398ecd8be9a1dfef8a667ff5807 provider/twilio/TwilioDeliveryProjector.java 18
cb9314c3ca38072a32b91624d100738d2cf48336 provider/twilio/TwilioFailureClassifier.java 51
46c31c270f57db8f2a91b899cfb2d563b9a775f7 provider/twilio/TwilioProviderProperties.java 55
6fcdd5b22c1536b793a92610817e219a5db63024 provider/twilio/TwilioReconciliationCapability.java 143
70267d28d61c00bb16ea8d640ec2acd28ef43ead provider/twilio/TwilioRequestMapper.java 73
e6d44946e74bbff547c6dd8e4d1d5c8384d5fcbf provider/twilio/TwilioSignatureValidator.java 49
dca3e434739645b612d2ff9fd87e65006c8a464d provider/twilio/TwilioSmsProviderAdapter.java 142
4c816e7f8d32a60cca1f72688a8a7158a20d01b7 provider/twilio/TwilioStatusNormalizer.java 41
ec6b11f1b143a29e2a544261067dffa8593cbb7a provider/webhook/WebhookNotificationProviderAdapter.java 258
80e29c405e8242e59c8e342b66158997ccb8d436 provider/webhook/WebhookSignatureStrategy.java 40
614294a7f4665a3d5639ad2e477072acb100adce provider/webhook/WebhookSubscription.java 55
359456fcbc08a78ce217125c9414c3e27699d733 provider/webpush/EncryptedWebPushPayload.java 37
14777489fd42c465a984808155ddb6a4b4e33e8a provider/webpush/Rfc8291Aes128GcmEncryptor.java 193
87ef2155bd8d2f5ce0ec19459ce3b28cc03aeb06 provider/webpush/VapidAuthorizationProvider.java 17
7ba2272bc4042387573727b1aac7e1d3d06f249a provider/webpush/VapidJwtSigner.java 122
3bfc669416aad333240c7f472255753a2d33f332 provider/webpush/VapidKeyRegistry.java 88
eb580fee7e3a5f4ceb8093a3b520cb091962e2c5 provider/webpush/WebPushFailureClassifier.java 41
41675cc7338538698cfb5319bbcbb5285186249b provider/webpush/WebPushNotificationProviderAdapter.java 111
066b705c60bafb34295659ebf23478edc11c6691 provider/webpush/WebPushProviderProperties.java 37
36f00d5cfcb3170c9c053b4003d8411775af10cd provider/webpush/WebPushReceiptCapability.java 97
b05661b1f98edd359572d4644f28bd547b79d0c0 provider/webpush/WebPushRequestMapper.java 120
main_count=60
=== OWNED FILES (test) ===
54cc57c9156ab1bb313d580574e0377d1c497b53 provider/apns/ApnsNotificationProviderAdapterTest.java 149
0b08c72129086b25546dcc3f296ba9eecd191c95 provider/fcm/FcmBatchAdapterTest.java 189
fba408587f286b631cb724e29e94ca51b28bff55 provider/fcm/FcmExpiryTest.java 90
d706742163592e7eec9deef8a5f6c2ece2b39c5e provider/http/EndpointGuardCallSiteTest.java 126
8a3694043ac8697b08c5fc86c073f437108153f0 provider/http/EndpointRoutabilityTest.java 109
e9c6f694c2424aee85bcba1c0034469beae2c391 provider/http/HttpCommitmentClassificationTest.java 92
6b02612740f93b46df463b8374a4d76ff5ec76c7 provider/ses/SesNotificationProviderAdapterTest.java 352
5ba06823c66b2431c2eb4bd6566970d94ae8936a provider/ses/SnsCertificateUrlTest.java 108
4d8c7bceb898e10a2f0e8cdabbade6e054cf1768 provider/smtp/SmtpAttachmentBodyTest.java 121
9a3a66a384616ee19ba41fe91045250bd3e5a4ca provider/smtp/SmtpNotificationProviderAdapterTest.java 146
7bdf8defefeeb52e9383cd6117b73b01680c09a9 provider/twilio/TwilioCallbackAndProjectionTest.java 175
93e574125420c29fc0e6cb77c803def7b4ca5ce7 provider/twilio/TwilioCallbackContractTest.java 104
b1aaad2a50303188e267e854dc22da4a80acefdc provider/twilio/TwilioSmsProviderAdapterTest.java 122
58f5fbeafdbd9a5bdc8e3d1ec905b286d31006db provider/webhook/WebhookNotificationProviderAdapterTest.java 291
5531598c6fa52201944df7e69b4826bea6b8bd4e provider/webpush/WebPushCryptoTest.java 273
fae9093091aa7d29e0b1123e2a23f733381596bb provider/webpush/WebPushProviderAdapterTest.java 183
test_count=16

=== 8.1 SSRF 가드의 프로덕션 호출처 전수 ===
$ grep -rn 'requireExternallyRoutable' --include=*.java . | grep '/src/main/'
./adapter/outbound/notification/src/main/java/dev/caskeleton/adapter/outbound/notification/platform/provider/ses/SesProviderProperties.java:27:    NotificationEndpoints.requireExternallyRoutable(endpoint, "SES endpoint", true);
./adapter/outbound/notification/src/main/java/dev/caskeleton/adapter/outbound/notification/platform/provider/http/NotificationEndpoints.java:65:  public static URI requireExternallyRoutable(URI endpoint, String name, boolean allowLoopback) {
./adapter/outbound/notification/src/main/java/dev/caskeleton/adapter/outbound/notification/platform/provider/webhook/WebhookSubscription.java:29:    // requireExternallyRoutable, not requireSecureOrLoopback. The scheme check accepted any HTTPS
./adapter/outbound/notification/src/main/java/dev/caskeleton/adapter/outbound/notification/platform/provider/webhook/WebhookSubscription.java:49:    NotificationEndpoints.requireExternallyRoutable(target, "webhook target", trusted);
exit=0

$ grep -rn 'requireSecureOrLoopback\|isSecureOrLoopback' --include=*.java . | grep '/src/main/'
./application-core/src/main/java/dev/caskeleton/application/notification/platform/contact/WebPushSubscriptionValue.java:27:    if (!isSecureOrLoopback(endpoint)) {
./application-core/src/main/java/dev/caskeleton/application/notification/platform/contact/WebPushSubscriptionValue.java:50:  private static boolean isSecureOrLoopback(java.net.URI endpoint) {
./adapter/outbound/notification/src/main/java/dev/caskeleton/adapter/outbound/notification/platform/provider/http/NotificationEndpoints.java:24:  public static URI requireSecureOrLoopback(URI endpoint, String name) {
./adapter/outbound/notification/src/main/java/dev/caskeleton/adapter/outbound/notification/platform/provider/http/NotificationEndpoints.java:47:   * <p>{@link #requireSecureOrLoopback} checks the scheme and nothing else, so any HTTPS URL was
./adapter/outbound/notification/src/main/java/dev/caskeleton/adapter/outbound/notification/platform/provider/http/NotificationEndpoints.java:67:    requireSecureOrLoopback(endpoint, name);
./adapter/outbound/notification/src/main/java/dev/caskeleton/adapter/outbound/notification/platform/provider/webhook/WebhookSubscription.java:29:    // requireExternallyRoutable, not requireSecureOrLoopback. The scheme check accepted any HTTPS
exit=0

$ sed -n '20,30p;44,60p' application-core/src/main/java/dev/caskeleton/application/notification/platform/contact/WebPushSubscriptionValue.java
  private static final int AUTH_SECRET_LENGTH = 16;

  public WebPushSubscriptionValue {
    Objects.requireNonNull(endpoint, "endpoint");
    Objects.requireNonNull(p256dh, "p256dh");
    Objects.requireNonNull(authSecret, "authSecret");
    Objects.requireNonNull(vapidKeyId, "vapidKeyId");
    if (!isSecureOrLoopback(endpoint)) {
      throw new IllegalArgumentException("endpoint must be https outside the loopback interface");
    }
    if (p256dh.length != P256DH_LENGTH) {
   * TLS is required, except on the loopback interface.
   *
   * <p>The threat the rule addresses is interception on the path, and a loopback endpoint has no
   * path. Narrowing the exception to loopback keeps the guard meaningful for every real
   * subscription while letting the contract suite exercise a genuine socket.
   */
  private static boolean isSecureOrLoopback(java.net.URI endpoint) {
    String scheme = endpoint.getScheme() == null ? "" : endpoint.getScheme();
    if ("https".equalsIgnoreCase(scheme)) {
      return true;
    }
    String host =
        endpoint.getHost() == null ? "" : endpoint.getHost().toLowerCase(java.util.Locale.ROOT);
    return "http".equalsIgnoreCase(scheme)
        && ("127.0.0.1".equals(host) || "::1".equals(host) || "localhost".equals(host));
  }

exit=0

$ grep -n 'Web Push endpoints' adapter/outbound/notification/src/main/java/dev/caskeleton/adapter/outbound/notification/platform/provider/http/NotificationEndpoints.java
49:   * 1918 address. Web Push endpoints and webhook targets are supplied by clients, which makes this
exit=0

$ grep -n 'WebPush\|webpush' adapter/outbound/notification/src/test/java/dev/caskeleton/adapter/outbound/notification/platform/provider/http/EndpointGuardCallSiteTest.java | wc -l
0
exit=0

=== 8.2 응답 본문 상한: 선언과 구현 ===
$ sed -n '108,128p' adapter/outbound/notification/src/main/java/dev/caskeleton/adapter/outbound/notification/platform/provider/http/JdkNotificationHttpGateway.java
  /**
   * The largest provider response body this gateway retains.
   *
   * <p>64 KiB: far more than any provider's acknowledgement or error document, and small enough
   * that a hostile or broken endpoint cannot make it interesting.
   */
  public static final int MAX_RESPONSE_BYTES = 65_536;

  /**
   * A body handler that stops reading at the cap.
   *
   * <p>Truncating rather than failing: the status code is the part that decides the outcome, and a
   * provider that accepted the message and then wrote a large body should not turn into an
   * ambiguous submission.
   */
  private static HttpResponse.BodyHandler<byte[]> boundedBody(int maxBytes) {
    return responseInfo ->
        HttpResponse.BodySubscribers.mapping(
            HttpResponse.BodySubscribers.ofByteArray(),
            body -> body.length <= maxBytes ? body : java.util.Arrays.copyOf(body, maxBytes));
  }
exit=0

=== 8.3 비밀 자료가 String으로 승격되는 지점 ===
$ grep -rn 'new String(secret\|new String(.*[Kk]ey' --include=*.java adapter/outbound/notification/src/main
adapter/outbound/notification/src/main/java/dev/caskeleton/adapter/outbound/notification/platform/security/AesGcmNotificationPayloadProtection.java:130:    String keyId = new String(envelope, 2, keyIdLength, StandardCharsets.UTF_8);
adapter/outbound/notification/src/main/java/dev/caskeleton/adapter/outbound/notification/platform/provider/ses/AwsSignatureV4Signer.java:118:        ("AWS4" + new String(secretAccessKey, StandardCharsets.UTF_8))
exit=0

=== 8.4 SNS SignatureVersion ===
$ grep -rn 'SignatureVersion\|SHA1withRSA\|SHA256withRSA' --include=*.java adapter/outbound/notification/src/main
adapter/outbound/notification/src/main/java/dev/caskeleton/adapter/outbound/notification/platform/provider/ses/SnsSignatureVerifier.java:49:    String version = envelope.get("SignatureVersion");
adapter/outbound/notification/src/main/java/dev/caskeleton/adapter/outbound/notification/platform/provider/ses/SnsSignatureVerifier.java:66:          Signature.getInstance("2".equals(version) ? "SHA256withRSA" : "SHA1withRSA");
exit=0

