pgstencil ships pgstencil, @pgstencil/auth and @pgstencil/stripe as packed tarballs (PACKAGES.md). This file states what the Better Auth integration in @pgstencil/auth, the Billing class in @pgstencil/stripe, and pgstencil itself guarantee to a consuming application, as conditions an auditor can check at one commit; each section ends with the tests that pin them. Bare names are under packages/auth/src/, packages/auth/better-auth-migrations/, packages/stripe/migrations/ or tests/; in the billing sections a bare method or constant is a member of Billing, or an export, in packages/stripe/src/index.ts.
The application owns everything outside the packages: TLS and its origin gate, the CSP of its own pages, secret and credential storage, database provisioning, provider registration, and — only when it opts in with ipAddressHeaders — a proxy that overwrites those headers on every request. For billing it also owns: authenticating the owner before every Billing call that takes an owner ID, and never taking that ID, a customer, Price, quantity or URL from the browser; CSRF and exact-origin checks on its billing routes; handing each webhook's raw body and Stripe-Signature header to webhook or verifyWebhook unaltered, under a body limit, and answering 2xx only after webhook resolves; storing the Stripe secret key and the webhook signing secret; and the Stripe account itself — its Prices, a customer portal that offers no Price outside prices, no quantity change and no second subscription, and a webhook endpoint created at STRIPE_API_VERSION. The example in examples/better-auth/ shows one way to do this, exercised by integration/billing-http.test.ts.
- FAIL IF an HTTPS deployment's auth cookies lack
__Host-,Secure,HttpOnly,Path=/orSameSite=Lax, or carryDomain; inspectbetter-auth.tsandbetter-auth-security.ts. - FAIL IF a session token, a provider access/refresh/ID token or the internal
singleSession/emailAuthenticatedfields reach browser JSON, a stored sessiontokenauthenticates without the server's cookie signature, or a bearer or API-key plugin is enabled; inspectpublicAuthResponseandplugins. - FAIL IF sessions outlive 24 hours, refresh on use, come from a cookie cache instead of the database, or session creation stops being serialized per user; inspect
sessioninbetter-auth.tsand002_security_policy.sql. - FAIL IF
sessionPolicy: 'single'leaves another device signed in,'multiple'(the default) revokes one, or sign-out revokes another session; inspectdatabaseHooks.session. - FAIL IF the readable
rememberLoginMethodcookie grants authority, records a failed attempt or an explicit link, or is written to the database; inspectlastLoginMethodinbetter-auth.ts. - FAIL IF an application hook receives anything but the login method, runs before its write commits, fires for an explicit link or a failed attempt, or can fail or delay the auth response by throwing, rejecting or never settling; inspect
runHook,loginMethodanddatabaseHooksinbetter-auth.ts, andhooksinbetter-auth-workers.ts.
Pinned by integration/better-auth.test.ts: email policy: secret-keyed codes, cross-browser redemption, concurrent single use and no token exposure, Better Auth time: 23 hours, expiration boundary, isolated async contexts and unchanged host clock, session policy: single, session policy: multiple; integration/better-auth-oauth.test.ts: Last login method remembers only successful sign-ins and survives logout, Auth hooks see each sign-up and login after it commits, and never a link or a failure, A hook that throws, rejects or never settles leaves sign-in working; unit/better-auth-workers.test.ts: a Workers hook factory that throws leaves auth requests working.
- FAIL IF a state-changing POST is accepted without an exact
Originmatch, a signature-verified CSRF cookie, a matchingX-CSRF-Tokenand anapplication/jsonbody; inspectprotectAuthinbetter-auth-security.ts. - FAIL IF an upstream Better Auth route outside the read/write allowlist answers with anything but 404, or a provider callback accepts a method other than GET and Apple's
form_postrelay; inspectprotectAuth. - FAIL IF a response, including a Workers setup or teardown failure, leaves without
Cache-Control: no-store,Referrer-Policy: no-referrer,X-Content-Type-Options: nosniff,X-Frame-Options: DENYand a CSP denying framing, inline script and third-party sources; inspectauthSecurityHeaders,protectAuthandcreateBetterAuthWorker. - FAIL IF the app accepts a non-canonical origin, a secret under 32 characters, or a success/error path off the application origin; inspect
authOptionsinbetter-auth.ts.
Pinned by integration/better-auth.test.ts: auth surface: explicit CSRF, exact origin, security headers and disabled unused endpoints, auth surface: an allowlisted write with a non-JSON body answers 415, auth options reject a non-canonical origin, a short secret and off-origin redirect paths, Better Auth email rejects expired codes and cross-origin sign-in; integration/better-auth-oauth.test.ts: Better Auth OAuth: Apple form_post relay, wrong browser, mismatched provider and expired state, Better Auth OAuth: callbacks refuse every method but GET and Apple's form-encoded POST; integration/better-auth-workers.test.ts: Workers auth setup failures return a safe 503 with security headers.
- FAIL IF a stored email code is recoverable without the application secret, is not separated by purpose from other keyed values, lives longer than ten minutes, survives more than three failed guesses, or is redeemed twice; inspect
emailOTPinbetter-auth.tsandkeyedinbetter-auth-security.ts. - FAIL IF a stored rate-limit key — Better Auth's
rateLimitrows or pgstencil'spgstencil_auth_limits— contains a raw email address or IP instead of a secret-keyed HMAC, or a changing client IP resets an address's one-minute resend cooldown, five sends or fifteen verification attempts per fifteen minutes; inspectrateLimitStorageandconsumeinbetter-auth-security.ts,rateLimitinbetter-auth.tsand005_hashed_rate_limit_keys.sql. - FAIL IF Better Auth's per-IP limiter or pgstencil's per-IP email budget counts any address but one trusted client IP — on Node the socket address
@hono/node-serverpasses asenv.incoming, or a forwarded header only when the application names it inipAddressHeaders— or either limiter keys an IPv6 address on anything finer than its /64, or a client-suppliedx-pgstencil-client-ipreaches either limiter; inspectprotectAuthandipBucketinbetter-auth-security.tsandadvanced.ipAddressinbetter-auth.ts. - FAIL IF the Workers adapter counts a client-IP header other than
cf-connecting-ip, or an address in the reservedidentity.pgstencil.invalidnamespace reaches an email route or the email sender; inspectbetter-auth-workers.tsandisIdentityEmail.
Pinned by integration/better-auth.test.ts: email policy: secret-keyed codes, cross-browser redemption, concurrent single use and no token exposure, email policy: distributed IPs cannot bypass cooldown, send quota or attempt budget, email policy: fifteen verification attempts per fifteen minutes stop even the right code from any IP, IP rate limits: stored limiter keys never contain a raw client IP, IP rate limits: the default Node path counts the socket, so rotating x-pgstencil-client-ip cannot escape, IP rate limits: concurrent requests from one address are counted atomically, IP rate limits: two IPv6 addresses in one /64 share one send:ip budget, IP rate limits: naming a victim's IP in x-pgstencil-client-ip spends only the caller's budget, IP rate limits: an explicit ipAddressHeaders opt-in counts the configured header; unit/ip-bucket.test.ts: ipBucket groups addresses by /64 as Better Auth's own normalizeIP does; integration/better-auth-workers.test.ts: Better Auth in workerd: deterministic replay, separate clocks, shared database rate limits; integration/better-auth-oauth.test.ts: Optional email: <provider> signs in by stable identity without a mailbox.
- FAIL IF a callback is processed without a signed browser-bound state cookie, an unexpired state row for the provider it names, and an atomic single-use claim taken in Postgres before the code exchange; inspect
oauthRequestinbetter-auth-oauth.tsand003_oauth_claims.sql. - FAIL IF a caller can choose the callback destination, the scopes or any OAuth parameter beyond the provider name, or can sign in by presenting a provider ID or access token directly; inspect
oauthRequestandprotectAuth. - FAIL IF a Google, Apple or Microsoft ID token is accepted without signature, issuer, audience, expiry and nonce verification or under an algorithm other than RS256, a Microsoft identity is keyed on anything but a verified tenant and object ID, or GitHub signs in without a verified primary email; inspect
verifiedOidc,socialProvidersandproviderSubjectinbetter-auth-email.ts. - FAIL IF a provider access, refresh or ID token survives identity verification in the database or a response; inspect
databaseHooks.account. - FAIL IF a provider's error description, or any provider error code outside the
errorCodesallowlist inpackages/pgstencil/src/diagnostics.ts, reaches a redirect URL, a page or a diagnostic record, or an allowlisted code reaches a redirect URL or a page; a diagnostic record may carry an allowlisted code aserrorCodeand Microsoft's bounded numericerror_codes[0]asproviderCode, nothing else from the provider's error; inspect the callback redirect handling inoauthRequestanddiagnosticError.
Pinned by integration/better-auth-oauth.test.ts: Better Auth OAuth: <provider> login, safe token storage and callback replay, Better Auth OAuth: <provider> rejects invalid signed claims, Better Auth OAuth: <provider> rejects ID tokens whose header names an algorithm other than RS256, Better Auth OAuth: concurrent callbacks exchange once and errors contain no provider details, Better Auth OAuth: caller cannot override callback origin or use direct provider tokens, Microsoft tenant identity and unverified email cannot capture another account; integration/better-auth-workers.test.ts: Better Auth OAuth in workerd: <provider>; unit/diagnostics.test.ts: diagnostics allowlist drops secrets even in unexpected fields and malformed values, provider error extraction keeps numeric codes and tolerates hostile getters.
- FAIL IF
accountLinkingdefaults to anything but'explicit', matching email addresses merge two identities under that default, or an explicit link starts without a session younger than ten minutes and a callback still carrying that same session; inspectaccount.accountLinkinginbetter-auth.tsandoauthRequest. - FAIL IF one provider identity ends up owned by two users, or an unverified or missing provider email establishes a match; inspect
validateUserInfoinbetter-auth.tsandaccount_provider_identityin002_security_policy.sql. - FAIL IF
accountLinking: 'same-email'joins a non-authoritative provider email to an account without a live session for that same address that an email code created less than ten minutes earlier; inspectvalidateUserInfoand004_email_session_proof.sql. - FAIL IF an
allowMissingEmailprovider-only account publishes its reserved address as an email, or a returning provider subject moves onto another account because the provider later supplied that account's address; inspectsocialProvidersandidentityEmail.
Pinned by integration/better-auth-oauth.test.ts: Better Auth OAuth: email collision requires explicit linking; linking binds to live session, Better Auth OAuth: missing/unverified email is rejected and provider identities cannot be stolen by linking, Same-email linking: <provider> requires a fresh email session for non-authoritative email, Same-email linking: wrong-email, OAuth-only, expired and revoked sessions cannot supply mailbox proof, Optional email: <provider> signs in by stable identity without a mailbox.
- FAIL IF a diagnostic record carries anything outside the enumerated events, categories (error codes only from the
errorCodesallowlist), bounded numbers, booleans and validated correlation identifiers — never an email, sign-in code, token, cookie, header, path, query string, SQL statement, error message, provider error description or stack; inspectdiagnosticanddiagnosticErrorinpackages/pgstencil/src/diagnostics.ts. - FAIL IF a failing or hostile sink changes a response, leaks exception text, or lets one request's context reach another's record; inspect
diagnosticandobserveRequest. - FAIL IF a caller-supplied request ID is trusted, or an expected 4xx auth rejection is recorded as a server failure; inspect
observeRequestandonAPIErrorinbetter-auth.ts.
Pinned by unit/diagnostics.test.ts: diagnostics allowlist drops secrets even in unexpected fields and malformed values, concurrent request logs preserve their own context and do not log arbitrary routes, a broken sink never changes successful responses or reveals thrown exception text, provider error extraction keeps numeric codes and tolerates hostile getters; integration/better-auth-oauth.test.ts: auth diagnostics identify Microsoft token failures without recording credentials or identity.
- FAIL IF a normal build injects
@pgstencil/auth/better-auth-testing, importing that module changes a process global, or a deployed Worker answers a test clock route. The module is a published export thatpackages:verifyimports; it is inert unless esbuild'sinjectnames it, which only test bundles do. Inspectbetter-auth-testing.ts,examples/better-auth/src/worker.tsbesidesupport/better-auth-worker.ts, and each bundle'sinject. - FAIL IF Better Auth introspects or migrates the schema during a request, or the committed migrations stop matching Better Auth's generated plan; inspect
advanced.databaseinbetter-auth.tsandschemaChangesinexamples/better-auth/src/schema.ts.
Pinned by integration/better-auth-workers.test.ts: normal Workers build uses real time and randomness and contains no test clock controls; integration/better-auth.test.ts: Better Auth email: repeatable cookies, database and email snapshots across parallel apps, Better Auth time: 23 hours, expiration boundary, isolated async contexts and unchanged host clock.
- FAIL IF
verifyWebhookparses the body beforeconstructEventAsyncchecks its signature againstwebhookSecret, passes a tolerance other than 300 seconds or a clock other than the injectedTime, or returns an event whoselivemodediffers fromlive, whoseapi_versionis notSTRIPE_API_VERSION, or that carries a Connectaccount; or a refusal is anything but aBillingErrorwith status 400, raised beforewebhooktouches the ledger; inspectverifyWebhookandwebhook. - FAIL IF
webhook(event)accepts an object that this sameBillinginstance'sverifyWebhookdid not return — a parsed body, a copy, or an event another instance verified; inspectverifiedinwebhook. - FAIL IF the ledger row, the owner's resync and
applydo not share one transaction that marks the event processed only afterapplyreturns; an event whose ledger row is already processed reaches the resync orapplyagain; concurrent deliveries of one event are not serialized on that row; or a failure — upstream, database orapply— commits any billing or application write instead of leaving the event recordedfailedand unprocessed for a retry; inspectwebhook. - FAIL IF
pruneEventsdeletes an event that was never processed or is markedfailed, deletes one processed within the retention it was given, measures that retention by anything but the injectedTime, or accepts a retention under 30 days; inspectpruneEvents. - FAIL IF anything from an event's body is written to
subscriptions,checkoutsoraccounts, or itscreatedtime or arrival order decides a write. The body may only choose whose state to resync — the owner of the customer it names, or for acharge.dispute.*event the customer Stripe returns for its charge — and which rowcontext.subscriptionreads back, filtered to that owner; the state comes fromsynchronize, which lists the customer's subscriptions from Stripe only after locking the owner'saccountsrow. An event naming a customer pgstencil did not create resyncs nobody; inspectwebhook,synchronizeandownerOfCustomer. - FAIL IF
completedCheckoutreports a Checkout other than a stored operation whose ID and session ID both match the event's and whose owner is the session'sclient_reference_id, or reports fields other than that stored operation's; inspectcompletedCheckout. - FAIL IF a
billing.webhook.*diagnostic names astripeEventIdfrom a delivery that failed verification, or carries anything beyond whatdiagnosticanddiagnosticErrorinpackages/pgstencil/src/diagnostics.tsallow — never the body, theStripe-Signatureheader, the webhook secret, or the message of a Stripe, database orapplyerror; inspectwebhook.
Pinned by integration/billing.test.ts: checkout ownership, card policy, signatures, mode and API version are enforced, pruneEvents deletes only events processed before the retention window, the five-minute tolerance reads the delivery signature: a retry signed hours later applies once, an old signature never, application fulfillment and the webhook ledger share one transaction, duplicate and out-of-order webhooks reconcile authoritative state exactly once, an upstream outage rolls back entitlement changes and a failed event can retry, a deleted webhook racing end() waits on the owner lock and reads its reason, owner and customer lookups, dispute owners through their charge, and an already-verified event, a checkout ref is validated, kept across retries, and read back from the completion event, payment diagnostics keep verified event IDs and report failure before recovery without payloads; integration/billing-security.test.ts: webhook(event) takes only an event this Billing verified, and a rejected delivery reaches neither the ledger nor apply, concurrent deliveries of one event run apply once and record one attempt, event content never grants access: the resync reads Stripe, and context and completions are matched to the owner, webhook diagnostics name an event only once it verifies, and never carry a body, signature, secret or message; integration/billing-workers.test.ts: Billing in workerd: fetch client, Web Crypto webhooks, founding offer and purchase counts.
- FAIL IF a Checkout Session is created with a customer other than the owner's stored
customer_id, a Price other than the oneofferedPriceresolved for a configured plan name and stored on the operation, more than one line item, a quantity other than 1, asuccess_urlother thanreturnUrlwithcheckout=<operation ID>, acancel_urlother thanreturnUrl, or caller-supplied metadata other than arefmatching^[a-z0-9:-]{1,40}$underpgstencil_ref; or for a PriceexpectPricerefused; orportalsends areturn_urlother thanreturnUrl; inspectcheckout,offeredPriceandportal. - FAIL IF the constructor accepts an
originthat is not a bare origin, areturnPaththat resolves off that origin, aredirectOriginsentry that is not a bare origin, an emptywebhookSecret, or one Price under two plans; inspect theBillingconstructor. - FAIL IF a Checkout Session is created without
payment_method_collection: 'always'andallowed_payment_method_types: ['card'], or with a trial but withouttrial_settings.end_behavior.missing_payment_method: 'cancel'; inspectcheckout. - FAIL IF an operation is stored with trial days while the owner's
trial_used_atis set,synchronizelists a subscription carrying the owner's metadata without setting it, or anything clears it; inspectcheckoutandsynchronize. - FAIL IF an owner can hold two
pendingoropencheckouts (billing_one_checkoutin003_billing.sql), racingcheckoutcalls open more than one Checkout Session, orcheckoutopens one while — after its own resync, under the owner'saccountsrow lock — the owner holds a subscription in aSUBSCRIBEDstatus; inspectcheckout. - FAIL IF a customer or Checkout Session creation reaches Stripe before its idempotency key — the account's
customer_key, or the operation's ID — is committed, a retry sends another key or a Price, trial,refor expiry other than the stored operation's, or a creation first attempted 23 hours or more earlier is retried instead of refused; inspectcustomer,checkoutandassertRetryable. - FAIL IF
confirmCheckoutacts on an operation not stored for the calling owner, or on a Stripe session whoseclient_reference_id, customer or mode is not that owner's subscription Checkout, or grants anything except throughreconcile; inspectconfirmCheckout. - FAIL IF
endcancels a subscription that is not synchronized to the calling owner, or records a reason outside^[a-z0-9:-]{1,40}$; inspectendand008_billing_end_reason.sql.
Pinned by integration/billing.test.ts: the browser chooses only a configured plan name, never a price, yearly discount uses the configured price; canceled accounts cannot repeat a trial, checkout charges only a Price the application expects, and replace gives way to another plan, a checkout ref is validated, kept across retries, and read back from the completion event, yes-card trial: signup and an open checkout grant nothing; confirmed trial ends exactly on time, expired and canceled checkouts can be replaced without spending the trial, lost creation responses and concurrent clicks reuse durable idempotency keys, ambiguous customer creation beyond the retry window fails closed, checkout ownership, card policy, signatures, mode and API version are enforced, failed first payment revokes access and a successful retry restores it, a paused subscription grants nothing and blocks another checkout; resuming restores access, end() cancels at once without proration and records its reason, which the deleted webhook reads; integration/billing-security.test.ts: the server picks customer, price, quantity and return URLs, and the constructor refuses off-origin configuration, racing checkouts open one Checkout Session per owner, and none opens beside a held subscription, creation keys are committed before Stripe sees them, retries resend them, and an ambiguous Checkout past 23 hours fails closed, confirmCheckout acts only on the calling owner's own subscription Checkout, a subscription counts only with the owner's metadata, its customer, one item, a recognized price, quantity 1 and the configured livemode.
- FAIL IF
synchronizewrites a row for a subscription whosepgstencil_ownermetadata is not the owner's ID; writes any row for an owner when one of its subscriptions carrying that metadata has another customer, other than exactly one item, a Pricepricesdoes not recognize, a quantity other than 1 or alivemodeother thanlive— that must fail the whole resync; or leaves a row Stripe no longer lists for the owner in any status butmissing; inspectsynchronize. - FAIL IF
status().accesscomes from anything but anowner_accessrow whoseaccess_untilis later than the injected clock;owner_accessyields a row for an owner holding no subscription, or more than one, in aSUBSCRIBEDstatus, filters on statuses other thanSUBSCRIBED, or gives anaccess_untilto any status butactive(itsperiod_end) andtrialing(itstrial_end); or signing up, opening Checkout or a success URL grants access; inspectstatus,SUBSCRIBEDand007_billing_owner_access.sql.
Pinned by integration/billing.test.ts: owner_access is the access rule status() applies, for application SQL under a read-only grant, yes-card trial: signup and an open checkout grant nothing; confirmed trial ends exactly on time, a retired price keeps granting its plan and is never offered, failed first payment revokes access and a successful retry restores it, a paused subscription grants nothing and blocks another checkout; resuming restores access; integration/billing-security.test.ts: a subscription counts only with the owner's metadata, its customer, one item, a recognized price, quantity 1 and the configured livemode, event content never grants access: the resync reads Stripe, and context and completions are matched to the owner, confirmCheckout acts only on the calling owner's own subscription Checkout.
- FAIL IF
packages:packbuilds from a modifiedpackages,scripts,tsconfig.json,tsconfig.build.json,pnpm-lock.yaml,compose.yaml,LICENSEorpackage.jsonwithout--allow-dirty; inspectscripts/build-packages.ts. - FAIL IF a tarball omits
package/dist/provenance.json— the contract path a consumer reads out of the archive — that file omits the 40-character packing commit, or a pack of a modified tree is not marked"dirty": true; inspectscripts/build-packages.tsand PACKAGES.md. - FAIL IF
packages:verifyaccepts an installed package whose provenance commit is not this checkout'sHEAD, or that is marked dirty; inspectscripts/verify-packages.ts.
Pinned by pnpm packages:verify in .github/workflows/check.yml.
- FAIL IF
.github/workflows/check.ymlstops runningdb:verify,test:scripts,testandpackages:verifyon pushes tomainand on every pull request, dropspersist-credentials: false, or grants any permission beyondcontents: read; inspect.github/workflows/check.yml. - FAIL IF
.github/workflows/security-audit.ymlis missing or disabled, loses itspushtomaintrigger or itssecurity-auditjob name — a consumer reads that check run by name — lets the reporting step treat anything but an exactVERDICT: PASSline above a<!-- END OF REPORT -->sentinel as passing, or drops the step that redacts secrets from the report and the transcript; inspect.github/workflows/security-audit.yml. - FAIL IF the audit job stops declaring
environment: security-audit, that environment's deployment-branch policy admits any ref butmain(gh api repos/diffplug/pgstencil/environments/security-audit/deployment-branch-policies), or the ruleset onmainstops requiring a pull request or blocking force-push and deletion (gh api repos/diffplug/pgstencil/rules/branches/main); the environment is what keepsCLAUDE_CODE_OAUTH_TOKENfrom a workflow pushed on any other branch.
Pinned by pnpm test:scripts, which runs the shipped reporting and redaction shell rather than a copy: scripts/security-audit.test.mjs.
.github/workflows/security-audit.yml executes this file nightly at 04:51 UTC, on demand, and on every push to main. One agent runs every FAIL IF above as a mechanical check, then reads the code behind them adversarially, following .github/audit/_preamble.md and .github/audit/security.md; scripts/security-audit-local.sh runs the same prompts against the same files locally. A run that does not reach an exact VERDICT: PASS files or appends to an open issue labelled security-audit-failure and exits non-zero, and a later pass closes it. Every run archives the report and the session transcript as the audit-transcript artifact — public, like this repository, and kept 14 days.
Report privately through GitHub's advisory form for diffplug/pgstencil; never a public issue. pgstencil is pre-1.0 and releases to npm only from main, so a fix lands on main and reaches a consumer in the next release (PACKAGES.md); there is no backport branch.
- The consumer-owned controls listed above.
- An attacker holding both the database contents and
AUTH_SECRET. Better Auth stores native session tokens, so that pair mints a session cookie; either alone does not. - Better Auth behavior beyond what these tests pin; its private dependency is pinned to the exact tested release, and an upgrade must rerun the security and snapshot suites.
- A provider that asserts an email it did not verify, and account recovery after a lost provider account or mailbox. Facebook sends no verification claim, so pgstencil treats any address Facebook returns as verified.
- Two applications sharing both a database and
AUTH_SECRET: they share OAuth state, sessions and limits, and count as one application here. - Multi-factor authentication and passkeys, and abuse beyond the budgets above.
- Stripe itself: its hosted Checkout and portal, its trial, renewal and dunning jobs, and the hosted URLs it returns, which
checkoutandportalhand back without comparing them withredirectOrigins. pgstencil reads subscriptions, not charges, so a refund or dispute changes no access until the subscription ends. - Trials per person: eligibility is per billing owner, so one person holding several owners can start several trials.
- A subscription changed in Stripe outside the Entitlement rules — a second item, a quantity other than 1, a Price removed from
prices— fails every resync for its owner. That keeps the owner's last synchronized rows, and whatever access they grant until their ownperiod_endortrial_end, until the subscription or the configuration is repaired; alert on failed events. - An attacker holding the webhook signing secret: billing state still comes from the resync, but
applyruns on an event they wrote, so anapplythat trusts the body beyondcontextandcompletedCheckoutis the application's. Whoever holds the Stripe secret key controls the Stripe account. - A delivery of an event whose ledger row has been deleted, as
pruneEventsdeletes old processed rows: it is processed again,applyincluded. - The rest of
@pgstencil/stripe— purchase counts, thereconcileStaleandreconcileAlljobs, and the StripeDev simulator inpackages/stripe/src/testing.ts, a test double — and the example applications; see BILLING.md.