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: *

{@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. * *

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. * *

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. * *

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 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