Subscription-access tracking + sharing detection (store-side)#18
Open
Ayymoss wants to merge 2 commits into
Open
Subscription-access tracking + sharing detection (store-side)#18Ayymoss wants to merge 2 commits into
Ayymoss wants to merge 2 commits into
Conversation
Server-side hardening for paid plugins that are streamed to clients (no DLL transfer), so a dumped/redistributed copy is harder to extract and useless without an active subscription. Slots into the existing ecommerce app (Firebase + ChargeBee) with no new infrastructure; all new behaviour is opt-in via env vars and a no-op until those are set. Entitlement gate (GET /plugin/entitlement): - Issues a short-lived ECDSA P-256/SHA-256 signed token bound to instance_id, only for plugins the email is actively subscribed to (else 403). The plugin verifies the signature with an embedded public key and fails closed when it enforces. - ECDSA chosen so both `cryptography` and the .NET BCL verify natively (no extra dep); signatures are DER (RFC 3279). The exact signed string is returned verbatim so the client never re-serializes (no cross-language canonicalization risk). - signing_helper.py loads the private key from ECOMMERCE_SIGNING_KEY (PKCS#8 PEM) with a clearly-marked DEV fallback; generate_signing_keypair.py mints a prod keypair. Sharing detection (access logging on /plugin_subscriptions + admin GET /plugin/sharing_report): - Records which instance_id fetches content per subscription, reusing the existing Firebase provider. Emails and IPs are stored only as keyed HMACs (ECOMMERCE_TRACKING_SECRET); raw values are never persisted. - Report is admin-only (X-Admin-Key == ECOMMERCE_ADMIN_KEY) and returns aggregates keyed by HMAC'd email, never raw emails. Flags concurrently-active instances rather than lifetime-distinct (instance_id regenerates on reinstall/ephemeral Docker). See src/ecommerce/PLUGIN_HARDENING.md for env vars, key generation, and rollout.
Per discussion, the client-side entitlement gate is trivially bypassable (single boolean, patchable in seconds) and adds little over the existing delivery gate, so it was dropped. This keeps only the part with standalone value: visibility into how subscriptions are used. - Remove the entitlement endpoint + ECDSA signing (signing_helper, key generator, plugin_entitlement resource, get_entitled_plugin_ids). - Access tracking now also records the plugin ids served per fetch (derived from the content_url basename, no extra ChargeBee call), so the report shows what each account/instance uses. - Sharing report now returns two views: by_email (one subscription on multiple concurrently-active instances) and by_instance (one instance_id under multiple subscription emails). Emails/IPs remain HMAC'd; report stays admin-only. Docs updated (PLUGIN_HARDENING.md). Still a no-op until ECOMMERCE_TRACKING_SECRET set.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What this adds
Visibility into how plugin subscriptions are used, so RaidMax can spot people reusing one subscription across multiple installs (paying once, running many) and see which plugins are in play. Lives entirely in the existing
ecommerceapp, reuses Firebase + ChargeBee — no new infrastructure or dependency — and is a no-op untilECOMMERCE_TRACKING_SECRETis set, so merging/deploying changes nothing on its own.Access tracking
get_subscribed_content()now records, per content fetch, whichinstance_idpulled which plugins for a subscription (plugin ids derived from thecontent_urlbasename — no extra ChargeBee call). Stored in Firebase undersubscription_access/{email_hash}/{instance_id}.Admin report —
GET /plugin/sharing_reportTwo views:
>=N concurrently active instances (one paid email, many installs), with the plugins in use.instance_idpulling content under more than one subscription email.Active-concurrent rather than lifetime-distinct, because
instance_idregenerates on reinstall / wiped config / ephemeral Docker (lifetime counts would false-flag honest users).Privacy
ECOMMERCE_TRACKING_SECRET) — raw values never persisted.X-Admin-Key==ECOMMERCE_ADMIN_KEY); the ecommerce app is internet-facing, so it returns aggregates keyed by hashed email, never raw emails.Actions needed, and why
ECOMMERCE_TRACKING_SECRET(any high-entropy string). Why: tracking is disabled until set; it's the HMAC key so the datastore never holds raw emails/IPs and a leak can't be correlated to customers.ECOMMERCE_ADMIN_KEY(any high-entropy string). Why: the report endpoint is internet-facing and returns401unlessX-Admin-Keymatches it.That's it — no keys to mint, no client changes, no infra changes. Details in
src/ecommerce/PLUGIN_HARDENING.md.Infrastructure impact
None. Same Flask app, same Firebase (
IA_DATA_*), same ChargeBee. Deploying without the env vars set leaves existing behaviour byte-for-byte unchanged.