Skip to content

Restrict Web Push endpoints to supported push services - #751

Merged
thomasluizon merged 1 commit into
mainfrom
thomasluizon/ticket-1503-push-endpoint-policy
Oct 18, 2026
Merged

thomasluizon merged 1 commit into
mainfrom
thomasluizon/ticket-1503-push-endpoint-policy

Conversation

@thomasluizon

Copy link
Copy Markdown
Owner

Closes thomasluizon/orbit-tickets#1503.

This replaces #748 as one commit on the current main. That pull request carried Microsoft's public sample WNS channel URI as a test fixture, which GitGuardian flags as a high-entropy secret in every commit it scans; the fixtures now use https://cloud.notify.windows.com/?token=test-channel, and the code is unchanged. The same three test groups pass (Domain 39, Application 26, Infrastructure 45).

Web Push subscriptions now accept only supported push service hosts. The same policy rejects unsupported registrations and skips unsupported rows already stored in the database. The delivery HTTP client disables redirects.

Approach

  • Orbit.Domain/Common/WebPushEndpointPolicy.cs owns the HTTPS provider boundary; PushSubscription.Create enforces it for Web Push entities.
  • SubscribePushCommand applies the policy before looking up or claiming an existing endpoint. Its validator uses the existing validation pipeline and PUSH_ENDPOINT_INVALID error.
  • PushNotificationService checks the stored endpoint immediately before preparing delivery. Unsupported rows remain stored and do not count as delivered; other valid subscriptions still receive their push.
  • ServiceCollectionExtensions.Infrastructure.cs configures the typed push client with AllowAutoRedirect = false.
  • Domain, validator, handler, delivery and registration tests cover supported providers, misleading hostnames, legacy rows, redirects and native FCM tokens. Existing subscription fixtures use an allowed authority so their original account ownership and eviction coverage remains active.

Provider and external interface evidence

The provider hosts were checked against these sources before implementing the policy:

These are documentation/example placeholders, not live subscription credentials. Each provider fixture passes entity creation, command validation and encrypted delivery through the real Web Push library with an HTTP test handler.

Installed Lib.Net.Http.WebPush 3.3.1 identifies commit 5f601ff75f9de62ab2fa83206ff0ac9b9ab10d4c in its NuGet manifest. Its PushServiceClient source confirms that it retains the supplied HTTP client, calls its SendAsync, and treats redirect responses as failures. No new provider response fields are read.

Installed .NET HTTP package source at commit 4099f83977f4d29604a9aeb971fb0a7bc1d5f699 confirms the typed client name and AllowAutoRedirect forwarding. The policy tests exercise real System.Uri parsing and its Scheme, Port, UserInfo, Fragment and Host properties through the production policy, rather than mocking URI parsing. The registration test resolves the real handler chain from the configured service collection and verifies the redirect setting.

Assumptions

  • Put the reusable policy in Domain.Common rather than Application so the entity guard and delivery service share one implementation without reversing dependencies.
  • Use the documented exact Google and Mozilla hosts and the documented Apple and WNS subdomain boundaries rather than trusting broader provider parent domains.
  • Require HTTPS on port 443 without user information or fragments rather than accepting alternate ports or credential-bearing authorities; subscription paths and query strings stay opaque.
  • Make the existing push registration method internal for a focused composition test rather than initializing unrelated external integrations.

Test evidence

Before changing implementation:

  • dotnet test tests/Orbit.Application.Tests --filter FullyQualifiedName~SubscribePushCommandValidatorTests --verbosity quiet: exit 0, 19 tests passed unchanged with the defect present. The substring filter also includes unsubscribe validator tests.
  • Added Validate_UnsupportedHttpsEndpoint_HasError, then ran dotnet test tests/Orbit.Application.Tests --filter FullyQualifiedName~SubscribePushCommandValidatorTests.Validate_UnsupportedHttpsEndpoint_HasError --verbosity quiet: exit 1, one failure because no validation error existed for Endpoint. A --no-build --logger 'console;verbosity=normal' rerun confirmed that exact assertion failure.
  • dotnet test tests/Orbit.Infrastructure.Tests --filter FullyQualifiedName~PushNotificationServiceTests --verbosity quiet: exit 0, all 29 existing delivery tests passed unchanged with the defect present.
  • Added SendToUserAsync_UnsupportedStoredEndpoint_SkipsDeliveryAndKeepsSubscription, then ran dotnet test tests/Orbit.Infrastructure.Tests --filter FullyQualifiedName~PushNotificationServiceTests.SendToUserAsync_UnsupportedStoredEndpoint --logger 'console;verbosity=normal': exit 1, expected delivered false but found true. This seeds and reloads a legacy row and drives TrySendToUserAsync through the real persistence and encryption path. The case was then expanded to a theory.

After the fix:

  • dotnet build Orbit.slnx --verbosity quiet: exit 0, zero errors. OpenAPI generation produced no contract diff.

  • dotnet test tests/Orbit.Domain.Tests --no-build --filter 'FullyQualifiedName~PushSubscriptionTests|FullyQualifiedName~WebPushEndpointPolicyTests' --verbosity quiet: exit 0, 56 passed.

  • dotnet test tests/Orbit.Application.Tests --no-build --filter 'FullyQualifiedName~SubscribePushCommandValidatorTests|FullyQualifiedName~SubscribePushCommandHandlerTests|FullyQualifiedName~UnsubscribePushCommandHandlerTests|FullyQualifiedName~GetPushSubscriptionsQueryHandlerTests' --verbosity quiet: exit 0, 56 passed, including the validator regression.

  • dotnet test tests/Orbit.Infrastructure.Tests --no-build --filter 'FullyQualifiedName~PushNotificationServiceTests|FullyQualifiedName~PushHttpClientConfigurationTests|FullyQualifiedName~PushSubscriptionAccountSwitchTests' --verbosity quiet: exit 0, 57 passed, including all six legacy endpoint cases and the registered redirect setting.

  • Added the sourced Edge browser fixture, rebuilt with dotnet build Orbit.slnx --verbosity quiet (exit 0, zero errors), and reran dotnet test tests/Orbit.Domain.Tests --no-build --filter FullyQualifiedName~SupportedEndpoint_PolicyAndEntityAccept --verbosity quiet (8 passed), dotnet test tests/Orbit.Application.Tests --no-build --filter FullyQualifiedName~Validate_DocumentedProviderEndpoint_NoErrors --verbosity quiet (5 passed), and dotnet test tests/Orbit.Infrastructure.Tests --no-build --filter FullyQualifiedName~SendToUserAsync_WebPushDelivered_KeepsSubscription --verbosity quiet (5 passed). Each command exited 0.

  • The first full Application run passed 5,494 tests and failed one existing HeldWriteRevisionTests.ValidEdit_ExecutesStoredRevisedValue subscribe case whose valid endpoint used example.com. Updated only that subscribe fixture to an allowed FCM authority, preserving its assertions; the invalid-revision fixture also starts from an allowed subscription.

  • dotnet test tests/Orbit.Application.Tests --filter FullyQualifiedName~HeldWriteRevisionTests --verbosity quiet: exit 0, all 144 revision tests passed after the fixture adjustment.

  • The first full Infrastructure run passed 3,372 tests and failed two account-reset tests while creating subscriptions at the synthetic https://push/ host. A focused unchanged-test run confirmed both failures came from the new entity guard rejecting those fixture endpoints. Updated only the three Web Push fixture URLs to an allowed FCM authority. dotnet test tests/Orbit.Infrastructure.Tests --filter FullyQualifiedName~AccountResetRepositoryTests --verbosity quiet then exited 0 with all 5 account-reset tests passing; deletion assertions are unchanged.

Full test projects, each judged by exit code:

Command Exit Passed Failed Skipped
dotnet test tests/Orbit.Domain.Tests --verbosity quiet 0 710 0 0
dotnet test tests/Orbit.Application.Tests --verbosity quiet 0 5495 0 0
dotnet test tests/Orbit.Infrastructure.Tests --verbosity quiet 0 3374 0 0
dotnet test tests/Orbit.Analyzers.Tests --verbosity quiet 0 32 0 0

All 9,611 tests passed. Commit hooks passed formatting, dash, timeless, root allow list and suppression checks. A scan found no added bare comments.

No browser session or live device delivery was exercised; verification uses sourced endpoint fixtures and unit tests as required by the work order.

🤖 Generated with Claude Code

Web Push subscriptions accept only HTTPS endpoints on the FCM, Mozilla, Apple and WNS push hosts, and the push HTTP client follows no redirects. Replaces orbit-api#748 as one commit so no commit carries the high-entropy sample fixture.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ No new issues found.

Reviewed changes across registration, domain validation, persisted-subscription delivery, HTTP configuration, and the web/Android consumers.

  • Provider policy: A shared domain policy restricts Web Push to supported HTTPS authorities on port 443, rejecting misleading hostnames, user information, and fragments while preserving opaque paths and queries.
  • Registration guards: The validator, command handler, and entity factory enforce the policy without changing native FCM token handling or subscription ownership rules.
  • Delivery boundary: Unsupported persisted endpoints are skipped without being deleted or counted as delivered; the production HTTP client disables redirects.
  • Regression coverage: Provider fixtures, legacy rows, redirects, account switching, eviction, unsubscribe, account reset, and held-write behavior are covered without changing the consumer payload contract.
  • Verification: The solution build passed with zero errors, and all 321 focused tests passed. Live provider/device delivery was not exercised.

Pullfrog  | View workflow run | Using gpt-6.1-sol | 𝕏

@sonarqubecloud

Copy link
Copy Markdown

@thomasluizon
thomasluizon merged commit d9f847a into main Oct 18, 2026
25 checks passed
@thomasluizon
thomasluizon deleted the thomasluizon/ticket-1503-push-endpoint-policy branch October 18, 2026 11:09
thomasluizon added a commit that referenced this pull request Oct 18, 2026
* fix: pin analyzer test Pkcs dependency to patched version (#728)

(cherry picked from commit 61b0090)

* fix: shard pull request domain mutation testing (#729)

(cherry picked from commit bcfd81e)

* Promote the API redesign with production compatibility checks (#723)

* Bring the API redesign tree onto main for #1413

* Preserve calendar connection detection for shipped clients

* Prove API and migrated schema compatibility with shipped clients

* Trim whitespace in the archived main schema proof

* Require the production health response to report the released commit

* Bind initial approval previews to stored pending operations

* fix: return the bulk skip refresh outcome and split the undo skip query

SonarCloud failed the quality gate on two reliability findings in code this pull request brings to main:
the bulk skip conflict check read a flag only a lambda wrote (S2583), and the undo skip check loaded
Tags and Goals in one query (S8733). The transaction now returns whether every habit refreshed, and the
undo skip check splits its query. A new case covers the concurrent change conflict; it failed with the
refresh outcome forced to true and passes with the fix.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit 5526c9b)

* Add per-account feature flag targeting and admin controls (#724)

* feat: resolve feature flags per account

* feat: administer account feature flags through MCP and REST

* test: exclude feature flag targets from rate limit fixture

* fix: reconcile account flag tests and migration metadata with redesign

(cherry picked from commit 1b886bd)

* Add header API versioning for v1 and v2 (#733)

* Add header API versioning with v1 compatibility

* Verify versioned OpenAPI and browser header compatibility

* Preserve skip undo path contract with optional version header

(cherry picked from commit ac8b34a)

* fix: expose the config response in OpenAPI (#734)

(cherry picked from commit a9d8dd5)

* Route production redesign accounts through Cloudflare (#735)

* Add cookie-based routing for the production redesign web service

* Document web routing rollout, permissions, and rollback checks

* Fix web routing coverage and disable infra install scripts

* chore: regenerate the v2 OpenAPI document on main

orbit-api#733 added openapi_v2.json and orbit-api#734 typed the /api/config
response in parallel, so main carries a v2 document without the typed config
schema and the Build check's stale-document step fails on every merge with
main. dotnet build src/Orbit.Api regenerates it; openapi.json is unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit 9c64770)

* Fix habit edit gating, end dates, and reminder updates (#736)

* fix: validate habit updates with the resulting time range

* fix: preserve end dates when creating root habits

* fix: gate slip alert activation and preserve unchanged reminders

* fix: compare effective reminder rules across stored formats

(cherry picked from commit 7472203)

* Reduce mutation and guard CI jobs (#737)

* Select mutation shards from changed projects

* Consolidate guards and filter artifact generation

* Skip project documentation in mutation selection

* Validate mutation base refs and restrict Git PATH

* Read mutation shard changes from standard input

(cherry picked from commit b802f9e)

* test: strengthen habit and goal ownership coverage (#739)

(cherry picked from commit 0e32628)

* test: verify checkout session user metadata (#740)

(cherry picked from commit da2f53a)

* fix: protect OAuth code delivery with Turnstile (#741)

(cherry picked from commit 1149606)

* fix: enforce chat interaction scope before loading context (#743)

(cherry picked from commit 98faf4e)

* fix: erase accountability pairs during account deletion and reset (#745)

* fix: erase accountability pairs during account deletion and reset

* test: preserve migration compatibility checks after accountability cleanup

(cherry picked from commit a9e5fb9)

* Fix goal sync dates and deadline reminder counts (#747)

* fix: compare streak sync dates in the user timezone

* fix: show actual days remaining in goal deadline reminders

* test: configure timezone for streak scheduler fixtures

(cherry picked from commit 0e63842)

* Manage staging landing routing in Terraform (#742)

(cherry picked from commit abdd738)

* Restrict Web Push endpoints to supported push services (#1503) (#751)

Web Push subscriptions accept only HTTPS endpoints on the FCM, Mozilla, Apple and WNS push hosts, and the push HTTP client follows no redirects. Replaces orbit-api#748 as one commit so no commit carries the high-entropy sample fixture.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit d9f847a)

* Erase account uploads before permanent deletion (#1507) (#750)

* fix: erase uploaded objects before completing account deletion

* fix: refuse upload reads after permanent account deletion

* fix: grant uploads bucket deletion permissions and verify provider wiring

* test: align storage startup checks with erasure requirements

* fix: retire deleted Supabase upload storage

* test: configure upload erasure providers in deletion fixtures

Share the existing object-store fake across account deletion and scheduler fixtures after merging main. Preserve accountability assertions and production provider requirements for thomasluizon/orbit-tickets#1507.

(cherry picked from commit fa33a94)

* fix: limit anonymous auth and waitlist by email and source IP (ORB-108) (#749)

* fix: enforce source IP ceilings alongside anonymous email limits

* test: cover both rate limit dimensions before bot verification

* fix: isolate BFF email rate limits by verified client IP

* fix: generate and persist BFF forwarding secrets

(cherry picked from commit de0f42f)

* Limit access tokens to fifteen minutes (#738)

* fix(auth): limit access tokens to fifteen minutes

* fix(auth): align managed JWT lifetime and prove logout rejection

(cherry picked from commit ade7016)

* fix(auth): equalize account provisioning work (ORB-96) (#744)

* test(auth): expose registration dependent sign-in work

* fix(auth): perform uniform registration work before sign-in responses

* test(auth): cover expired codes and tracked upsert conflicts

* fix(auth): isolate signup retries and restore provisioning guards

* fix(auth): recover signup work after dispatch failures

* fix(auth): roll back failed signup referrals before retry

(cherry picked from commit 1bd0d26)

* fix: expose redesign controllers in both API versions

Apply the carried version declarations to the Notion and proactive controllers and regenerate the v2 contract. Refs thomasluizon/orbit-tickets#746.

* fix: honor account flags in proactive engine

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant