Status: Pre-production security assessment Assessment date: 2026-07-12 Scope: This repository's React application, Firebase configuration/functions/rules, Stripe integration, Strava integration, CI/CD, operational data, and documented deployment model
This is both the repository security policy and the current engineering risk register. It is not a penetration-test report, legal opinion, PCI attestation, privacy certification, or guarantee that deployed cloud settings match the repository. External Firebase, GCP, Stripe, GitHub, DNS, Sentry, and email-provider configuration must be reviewed separately.
The public informational website can continue operating with normal care. Live race or merchandise payments should remain disabled until every P0 launch blocker below is closed and verified in a staging dress rehearsal. The existing payment implementation is a useful prototype, but several correctness defects could mark unpaid transactions paid, lose payment callbacks, oversell race capacity, fail to reconcile late-payment links, or allow overly broad administrative access.
Do not disclose a suspected vulnerability, secret, customer record, payment reference, or exploit in a public GitHub issue. Until MPRC publishes a dedicated security address:
- Use the private contact channel listed on the live MPRC contact page.
- State that the message is a security report and request a secure reply channel.
- Include the affected URL/component, impact, reproduction with synthetic data, and any suggested mitigation.
- Do not access, change, download, or retain data beyond what is necessary to demonstrate the issue.
An implementation issue must establish security@runmprc.com or another monitored private address, named primary/backup responders, an acknowledgement target, and a disclosure policy before commerce launch.
- P0 — launch blocker: credible risk of incorrect money state, privilege compromise, secret/PII exposure, oversell, or inability to recover safely. No live payments.
- P1 — high: material abuse, privacy, availability, or operational-control weakness. Complete before broad launch or explicitly accept with a time-bounded compensating control.
- P2 — medium: defense-in-depth, maintainability, or bounded operational risk. Schedule soon after the secure pilot.
- P3 — improvement: useful hardening with lower immediate impact.
Closing an item requires code/configuration, automated or documented tests, deployed-environment evidence, monitoring, and an owner—not only a code diff.
The findings below describe the repository at the start of the 2026-07-12 assessment. The IDs use the RISK-* namespace so they cannot be confused with the independently publishable issue IDs in GITHUB_ISSUES.md. A working-tree fix lowers exposure only after review, merge, deployment, external configuration, and evidence; the remediation ledger after the tables records that distinction.
| ID | Finding and evidence | Impact | Required treatment |
|---|---|---|---|
| RISK-001 | stripeWebhook.js marks every checkout.session.completed record paid without validating payment_status, amount_total, currency, environment, metadata schema, or allowed prior state. |
Delayed or anomalous payments can be fulfilled as paid; local totals can disagree with Stripe. | Implement verified payment reduction, async success/failure handling, quarantine, and reconciliation. |
| RISK-002 | Stripe Event IDs are not durably deduplicated; object transitions and emails are not comprehensively idempotent. Existing webhook tests cover only invalid signatures/method. | Duplicate/retried/out-of-order events can repeat side effects or corrupt audit/state. | Add a durable event inbox, atomic transitions, and duplicate/out-of-order tests. |
| RISK-003 | Paid Checkout Sessions are created before their registration/order record is persisted. The current handlers also relied on the resolved Session's raw ID and URL without checking its declared mode, amount, currency, buyer, labels, callbacks, state, or URL origin. The webhook only queries for the stored Session ID. | A fast webhook, lost reply, malformed result, function crash, or repeated browser request can leave a paid or payable Stripe transaction with no local confirmation, an arbitrary redirect, or duplicate Sessions. | Keep #357's immediate result/terminal-retry containment and #503's synchronous pre-render one-attempt browser guard, then replace the flow with a persistence-first deterministic-key saga, durable result/reconciliation evidence, approved URL/configuration binding, and metadata-direct lookup. |
| RISK-004 | Race capacity uses a count-then-create sequence outside a transaction; registeredCount is displayed but not maintained. |
Concurrent users can exceed capacity; stale pending Sessions can block capacity for up to seven days. | Introduce transactional capacity counters/reservations, matching expiry, backfill, and concurrency tests. |
| RISK-005 | Merchandise has product status but no SKU/variant inventory reservation. | The club can sell unavailable size/color combinations or oversell stock. | Add canonical variants and atomic inventory holds before live merchandise. |
| RISK-006 | main source now removes the recursive browser-admin rule, but deployment repeatedly skipped Firebase and the live Rules revision is unproven. |
A stale deployed ruleset may still expose secrets or financial/audit writes to a compromised admin session. | Use #105 to stage and deploy the exact tested #100 Rules revision, verify deny behavior with synthetic accounts, and record rollback. |
| RISK-007 | Repository source rejects unverified targets at the two existing role-grant endpoints. AUTH-001B #196 adds exact verified-email checks to role-based Firestore Rules, and AUTH-001C #209 tracks the matching gate at current Functions role consumers. Firebase deployment, verification-mirror refresh, revocation, authoritative membership, scoped capability/recent-auth controls, and replacement of the legacy static-key endpoint remain unproven. | Older deployed code or a remaining role-only server guard can let an unverified known-email account use member/admin authority. CSV export also retains separate minimization, recent-auth, and audit risks. | Deploy and verify the exact Rules/Functions through #105, complete the remaining AUTH-001 mirror/revocation work, require authoritative membership and scoped privileged access, harden roster export under #116, and retire the static-key endpoint. |
| RISK-008 | requireAppCheck is controlled by optional ENFORCE_APP_CHECK; repository/deployment config does not prove it is true. Missing site key also disables client App Check. |
Public callable functions may be scripted directly, enabling abuse and cloud/Stripe cost amplification. | Use Firebase runtime enforceAppCheck: true for sensitive callables, reCAPTCHA Enterprise, staged metrics, and no environment fail-open. |
| RISK-009 | #99 source preserves and validates the complete same-origin Pages callback route, applies strict-origin before subresources, and suppresses initial callback App Check/telemetry startup; #126 adds that standalone suite as a blocking hosted CI step. #443 adds only the source/test Strava same-document handoff after the native and Router callback locations are clean. No production Stripe/Strava callback, provider-backed App Check token behavior, or live custom-domain handoff has been rehearsed, and the live custom domain uses a different host. |
Hosting, provider, configuration, or release drift can still break return links or the App Check handoff even when source tests pass. Enforcing a callback callable without its matching released client handoff and staged provider evidence can also break confirmation or Strava exchange. | Keep the hosted callback gate blocking; keep lookupRegistration/lookupOrder deferred until DATA-001A; keep native enforcement for stravaExchangeCode deferred to ABUSE-001A2 after the exact #443 website source and provider-backed Enterprise token behavior are proven in protected staging; verify test-mode callbacks only after provider gates; consolidate hosting under WEB-001. |
| RISK-010 | Development/test now use synthetic loopback Auth, Firestore, and Functions, but optimized previews still target production Firebase and emulated Functions can call outside providers. | A maintainer can mistake a preview or Firebase emulator for complete staging and affect production/provider systems. | Prohibit private actions in previews; add dedicated staging/config under #105/CONFIG; prove test keys/sinks before provider flows. |
| RISK-011 | Local cancellation only changes Firestore; it does not expire the active Stripe Session. The webhook can later move a cancelled record back to paid. | Customers can pay a supposedly cancelled registration/order; capacity/inventory and support state diverge. | Add explicit cancellation saga with Stripe Session expiry and allowed-state transitions. |
| RISK-012 | Repository source now denies every admitted positive late-registration amount before identifier/token allocation, registration writes, Stripe construction, or Product/Price/Payment Link calls, and the Admin screen exposes only the exact-zero compatibility path. Firebase deployment is unproven, and legacy reusable Payment Links may still exist. | Older deployed code or an existing link can still create unreconciled or repeated charges/Sessions for one logical registration. The exact-zero local record is also marked paid without Stripe and is not payment, free, comp, or membership authority. | Keep paid late registration unavailable; inventory, deactivate, and reconcile legacy links privately; then replace them with one-off idempotent Checkout Sessions through the normal registration/payment lifecycle. |
| RISK-013 | The assessment baseline permitted Stripe promotions while Firestore stored base price. Repository source now disables promotion entry and automatic tax, requires a complete adjustment breakdown, and quarantines unknown or nonzero discount/tax/shipping amounts; deployment and provider state are unproven. | An older open Session, provider drift, skipped backend release, or later guard regression could still make reporting, refunds, and local payment confirmation disagree. | Privately inventory pre-change Sessions/provider settings, release through the protected gate, verify Firebase and Stripe separately, and keep every adjustment disabled until an approved authoritative model exists. |
| RISK-014 | The 2026-07-14 UTC Node 20.19.5 production audits on exact main e304342c68bf8c4e93b147827bf8433c4ca7f99c report 6 root affected-package findings (3 high, 2 moderate, 1 low, 0 critical) after #127 removes unused gpxparser, #192 patches the compatible React Router graph, and #202 upgrades the browser Firebase SDK; every affected root package node is transitive. After #212 patches the Functions form-data node, the Functions production audit reports 8 moderate affected-package findings (0 high or critical); firebase-admin is the sole directly affected package across the two manifests. |
Known vulnerable or unmaintained runtime/build chains increase XSS, request, DoS, and supply-chain exposure. | The remaining findings are unresolved and not accepted as safe. Stage the supported Firebase Admin/Functions upgrade and build-system replacement separately with compatibility tests; add continuous scanning without a forced audit upgrade. |
| RISK-015 | Privacy.jsx and Terms.jsx explicitly say REPLACE WITH DATE and Placeholder template; tax, refund, shipping, waiver, retention, and customer-support policies are not approved. |
Users are asked for sensitive data and money without final disclosures/policies; disputes and regulatory obligations are unmanaged. | Obtain appropriate legal/tax/insurance review, approve versioned policies, and block live-mode configuration until complete. |
| ID | Finding | Required treatment |
|---|---|---|
| RISK-016 | Success/lookup bearer tokens are stored plaintext in Firestore and placed in query strings, which can enter history, screenshots, logs, analytics, and referrers. | Prefer authenticated ownership or server-verified Session IDs; hash anonymous tokens, expire them, use fragment/session state, and scrub history. |
| RISK-017 | Input validation checks only a few required fields. Names, phones, dates, notes, tracking values, custom field maps, arrays, URLs, and payload sizes are weakly bounded or unbounded. | Add shared strict schemas, allowlists, normalization, size limits, URL policy, and hostile-input tests before any write or external call. |
| RISK-018 | Event-defined required custom fields and allowed options are enforced by HTML, not by the checkout function. | A scripted client can omit required answers or inject arbitrary data into protected records/exports. |
| RISK-019 | Confirmation email HTML directly interpolates runner and event values; email outbox creation and sent-marker update are separate writes. | HTML injection/phishing-like content and duplicate messages on trigger retries. |
| RISK-020 | Refund endpoints lack stable Stripe idempotency keys, comprehensive remaining-balance validation, and a pending/canonical webhook model. | A timeout/retry or repeated click can create ambiguous or multiple refund attempts. |
| RISK-021 | Admin state transitions accept impossible combinations: cancel paid without refund decision, fulfill unpaid/cancelled orders, substitute without new waiver, or comp without proven waiver. | Financial, legal, fulfillment, and waiver records can be inconsistent. |
| RISK-022 | Existing rate-limit documents contain raw IP or email keys and values; the profile-thumbnail slice instead uses a domain-separated SHA-256 account-key pseudonym, which is stable and not anonymous. Fixed windows are distributed only by those identifiers and cleanup depends on an externally configured TTL. The profile-thumbnail upload limiter charges every image-processing invocation, including an exact domain-command retry, because request ID alone cannot prove unchanged image bytes without retaining more image-derived data. | Additional PII or linkable pseudonymous storage, unbounded growth if TTL is absent, identifier-targeted denial of service, retry quota consumption, and easy distributed abuse. |
| RISK-023 | The legacy functions.config().api.key endpoint uses a static shared key with no replay control, source restriction, App Check, authenticated operator, or request audit. The API is also scheduled for Firebase decommissioning in March 2027. |
Key theft permits bulk role changes and future deployment failure. |
| RISK-024 | OAuth tokens remain plaintext. Repository Rules deny browser access, but protected deployment and live behavior are unproven; least-privilege runtime IAM and an encryption decision remain open. OAUTH-001A1H #606 source transactionally rereads the exact access-token, refresh-token, and expiry tuple before persisting a refresh response. A retry rereads the tuple; changed, missing, or malformed state returns one fixed failure without a stale write or downstream bearer call. OAUTH-001B9 #608 source records the disconnect secret's Firestore update time before optional revocation, then transactionally rereads it and deletes the local secret/connection pair only when the secret is absent or still has that exact version. A newer version, including byte-identical replacement, is preserved with one fixed unknown result; a transaction conflict rereads without repeating provider work. OAUTH-001B10 #610 browser source admits only a fulfilled response value that presents exactly one own enumerable data field, ok equal to true; it returns a new frozen confirmation and routes other fulfilled results or exceptional inspection through the existing fixed unknown-result view without hiding the connection. Browser JavaScript cannot distinguish a transparent Proxy that faithfully presents that contract, so the received object is never retained. Synthetic tests use no real token, account, or provider call. Provider calls are still not serialized; an exact browser confirmation does not independently prove provider revocation, and provider/local and lost-acknowledgement reconciliation, a trusted connection-only write that does not also version the secret, deployment, and live behavior remain unproven. |
Token theft exposes member activity data; an undeployed boundary or an unhandled provider/acknowledgement race can still lose availability or leave local and provider state ambiguous. |
| RISK-025 | Admin authentication relies on password Auth plus a long-lived role token; no repository evidence of MFA, recent-auth checks, re-auth for refunds/role grants, or rapid revocation workflow. | A stolen admin session has broad durable impact. |
| RISK-042 | Profile photos and an officer people finder have no approved privacy notice, retention/backup treatment, scoped capability, or connected production release path. The existing Admin Members page downloads complete account documents and cannot enforce an opt-out promise. #505/#506 source supplies the private processed thumbnail, default-off preference, minimum server-only projection, and bounded audited verified-admin name search, but none is deployed. MEMBERS-DIRECTORY-001D #621 makes the frontend default an inert, disabled preview with zero directory reads, uploads, searches, saves, request IDs, or service calls; it does not make the backend available. WEB-002C #623 published exactly that frozen inert frontend as rollback deploy 6a7e072f8f346b0008510d29. WEB-002D #659 preserved it byte-for-byte beneath the live accessibility delta in deploy 6a7ece87c5ca4d0007c1a3fc. Neither release connected or deployed the backend; directory availability remains literal false. |
A photo, name, query, preference, prefix digest, or stable entry reference could be exposed, retained, enumerated, correlated, or mistaken for membership evidence if the connected path is enabled prematurely; automated face matching would add unapproved biometric processing. A browser availability constant is not authorization. |
| RISK-026 | Firestore Admin SDK bypasses rules and the Functions runtime IAM scope is not documented. #135 removes the long-lived service-account JSON path from release source, but the least-privilege short-lived deploy identity is not configured yet. | Server or CI compromise may expose the entire project; missing provider configuration also blocks release. |
| RISK-027 | Sentry optionally records user email and enables 100% replay on error; analytics/privacy consent, redaction, retention, and field-deny policies are not evidenced. | Forms and account flows contain PII that may be sent to monitoring vendors unexpectedly. |
| RISK-028 | No payment reconciliation job, dead-letter/quarantine workflow, or alert proves paid Stripe objects match Firestore. | Missed webhooks and partial failures can persist unnoticed. |
| RISK-029 | Hosting on GitHub Pages limits controlled response headers and relies on custom JavaScript SPA fallback. | Weaker CSP/clickjacking/referrer controls and fragile OAuth/payment routes. |
| RISK-041 | The Admin Product editor directly creates or updates Firestore records without a durable command ID, version fence, audit record, readback, or reconciliation. Its browser catch also exposed rejected save details and made an ambiguous result immediately repeatable; a rejection may follow a committed write. | Private provider, account, or database details can reach the page. Repeated or obsolete writes can leave catalog state uncertain. |
| ID | Finding | Required treatment |
|---|---|---|
| RISK-030 | Server-side Stripe client requests select 2023-10-16, but webhook endpoint Event versioning is configured independently at Stripe and is unverified; ingress does not validate event.api_version. The SDK is old and no upgrade cadence is documented. |
Privately inventory the endpoint Event version; validate/pin the supported contract; schedule test-mode API/SDK and webhook-version upgrades with breaking-change fixtures before accepting newer enums. |
| RISK-031 | Lazy Stripe Product creation is concurrency-prone and reachable from anonymous checkout traffic. The current paid race and Shop paths also trust stored Product links and resolved Product-create results too broadly; PAY-PRODUCT-001A #353 adds narrow source containment for those two inputs. | Keep the risk open. Replace anonymous creation with authenticated, idempotent catalog synchronization and reconciliation. Before deployment, inventory and approve every real mapping under #113 and complete Product-specific plan, pre-send, result, lost-acknowledgement, retry, and reconciliation controls. |
| RISK-032 | auditLog arrays grow within primary documents and are editable by broad admins. |
Move to append-oriented, immutable-by-client audit records with retention/export rules. |
| RISK-033 | CSV export mitigates formula injection, but roster export has no explicit re-auth, download audit, row limit/streaming limit, or data-minimization profiles. | Add scoped exports, reason/re-auth, audit, minimum columns, safe filename IDs, and large-export controls. |
| RISK-034 | Webhook error response includes the Stripe library's signature error detail. | Return generic client errors; keep sanitized structured diagnostics server-side. |
| RISK-035 | The deterministic frontend Jest suite and standalone SPA callback suite run as separate blocking hosted CI steps. CI-001B4/#186 merged a non-mutating frontend lint gate as bec7d5e365eacb418563a172029f241f660d9768; exact PR and post-merge runs passed. CI-001B4A #227 removes one reviewed arrow-body-style error, CI-001B4B #239 removes one stale AdminMembers unknown-rule suppression record, and PAY-004C1 #359 retires one no-alert warning plus two label-association errors with the unsafe reusable-link controls. Reviewed functional changes in PR #391 and PR #392 add one and two TypeScript files respectively while retiring two TSX errors each. CI-001B4C #449 replaces the four remaining stale react-hooks/exhaustive-deps directives with ordinary same-line comments, without changing executable code or the gate. MEMBERS-CONTENT-001B #492 removes one finding-free dormant JSX file. MEMBERS-DIRECTORY-001A #505 adds four finding-free TypeScript/TSX files, MEMBERS-DIRECTORY-001B #506 adds four more, WEB-SUGGESTIONS-001A #618 adds one finding-free JSX page, and MEMBERS-DIRECTORY-001D #621 adds one finding-free TypeScript availability module. The current baseline scans 120 files and still records 113 configured errors and 6 warnings after the lint process disables the repository's severity-masking eslint-plugin-only-warn hook. Branch protection, remaining lint-debt cleanup, and broader domain/integration coverage remain incomplete. |
Continue reducing the reviewed finding baseline in focused changes, prove required branch checks, and add domain/integration coverage. Never regenerate the baseline merely to make CI green. |
| RISK-036 | #135 adds a manual exact-commit source gate, fixed profile-recovery targets, backend-first order, missing-config failure, and ordinary Git-triggered Netlify production containment. WEB-UX-001A #457 used a temporary exact-artifact exception. On 2026-08-01 an overbroad WEB-002A artifact was merged after a late blocker and published; exact rollback merge 1099ee8 restored source ed1b0833, and dee7951 paused the manifest. Bounded replacement #473 then published exact source 39ab8649 as deploy 6a6dc9ea588b0c0008036312; its delta is only Shop and Events/Calendar failure containment, and its public checks passed. Final control cb6a8f0 made the manifest inactive; Netlify attempt 6a6dcdd47bc81e000859a249 stopped unpublished and left that bounded deploy as #623's rollback. WEB-002C #623 then completed one exact-artifact release: merge 9d5cc8612b4321172370bd949d307e7e4ac0ec7d published frozen inert source c2d87d1f69f15e128a0bc9b1b9f915b7c8417aec, tree 411aa6ec9a9459f5d923030533ffc7c007fe6908, 62 files, and digest d837272a1e5efc1575809e87f532276b38d1a63f1dd79ec1aef0533f6da8afb1 as deploy 6a7e072f8f346b0008510d29. Signed-out marker, route, guard, and no-connected-symbol/request checks passed. Repause merge c8678c623afdd9becf77d596b71f36f26f04b746 made the manifest inactive; attempt 6a7e081e73fdd60009f7ba57 stopped unpublished and retained the verified deploy. The release source is absent and the rollback ref remains. Protected environments/OIDC, isolated staging, a reusable live-Netlify path, and provider-owned atomic rollback remain unverified. |
Require a final blocker re-read and an executable delta from the live artifact before every release merge. Preserve the exact #473 rollback evidence, keep ordinary publication paused, and keep the reviewed Git rollback projection available. Treat #623 as a completed one-off, not a reusable control. Complete #133 and #136, provision isolated staging, protect the reusable Netlify release path under WEB-001, and rehearse provider rollback before broader production work. |
| WEB-002D completed exact-artifact containment for RISK-036 | #659 completed one accessibility-only release on 2026-08-14. Exact release merge 46e23647d8e0bf9fa3a574ea5c5f993be10a419d published deploy 6a7ece87c5ca4d0007c1a3fc; its marker and all 62 artifact paths matched frozen source 7496fe0881fb52908c4ff2f40f488df09c94c908, tree ccac4c189c195db8ab594e0eefe256ea9fa04996, digest e4c26e6f0fbcd086663d86238675f0be228fb649a00628c1c97d1166612f49c7, and exact six-path diff digest 462eeb01e7a9858678802464f7dd4b76cd2fcb3c13be827efb4f98fa53ca809c. Signed-out desktop and phone route-focus/menu checks passed. Repause merge 3138a00c1c48e1d5d1dcda0b44722b09a2194ff7 passed exact-main CI run 31783808994; attempt 6a7ed0ddb00a46000818878d stayed unpublished and retained the verified deploy. Literal-false directory availability, Firebase, Rules, Functions, indexes, outside-provider configuration, accounts, sign-in, production data, payments, and connected directory behavior were unchanged. |
The manifest is inactive; source/control/repause refs are absent; rollback ref codex/netlify-source-659-rollback remains pinned to #623 source c2d87d1f69f15e128a0bc9b1b9f915b7c8417aec. Treat #659 as a completed one-off, not reusable authority. Keep ordinary publication paused, preserve #507 connected-directory gates, and preserve active #616 OAUTH-001A2L files and its RISK-024 wording byte-for-byte. |
| RISK-037 | Account/registration deletion, export, retention, backup, and restore procedures are incomplete. | Approve retention matrix, automate minimization, support access/deletion requests, and test backup restoration. |
| RISK-038 | Source-controlled secret scan is ad hoc; no continuous secret scanner, dependency update bot, SBOM, provenance, or branch protection is documented. | Add secret/dependency/code scanning, reviewed lockfile updates, protected environments/branches, and artifact provenance appropriate to project scale. |
| RISK-039 | Some authenticated accounts can lack members/{uid} after the Firebase cutover; the account screen hid the read failure and exposed an update that could only fail. Manual database/account repair could corrupt roles or private data. |
Use an authenticated create-once server bootstrap, keep browser creation denied, fail the UI closed, and prove backend-first deployment with synthetic accounts. |
| RISK-040 | Registration currently treats account creation and the later verification-email request as one success. A rejected request can still produce “check your inbox,” while sender delivery and Spam placement are unverified. | Return two explicit outcomes, preserve the signed-in account on email-request failure, show only generic recovery text, and verify source, website revision, and provider delivery separately. |
These entries are implementation evidence, not a production risk-acceptance decision:
| Risks | Source result | Remaining closure evidence |
|---|---|---|
| Source-only containment for RISK-008, RISK-017, RISK-022, RISK-032, and RISK-042 | MEMBERS-DIRECTORY-001A #505 adds four caller-only profile callables with native App Check enforcement and fail-closed private no-store responses. Strict requests, revisions, current-name opt-in, normalized 256-by-256 WebP output, metadata/animation/size rejection, separate default-off visibility, server-only preference/photo records, atomic append-oriented audits, and direct-browser denials are covered with generated non-face data. No original image, filename, provider photo URL, image-derived digest, face matching, or membership authority is stored or returned. The exact sharp@0.35.0 dependency has no finding in the 2026-08-01 production audits; the audits still report 4 existing root findings (1 high, 2 moderate, 1 low) and 10 existing Functions findings (9 moderate, 1 low). |
This slice is not live and by itself supplies no officer search; #506 adds that separate source boundary. Every upload or retry still writes stable, domain-separated, SHA-256 account-key abuse-control bookkeeping, consumes quota, and depends on an unverified TTL; that pseudonym is linkable and is not anonymization. Profile/photo/audit state alone is read-only on an exact retry. Complete #110 privacy/retention/backup decisions, #133 protected short-lived authority, #507 isolated staging and backend-first deployment/readback, approved public notice wording, synthetic opt-out/removal/cache proof, website publication, and runmprc.com verification before use. Do not inspect real profiles or enable biometric processing. |
| Source-only containment for RISK-008, RISK-017, RISK-022, RISK-032, and RISK-042 | MEMBERS-DIRECTORY-001B #506 adds one separate officer people-finder page, one native-App-Check verified-admin callable, and one current-state projection reconciler with idempotent background retry. The browser submits only an explicit UUID/name-prefix command and accepts at most 24 exact display-name/optional-thumbnail projections. Direct Firestore access is denied. The server rejects Unicode controls/formats and invalid canonical bounds, stores only prefix digests under a proven 272-entry ceiling, permits 30 searches per verified-admin account/hour, queries at most 48 candidates, re-reads each current member/preference/photo record before returning it, and atomically records one minimal query-free audit. An opted-out, missing-name, malformed, stale, or now-nonmatching candidate is hidden. The existing full-account admin filter is visibly separate. | The projection's internal UID key, prefix digests, entry reference, rate key, and audit identity remain stable/linkable server-side pseudonyms, not anonymization. A response still contains personal data for its approved purpose. Exact request-ID reuse gets a fixed denial instead of retained-result replay and creates no second audit; the rate limiter may still consume an attempt. The current verified admin role is only a documented compatibility boundary, not a scoped people-finder capability. Source/tests/preview do not configure TTL, privacy notice, retention/backups, repair/backfill ownership, staging, Firebase Rules/Functions/indexes or trigger policy, Netlify, or production behavior. Complete #110, AUTH-003, #133, and #507 before use; never test against real profiles or add image queries, facial recognition, embeddings, similarity, analytics, or raw-query logs. |
| Frontend-only containment for RISK-042 | MEMBERS-DIRECTORY-001D #621 adds one source-controlled availability boundary that defaults unavailable. The account page and AdminGuard-protected People finder render the future layout as visibly described, natively disabled controls. The default path does not mount the connected components, read a file or saved state, accept a name, create a request ID, initialize or call a directory service, or render sample or result cards. Focused tests preserve the connected source behind an explicit seam using only synthetic data. #623 published the equivalent inert interface projection as exact 62-file deploy 6a7e072f8f346b0008510d29. Protected-layout evidence stays synthetic; completed signed-out public checks proved only the exact revision, normal sign-in/admin guards, and absence of a member-directory request. The manifest is inactive. |
This is an accidental-call and misleading-interface containment, not an authorization, privacy, retention, backend, provider, or connected-live-behavior control. A later build must not turn availability on through configuration drift or an unreviewed environment value. #623 changed no Firebase, provider configuration, account, sign-in, or production data. #507 must complete approved notice and backup/removal wording, scoped authorization, protected authority, isolated staging, backend-first Rules/Functions/index deployment and readback, synthetic privacy/race proof, and then a separately reviewed source flip before connected website publication. Until then, no real name or photo may be entered, read, uploaded, searched, or saved. |
| Source-only privacy and accessibility containment for RISK-042 | MEMBERS-DIRECTORY-001E #627 makes the preserved connected Account branch use the projection's bounded Unicode display-name eligibility before a new opt-in while preserving turn-off for an existing opt-in whose name becomes ineligible. It gives no-photo placeholders image semantics, associates generic photo/setting errors with the affected control, exposes name-search validation state and descriptions, and announces a successful non-empty result without a total. Native controls retain keyboard use, explicit readable foreground/background colors, at least 44-pixel interaction height, and 320-pixel containment. A deferred file read cannot submit or render after the application/user context changes. Generated-only tests cover eligible/ineligible current-name updates, accessibility states, narrow layout/contrast classes, and stale file-read races. | This is source-only frontend hardening behind the unchanged false availability value. The live #623 preview remains inert; no backend, Rules, index, service contract, package, workflow, provider, release, account, sign-in, or production-data behavior changes. Accessibility state is not authorization, and a browser eligibility check is not server enforcement. #507 still owns privacy approval, scoped authorization, protected authority, isolated staging, backend-first deployment/readback, the availability flip, connected publication, and live proof. Do not use a real name or photo. |
| Source-only explicit photo-review containment for RISK-042 | MEMBERS-DIRECTORY-001F #629 makes the preserved connected Account branch validate and read a selected JPG, PNG, or WebP locally into a centered-square component-memory draft labeled Selected photo — not uploaded yet. Selection, validation, reading, preview, and Cancel create no request ID and call no directory service. Only explicit Save profile photo creates the request ID and sends the existing revisioned upload request. Confirmed Save clears the draft after authoritative refetch. Request-ID failure or definitive rejection plus successful refetch retains a retryable draft and adopts the refreshed revision; unknown or failed readback discards the draft and hides controls. A visibility save preserves the draft. The application never reads, retains, renders, logs, sends, or otherwise exposes file.name; tests use only opaque generated fixture names. Generation and application/account-context fences prevent an older read or render event from changing or uploading a newer draft. Invalid and unreadable selections retain no draft. Unrenderable bytes and their draft UI are discarded, the fixed error remains associated with the persistent file control, and no Save action is exposed. The authoritative saved thumbnail has a version-reset, byte-free Photo unavailable fallback while Remove remains available. |
A local preview still places private image bytes in browser memory, and explicit Save does not itself prove an approved notice, scoped authority, backend deployment, or successful processing. Retryable bytes intentionally remain in component memory only for request-ID failure or definitive rejection with successful readback; an unknown outcome clears them. This source-only UI control stays behind the unchanged false availability value; the live #623 preview remains inert. It changes no visibility preference or service/server contract. #507 still owns privacy and retention approval, scoped authorization, protected authority, isolated staging, backend-first deployment/readback, the availability flip, connected publication, and live proof. Use only generated non-face test images; do not inspect or upload a real photo. |
| Source-only rendered-result containment for RISK-042 | MEMBERS-DIRECTORY-001G #631 makes the preserved connected People finder distinguish an absent photo from a supplied thumbnail that the browser cannot display. null renders No photo; a failed non-null render removes the image/data URL from the page and renders Photo unavailable; a different later version receives a fresh render attempt. After completed validation, empty results, fixed failure, or result cards, Clear search and result cards empties the query, removes the prior message, headings, cards, names, and images from component state and the rendered page, announces Search field and displayed result cards cleared., and focuses the name input. It creates no request ID and makes no directory-service call. Generated-only tests use made-up names and non-face thumbnails to cover the fallbacks, version reset, four clearable outcomes, focus, zero-call behavior, keyboard/44-pixel/contrast/320-pixel layout, and the unchanged unavailable default. |
This local rendered-page disposal is not cancellation, browser-memory erasure, cache removal, audit rollback, or recall of a response already returned, seen, or captured. Clear is absent while idle or pending; existing pending, stale-context, guard, projection, cap, and fixed-error behavior remains. The source-controlled availability value stays false, and live #623 remains inert. No service/server contract, Firebase, provider, account, sign-in, production data, or live behavior changes. #507 still owns notice/retention approval, scoped authorization, protected authority, isolated staging, backend-first deployment/readback, the availability flip, connected publication, and live proof. Never add a photo query, face recognition, embedding, similarity, biometrics, total, export, or roster authority; use no real name or photo. |
| Source-only reviewed-replacement containment for RISK-042 | MEMBERS-DIRECTORY-001H #633 renames the destructive control Remove current saved photo and makes a confirmed remove plus authoritative readback preserve the exact current reading or ready component-memory draft identity while updating only the current saved-photo state and revision. The replacement creates no upload request or service call until a later explicit Save creates a new request ID, sends the same content type and bytes once, and uses the refreshed revision. Definitive rejection plus successful readback preserves the draft and associates the fixed error with Remove. Unknown outcome or failed post-mutation readback clears the draft identity, bytes, and data URL, hides photo and finder mutation controls, retains only the existing Reload settings recovery, and exposes no Save or duplicate-mutation retry. Confirmed readback moves focus to a still-current Remove action, otherwise an eligible Save action, otherwise the persistent Add/Replace input. Generated-only tests cover exact-byte/revision reuse, reading and ready drafts, no-draft and concurrent-photo focus, rejection, unknown/readback failure, unchanged visibility, stale contexts, reselection, unmount, and the unchanged unavailable default. | Intentionally preserving an unsaved replacement keeps private image bytes in component memory after the current saved photo is removed; it is not an upload, durable draft, server state, cancellation, or deletion proof outside the authoritative saved-photo readback. Context and generation fences limit stale restoration, while unknown state fails closed by discarding the draft. The source-controlled availability value stays false, and live #623 remains inert. No service/server contract, photo query, face recognition, matching, embedding, similarity, biometric processing, roster authority, membership proof, Firebase, provider, account, sign-in, production data, or connected/live behavior changes. #507 still owns notice/retention approval, scoped authorization, protected authority, isolated staging, backend-first deployment/readback, the availability flip, connected publication, and live proof. Use only generated non-face test images; do not inspect or upload a real photo. |
| Source-only uncertain-change recovery containment for RISK-042 | MEMBERS-DIRECTORY-001I #635 gives the preserved connected Account branch a component-lifetime uncertainty marker for an ordinary mutation failure or failed authoritative post-mutation readback. Failed and repeated Reload settings reads retain the fixed We could not confirm that change warning, keep photo and finder mutation controls hidden, and cannot restore discarded draft bytes or data URLs. A successful guarded authoritative profile read clears uncertainty and displays the returned state. Initial load failures and failed confirming reads after definitive mutation rejection remain generic unavailable; that generic message no longer globally promises No setting was changed. Reload creates no request ID or visibility, upload, or removal mutation. Generated-only tests cover repeated failures, eventual authoritative success, generic failures, zero mutation retries, discarded bytes, and stale application/account reload fences. | The marker preserves truthful uncertainty only within one mounted application-and-account component lifetime; it is not provider acknowledgement, durable mutation evidence, reconciliation, deletion proof, or authorization. Generic unavailable state deliberately does not infer whether a prior change occurred. The source-controlled availability value stays false, and live #623 remains inert. No data movement, page structure, service/server contract, Firebase, provider, account, sign-in, production data, deployment, or connected/live behavior changes. #507 still owns notice/retention approval, scoped authorization, protected authority, isolated staging, backend-first deployment/readback, the availability flip, connected publication, and live proof. Use no real name or photo. |
| Source-only recovery-focus containment for RISK-042 | MEMBERS-DIRECTORY-001J #637 gives the preserved connected Account branch one explicit recovery-focus intent for a user-initiated uncertain mutation, failed authoritative post-mutation readback, or failed confirming read after definitive rejection. The intent is tied to the current mounted application-and-account lifetime. A Reload-created intent is additionally bound by the next load effect to that exact load identity. Only the matching current unavailable or unknown transition focuses the rendered Reload settings action. Repeated failed reloads focus each replacement action. Initial or background load failure has no intent and does not steal focus; successful authoritative reload clears the intent without redirecting focus to an unrelated ready control. Application change, account change, unmount, and stale completion remain focus-inert. Generated-only tests cover all three mutations, both readback-failure classes, repeated uncertain and generic reload failures, successful reload, outside focus, application/account changes, unmount, zero retry or request-ID creation, and the unchanged unavailable default. | Programmatic focus is accessibility state, not provider acknowledgement, authorization, durable reconciliation, deletion proof, or evidence that a stale load became current. It creates no service call, mutation, automatic retry, draft restoration, data URL, or new data flow. The source-controlled availability value stays false, and live #623 remains inert. No page structure, service/server contract, Function, Rule, index, Firebase or provider configuration, account, sign-in, production data, deployment, or connected/live behavior changes. #507 still owns notice/retention approval, scoped authorization, protected authority, isolated staging, backend-first deployment/readback, the availability flip, connected publication, and live proof. Use no real name or photo. |
| Source-only saved-photo response containment for RISK-042 | MEMBERS-DIRECTORY-001K #639 makes the preserved connected Account service admit an inbound returned saved photo only when the existing exact object, image/webp, 256×256 dimensions, and UUID version contract contains canonical base64 that decodes to 12 through 65,536 bytes with RIFF at bytes 0–3 and WEBP at bytes 8–11. Any failure returns only Invalid member directory response. without rendering or logging raw bytes, a provider value, or caught detail. A real Account component-to-real-service test with only firebase/functions mocked proves mislabeled bytes stop before a saved-thumbnail image or data URL. Structurally admitted but browser-undecodable bytes retain the version-scoped, byte-free Photo unavailable fallback and enabled Remove current saved photo action without another callable. Outbound JPG, PNG, and WebP admission and exact bytes remain unchanged. |
RIFF/WEBP markers are structural admission, not full decoding, safe image proof, authenticity, server-state proof, authorization, or provider acknowledgement; header-shaped hostile bytes can still reach the browser decoder, so the byte-free render fallback remains required. The source-controlled availability value stays false, and live #623 remains inert. No Function, Rule, index, schema, package, workflow, photo query, facial recognition, matching, embedding, similarity, biometric processing, Firebase or provider configuration, account, sign-in, production data, deployment, or connected/live behavior changes. #507 still owns notice/retention approval, scoped authorization, protected authority, isolated staging, backend-first deployment/readback, the availability flip, connected publication, and live proof. Use only generated non-face test bytes; do not inspect or upload a real photo. |
| Source-only search-focus containment for RISK-042 | MEMBERS-DIRECTORY-001L #641 gives the preserved connected People finder one exact-operation focus intent after a valid query and request ID admit pending, and only when the persistent name input or Search button owns focus. Current result-card, empty-result, and fixed-failure settlement consumes the intent after render. The already-focused origin is left alone; body, root, absent, or disconnected focus returns to the same now-enabled origin; and another connected element focused during the request retains focus. Programmatic or outside-focused submit creates no intent. Editing, Clear, local validation failure, request-ID failure, application or administrator change, unmount, and stale resolution or rejection clear or fail the guards. Generated-only tests cover both origins and all three outcomes, deliberate outside focus, retained-origin focus, local failures, Clear, context changes, unmount, one exact request, and the unavailable default. |
Programmatic focus is accessibility state, not authorization, search correctness, provider acknowledgement, audit proof, membership evidence, or live behavior. It stores no query, name, result, photo, account ID, request ID, or service value and creates no request ID, search, retry, Clear action, result, audit, service call, or data URL. Existing response/privacy bounds and name-only search plus human comparison of voluntary thumbnails remain unchanged; never add a photo query, face recognition, matching, embedding, similarity, biometric processing, totals, export, or roster authority. The source-controlled availability value stays false, and live #623 remains inert. No data movement, page structure, service/server contract, Function, Rule, index, Firebase or provider configuration, account, sign-in, production data, deployment, or connected/live behavior changes. #507 still owns notice/retention approval, scoped authorization, protected authority, isolated staging, backend-first deployment/readback, the availability flip, connected publication, and live proof. Use no real name or photo. |
| Source-only rejected-removal focus containment for RISK-042 | MEMBERS-DIRECTORY-001M #643 gives the preserved connected Account branch one exact-operation pending focus intent only after request-ID creation and admitted pending removal, and only when Remove current saved photo owns focus. A definitive rejection plus current successful authoritative readback transfers the matching pending intent to the result ref for one ready render. The destination is a surviving Remove action, otherwise the enabled Save action for the exact current ready draft, otherwise the persistent file input for no, reading, or not-yet-ready draft. Body, document-root, absent, or disconnected focus returns to that current destination; an already-focused destination is left alone; any other connected focus selected during pending is preserved. A surviving Remove alone describes the fixed rejection. If Remove disappears, the fixed alert stays standalone and is not attached to Save or the input. Generated-only tests cover all destinations, native focus eviction, retained and deliberate outside focus, request-ID failure, both readback-failure classes, application/account changes, unmount, zero extra calls, and the unavailable default. |
Programmatic focus is current-interface accessibility state, not provider acknowledgement, deletion proof, reconciliation, authorization, audit evidence, or proof that the rejected removal succeeded. The focus intent contains only the mounted application-and-account lifetime and exact mutation-operation symbol, with no photo bytes, profile, request ID, revision, provider value, or error, and creates no request ID, callable, retry, mutation, draft, data URL, confirmation, audit, provider action, or data action. Existing confirmed-success and #637 Reload focus behavior remains. The source-controlled availability value stays false, and live #623 remains inert. No data movement, page structure, service/server contract, Function, Rule, index, Firebase or provider configuration, account, sign-in, production data, deployment, or connected/live behavior changes. #507 still owns notice/retention approval, scoped authorization, protected authority, isolated staging, backend-first deployment/readback, the availability flip, connected publication, and live proof. Use no real name or photo. |
| Source-only confirmed-photo focus containment for RISK-042 | MEMBERS-DIRECTORY-001N #645 gives the preserved connected Account branch separate pending and result confirmed-photo focus refs. Only a successful request-ID creation followed by admitted pending upload or removal may record an intent, and only while the exact Save profile photo or Remove current saved photo initiating control owns focus. The intent contains only the mounted application-and-account lifetime, exact operation symbol, and upload-or-remove action. A successful mutation and current successful authoritative readback transfer only a matching lifetime, operation, and action for one ready render. Confirmed upload targets the persistent file input. Confirmed removal retains the existing surviving Remove, exact current render-ready Save, then persistent file-input priority. Body, document-root, absent, or disconnected focus returns to the current destination; an already-focused destination is left alone; every other connected focus selected during mutation or readback is preserved. Generated-only tests cover both actions, all removal destinations, native focus eviction, redundant-focus avoidance, deliberate outside and in-profile focus, programmatic invocation, request-ID failure, definitive rejection, unknown and readback failure, application/account changes, unmount, one-shot consumption, exact call/byte/revision behavior, and the unavailable default. |
Programmatic focus is current-interface accessibility state, not provider acknowledgement, upload or deletion proof, reconciliation, authorization, audit evidence, or connected-live proof. The focus intent stores no name, profile, revision, request ID, photo bytes, data URL, provider value, response, or error and creates no request ID, callable, retry, mutation, draft, data URL, confirmation, audit, provider action, or data action. #643 rejected-removal focus/error ownership and #637 Reload recovery remain separate. The source-controlled availability value stays false, and live #623 remains inert. No data movement, page structure, People finder, visibility behavior, service/server contract, Function, Rule, index, schema, Firebase or provider configuration, account, sign-in, production data, deployment, biometric processing, or connected/live behavior changes. #507 still owns notice/retention approval, scoped authorization, protected authority, isolated staging, backend-first deployment/readback, the availability flip, connected publication, and live proof. Use only generated non-face test images; never add a photo query, facial recognition, matching, embedding, similarity, biometric processing, roster authority, or membership proof. |
| Source-only visibility-focus containment for RISK-042 | MEMBERS-DIRECTORY-001O #647 gives the preserved connected Account branch separate pending and result visibility focus refs plus the exact persistent officer-finder checkbox ref. Only successful request-ID creation followed by admitted pending visibility may record an intent, and only while that checkbox owns focus. The intent contains only the mounted application-and-account lifetime and exact operation symbol. Confirmed success plus a current successful authoritative profile read, or definitive rejection plus a current successful confirming read, transfers only a matching intent, and one ready render consumes it before checking the destination. Body, document-root, absent, or disconnected focus returns only to the same connected and enabled checkbox; retained checkbox focus is left alone; another connected outside or in-profile focus is preserved; and an ineligible returned off checkbox consumes the result without focus because it is disabled. Generated-only tests cover requested on, requested off, changed-again state, definitive rejection and error association, native focus eviction, redundant-focus avoidance, deliberate connected focus, programmatic invocation, request-ID failure, unknown and both readback-failure paths, name-ineligible disablement, application/account changes, unmount, stale mutation/readback completion, one-shot consumption, exact call counts, and the unavailable default. |
Programmatic focus is current-interface accessibility state, not provider acknowledgement, saved-setting proof, mutation correctness, reconciliation, authorization, audit evidence, membership proof, or connected-live proof. The focus intent stores no query, name, profile, revision, request ID, result, error, provider value, photo byte, or data URL and creates no request ID, read, mutation, retry, audit, result, draft, photo byte, data URL, provider action, or data action beyond the already admitted operation. #637 Reload recovery, #643 rejected-removal focus, and #645 confirmed-photo focus remain separate. The source-controlled availability value stays false, and live #623 remains inert. No data movement, element structure, page topology, People finder, service/server contract, Function, Rule, index, schema, Firebase or provider configuration, account, sign-in, production data, deployment, publication, biometric processing, or connected/live behavior changes. #507 still owns notice/retention approval, scoped authorization, protected authority, isolated staging, backend-first deployment/readback, the availability flip, connected publication, and live proof. Use no real name or photo; never add a photo query, facial recognition, matching, embedding, similarity, biometric processing, total, export, roster authority, or membership proof. |
| Source-only successful-Reload result and focus containment for RISK-042 | MEMBERS-DIRECTORY-001P #649 gives every valid explicit current Reload whose guarded authoritative profile read succeeds one fixed first-in-ready-controls result, Profile photo and officer finder settings reloaded., with status, polite live, atomic, programmatic-focus, and scoped visible-outline semantics. It is separate from mutation confirmations and proves only that the current read completed. Separate pending and result ReloadFocusIntent refs contain only the mounted application-and-account lifetime and exact load symbol. The pending intent is armed only if the exact rendered Reload button owns focus at invocation, is bound to the exact next load, and transfers only on its matching current guarded success. One ready effect consumes the result before checking the exact lifetime, current load, and connected result target. It leaves retained result focus alone, restores body, document-root, absent, or disconnected focus, and preserves any other connected outside or in-profile focus. A valid programmatic or unfocused Reload shows the result without moving focus. Initial/background success shows no Reload result; failure clears the new refs and retains #637 replacement-Reload focus; application/account change, unmount, new load, obsolete completion, and later rerender are inert. Generated-only tests cover generic and uncertain recovery, fixed copy and placement, live semantics, visible focus style, lost-focus restoration, redundant-focus avoidance, deliberate outside and in-profile focus, unfocused invocation after its origin disappears, initial load, failure and retry, one-shot consumption, context change, unmount, exact read counts, zero mutations, and the unavailable default. |
The result and programmatic focus are current-interface accessibility feedback, not proof that an earlier mutation succeeded or failed, provider acknowledgement, saved-setting correctness, reconciliation, authorization, audit evidence, membership proof, or connected-live proof. The intent stores no UID, query, name, profile, revision, request ID, result, error, provider value, photo byte, or data URL and creates no request ID, extra read, mutation, retry, audit, draft, photo byte, data URL, provider action, or data action. Existing upload, removal, visibility, People-finder, and #637 failure-focus contracts remain separate. The source-controlled availability value stays false, and live #623 remains inert. This adds one visible page-structure node but changes no data movement, permission, ownership, service/server contract, Function, Rule, index, schema, Firebase or provider configuration, account, sign-in, production data, deployment, publication, or connected/live behavior. #507 still owns notice/retention approval, scoped authorization, protected authority, isolated staging, backend-first deployment/readback, the availability flip, connected publication, and live proof. Use no real name or photo; never add a photo query, facial recognition, matching, embedding, similarity, biometric processing, total, export, roster authority, or membership proof. |
| Immediate Product-binding containment part of RISK-031 | PAY-PRODUCT-001A is tracked in live #353. One dependency-free projection is used by the paid race and Shop checkout paths. A present stored Product link must be an own primitive non-empty string from 1 through 255 JavaScript code units and is copied without trimming, conversion, character restrictions, or prod_ inference. Only a genuinely missing own field retains the compatibility Product-create path. Before any mapping write, a resolved Product must provide a bounded custom ID, exact Product kind, expected declared mode, and an installed-SDK 2xx response marker. Malformed stored links stop before token, registration/order identifier, Product-link or business-record write, or Stripe work; earlier access and request-count checks and their safety-counter writes may already have run. Malformed created results stop after at most one Product attempt but before mapping, Checkout Session, or business-record writes. |
This structural containment does not prove provider origin, Stripe account ownership, intended catalog identity, metadata binding, Product status, price, dispatch, delivery, or reconciliation. A rejected create result may leave an orphaned Product; do not retry automatically. Anonymous clean-missing creation remains concurrency-prone and reachable from public traffic. Complete #113 inventory/disposition, authenticated idempotent catalog synchronization, Product-specific plan/pre-send/result/lost-acknowledgement/reconciliation controls, isolated staging, protected Firebase deployment, and provider proof before live commerce. |
| Immediate current-handler containment part of RISK-003 | PAY-SESSION-001A is tracked in live #357. The current paid race and Shop handlers build one immutable expectation before the Session call, catch rejection without opening it, and immediately project only a closed installed-SDK result. A result must match declared test/live mode, payment/open/unpaid state, exact cents, USD, buyer email, exact closed metadata, exact callbacks, a mode-compatible bounded Session ID, one canonical HTTPS capability at the hard-coded default checkout.stripe.com origin, and an installed-SDK 200 marker. Only copied ID/URL values continue; records store the ID but never the URL. Invalid results create no registration/order record and return one fixed unknown-result message. The website makes rejection or a missing paid URL terminal for that page visit, retains the form, and blocks a second direct handler call. |
This is a narrow legacy compatibility stop, not trusted provider/account origin, deterministic business idempotency, dispatch/delivery proof, durable result evidence, payment proof, or adoption of the unused C4 chain. The Session call occurs first, so a rejected result may leave a payable orphan; earlier rate-counter, token/identifier, and lazy Product-mapping effects may remain. Reload, another tab/device, or a scripted caller bypasses the page lock. A configured Stripe custom checkout domain is blocked until #113 and a protected configuration boundary approve it. Complete PAY-002C/D persistence-first sagas, trusted C4 controller/result persistence, reconciliation, protected staging/deployment, provider readback, and exact website/Firebase/live proof. |
| Immediate pre-render browser containment part of RISK-003 | PAY-SESSION-001B is tracked in live #503. Each current public race and Shop page now keeps one component-local in-memory marker. After existing local checks admit a request, the handler sets that marker synchronously before analytics or its first Checkout promise can settle. A same-action or later submission on that mounted page is inert, including the gap before React renders the existing pending disabled button. Focused actual-route tests prove one service/analytics attempt across same-action and post-render repeats; preserve free-participant, volunteer-without-price-tier, and both paid-link navigation branches; and prove local waiver/native-disabled paths do not consume an attempt. | This browser marker is not a server boundary, durable idempotency key, provider dispatch/result record, business-state lock, payment proof, or reconciliation. It never releases during the mounted page visit because the admitted result must navigate or enter #357's terminal unknown-result state. Existing form controls other than the submit button remain editable. Reload, a remount, another tab/device, a script, or a direct service caller can bypass it. A same-mounted route change instead stays locked, and this slice does not fence an older pending result from that changed route. Complete PAY-002C/D persistence-first deterministic commands, durable replay/reconciliation, protected deployment, and exact website/Firebase/Stripe/live proof. |
| Immediate paid-late-registration containment part of RISK-012 | PAY-004C1 is tracked in live #359. After the existing access, configuration, commerce-admission, email, and amount-format checks, every positive admitted amount returns one fixed Paid late registration is not available result. The stop precedes registration-ID/token allocation, Firestore writes, Stripe construction, Product/Price/Payment Link calls, URL return, and logs. Exact zero preserves the existing local result. The Admin screen removes amount/tier entry and Payment Link prompting, sends exact zero, and says paid late registration is NOT AVAILABLE YET. |
This source containment is not a safe paid-late-registration implementation, an approved free/comp decision, payment or membership proof, Firebase/website deployment, or legacy-link cleanup. The zero path still writes a local record marked paid without Stripe. Complete private legacy inventory/deactivation/reconciliation under #113/PAY-004C, build one-off persistence-first Checkout with the normal verified payment lifecycle, and prove protected Firebase/website/provider release before officer use. |
| Immediate input-containment part of RISK-020 | PAY-005A1 is tracked in live #200. Both existing admin refund Functions share one non-coercing validator: a partial amount must be a primitive positive safe integer below the stored positive safe-integer original cents. Invalid caller or stored values stop before Stripe construction/refund creation and Firestore mutation; valid partials always include the exact amount; only the explicit full action omits it. Provider exceptions expose no detail and return a fixed result-not-confirmed, do-not-retry, and escalate message. Focused tests cover both race and merchandise paths, fixed errors, valid boundaries, provider failure, and malformed/equal/over/non-finite/coerced inputs. |
This immediate guard does not prove the remaining refundable Stripe balance, cumulative prior refunds, command identity/idempotency, concurrency safety, pending/event-finalized state, capability/MFA/recent-auth, approved policy, Firebase deployment, Stripe configuration, or live behavior. An unconfirmed provider result remains ambiguous and requires reconciliation, not a blind retry. Complete PAY-005A, its PAY-002/PAY-003 dependencies, AUTH-003, and every listed residual gate before authorizing production refunds. |
| Immediate resolved-result containment part of RISK-020 | PAY-005A2 is tracked in live #204. Both existing refund Functions build one immutable expectation before Stripe from a bounded stored PaymentIntent, positive original cents, exact usd, action, and admitted partial cents. Only a plain succeeded Refund with a bounded ID, exact primitive PaymentIntent/currency binding, and permitted amount returns a frozen minimal projection. Partial cents must match exactly; explicit full accepts the positive full-remaining amount up to the original total. Invalid stored targets stop before Stripe. Thrown, malformed, mismatched, pending, action-required, failed, cancelled, unknown, or post-provider local-write failures return the same fixed ambiguous message and never return local success. A rejected Stripe result causes no local success write attempt; a later local-write error leaves the record state unknown. The audit uses only the validated refund ID and actual returned cents. |
The provider call precedes response validation and the local write, so an unconfirmed provider result may already have moved money. A local write that reports an error may have committed or may remain unchanged, especially when its acknowledgement is lost. Do not retry either outcome; reconcile it. This slice adds no durable pre-send operation/ID, idempotency key, remaining-balance retrieval, cumulative amount, concurrency fence, webhook-finalized state, capability/MFA/recent-auth, approved policy, provider rehearsal, Firebase deployment, or live proof. Complete PAY-002B2C3B/C, PAY-005A, PAY-003B, AUTH-003, reconciliation, and protected release before authorizing production refunds. |
| Foundation only for parts of RISK-011, RISK-020, RISK-021 | PAY-002A1 is tracked in live #161. The source-only child defines versioned pure payment, registration, fulfillment, confirmed-refund, and per-dispute structural reducers; fixed cross-dimension review/rejection; and a deterministic synthetic legacy classifier/aggregate report with no IDs. Orders/registrations carry no singular canonical dispute state. Forbidden regressions fail closed, refunds/disputes do not overwrite payment state, and paid_after_cancellation remains paid plus cancelled plus review rather than becoming a payment enum. |
This does not yet mitigate those risks in a runtime. An unchanged enum does not prove duplicate Event/command/evidence, authorization, amount monotonicity, provider state, or club policy. No endpoint imports the library and no business record is read/written or migrated. PAY-002B1 #163 supplies pure command/attempt keys; PAY-002B2A #165 supplies only unused registration/audit persistence; PAY-002B2B #169 supplies unused lease/fence/terminal-commitment source and tests without provider permission; PAY-002B2C1 #173 supplies only unused lease-bound attempt-1 plan commitments/audit; PAY-002B2C2 #182 supplies only unused pre-send evidence/audit and the exact persisted retry cutoff; PAY-002B2C3A #184 supplies only an unused pure closed evidence classifier; PAY-002B2C3B #206 supplies only unused immutable candidate evidence/audit persistence after the cutoff and expired-lease gate; PAY-002B2C3C #226 supplies only an unused fresh-lease authorization record/audit for one later attempt; PAY-002B2C4A #232 supplies only the unused immutable attempt-2 plan/audit under that exact authorization and a current active lease. PAY-002B2C4B #238 supplies only the unused attempt-2 pre-send pair, a complete authorized-plan commitment version 2, fixed 23-hour deadline, and post-transaction clock gate. C4B's fixed send_permitted is retry-safety classification only: it makes no Stripe request, records no result, and grants no caller or business authority; PAY-002C/D persistence-first checkout; PAY-003B Event/version/evidence adoption including same-state amount changes and separate dispute records; PAY-004/PAY-005 cancellation/refund operations; MERCH-002/LEGAL-001 return/policy gates; #113 and a later migration child real inventory/dry-run. Firebase/Stripe deployment and live behavior remain unproven. |
| Foundation only for retry/duplication parts of RISK-003 and RISK-020 | PAY-002B1 is tracked in live #163. The pure source child defines a canonical UUID v4 boundary, a domain-separated caller/environment command hash, a command-type plus canonical frozen-payload fingerprint, and a deterministic environment/mode/operation/command/provider-attempt Stripe key. Invalid production/test or non-production/live pairs fail. Raw caller, UUID, operation, and payload values never appear in outputs. PAY-002B2C1 is tracked in live #173 for an immutable lease-bound attempt-1 plan and deterministic audit whose account, canonical parameters, and B1 key are stored only as command-bound commitments. PAY-002B2C2 is tracked in live #182 for a separate atomic pre-send marker/audit, a complete command-bound C1-plan digest in both partners, a post-transaction lease/deadline freshness check, and persisted 23-hour attempt-1 cutoff. PAY-002B2C3A is tracked in live #184 for the pure exact attempt-1 reconciliation evidence policy. PAY-002B2C3B is tracked in live #206 for the deterministic candidate record/audit pair bound to complete C1/C2 commitments and an observed expired lease. PAY-002B2C3C is tracked in live #226 for the deterministic later-attempt authorization/audit pair bound to that exact candidate, a matching closed transition commitment, and a fresh active lease. PAY-002B2C4A is tracked in live #232 for the deterministic attempt-2 plan/audit pair bound to the exact C3C authorization and current active lease. PAY-002B2C4B is tracked in live #238 for its atomic pre-send pair, explicit version-2 commitment over every C4A field and authorization provenance, immutable 23-hour deadline, and second trusted-time check. | Hashes and commitments are pseudonymous server-only equality evidence—not anonymization, caller authorization, configured-account proof, current business-state proof, durable response replay, or provider-execution proof. No endpoint imports these libraries. PAY-002B2A/#165 registers only a command/audit pair. PAY-002B2B/#169 adds a separate fixed-duration lease, holder fingerprint, monotonic fence, terminal commitment, and transition audit without provider permission. C1 does not permit a send. C2's fixed send_permitted is only a retry-safety classification under the exact lease/fence/plan; it is not caller authorization, configured-account proof, a Stripe call/outcome, or response replay. C2 makes no provider request and never advances attempt 1. A changed/replaced plan fails its stored digest. Lease/deadline equality, later time, rollback, or missing/unreadable paired time returns reconciliation_required. C3A returns only fixed existing_attempt_found, new_attempt_candidate, or reconciliation_required results from closed enum evidence. C3B can persist only an exact candidate at or after both the stored cutoff and current lease expiry; early/active candidates write nothing, exact retry is read-only, and changed/malformed/orphan/future evidence fails closed. C3C accepts only the two complete safe tuples under a later current lease; its opaque transition commitment is command-bound but is not proof of current business state. Its fixed requires_plan_binding result creates no attempt-2 plan, send marker, or provider call. C4A requires that exact authorization plus a current active lease and preserves attempt-1 account/mode/API/operation/endpoint/parameters in its immutable attempt-2 plan. Its fixed requires_pre_send_evidence result creates no send marker or provider call. C4B revalidates the complete chain and current lease, then returns fixed send_permitted only before both the captured lease expiry and stored deadline; equality, later time, rollback, or unreadable time reconciles. It creates no provider call, attempt 3, business write, or result. Only the Checkout Session parts of PAY-002C/D may adopt C4A/C4B. Product/Price creation, Session expiry, refunds in PAY-005, and other provider operations need separate operation-specific plan, pre-send, result, and reconciliation boundaries. All runtime adoption also requires trusted business transactions, endpoint schemas, and failure-boundary tests. Deployment, provider configuration, and live behavior remain unproven. |
| Foundation only for attempt-2 result-shape parts of RISK-003 and RISK-020 | PAY-002B2C4C1 is tracked in live #246. The unused pure classifyAuthorizedStripeCheckoutResultEvidence API accepts only a primitive, length-bounded, canonical JSON string encoding the reported exact 16-field closed assertion envelope for attempt 2 and checkout_session_create; deterministic reserialization must match. Parsing creates the ordinary record inside the module. Non-string objects fail before property access, and a hard-coded Node 20 baseline rejects Object.prototype pollution present before module load or introduced later. One complete reported direct-response shape returns fixed frozen unbound_result_candidate; all 46 alternate enum values reconcile, and malformed, hostile, oversized, or non-canonical serialized values throw one fixed redacted error. The API parses no Stripe object and echoes no supplied value. |
The candidate is deliberately unbound. C4B proves only pre-send evidence, not dispatch. #246 contains no dispatch/idempotency/result binding, projector, journal write, result persistence, Session ID/URL, network/provider call, runtime import, or business transition. A later runtime boundary must use trusted dispatch evidence, a reviewed raw-result projector/provider validator, recoverable server-only persistence under approved retention, and business validation before any Session can be returned or state can advance. Source/tests/merge do not prove Firebase deployment, Stripe configuration, production data, website publication, or live behavior. |
| Test-only observation for later attempt-2 projection parts of RISK-003 and RISK-020 | PAY-002B2C4C2A is tracked in live #275. A Node 20 contract suite uses installed stripe 14.25.0 through its exported but experimental/unstable HttpClient interface and a synthetic in-memory fake. This is an installed-version upgrade gate, not a provider contract. It pins selected Session own data and the same fake raw-response object that the SDK attaches as own non-enumerable, non-writable lastResponse; SDK-added header observations are separate from the fake-supplied statusCode and do not model an exhaustive production response. Synthetic unknown/PII/metadata/client-secret fields and unvalidated fragment-bearing or custom .invalid URLs survive; raw JSON omits lastResponse but retains unsafe fields. A normal error envelope rejects, while bare non-2xx may resolve. |
Every observed body/header and resolve/reject result is controlled and forgeable. The suite proves no Stripe origin/account, dispatch, request delivery, idempotency binding, plan/send binding, application environment, business clock, payment, capacity, inventory, or business truth. Never enumerate, spread, clone, stringify, log, or persist a whole Session; no dynamically captured raw value may enter a snapshot/log/artifact. C4C2B1/#280 now adds only the unused untrusted allowlist projection; C4C2B2 still must control the runtime call and raw memory-only comparisons, validate the URL, bind trusted plan/send/dispatch/business/time evidence, and perform approved persistence. Persist only approved server evidence and never the Checkout URL; retrieve by stored Session ID and revalidate before URL replay. #275 adds no production source/provider call or external/live proof. |
| Source-only projection foundation for attempt-2 parts of RISK-003 and RISK-020 | PAY-002B2C4C2B1 is tracked in live #280. The unused pure projector reads only named own data descriptors from an untrusted Session-like object and its SDK-attached lastResponse; proxies, accessors, inherited selected fields, wrong descriptor flags, coercion objects, and malformed technical values fail with one fixed redacted error. Unknown fields are ignored without enumeration. The fresh frozen null-prototype result fixes schema 1, provider stripe, and operation checkout_session_create, then contains technically bounded server-only Session business primitives and only fixed URL/response categories. Raw URL, request ID, idempotency key, Stripe account ID, API-version text, metadata, contact data, client secret, callback URLs, body, headers, socket, and raw response never enter the result. |
untrusted_checkout_session_projection remains forgeable, server-only, and unsafe to log, expose, or persist. Its fixed provider/operation labels are not provider-origin or execution proof. A bounded Session ID is not persistence authorization; a bounded HTTPS URL category does not approve its host, callbacks, or fragment; response categories do not prove provider origin/account, dispatch/delivery, key use, plan/send/configuration binding, current time, payment, inventory/capacity, retention, or business state. #280 adds no SDK/provider call, C4C1 positive mapping, journal/runtime import, persistence, or external/live proof. C4C2B2 must control the SDK promise, compare raw memory-only facts with trusted C4A/C4B/configuration evidence, validate the raw URL, bind business/time facts, persist only approved evidence, and return a current URL without logging or storing the URL. |
| Source-only transport-comparison foundation for attempt-2 parts of RISK-003 and RISK-020 | PAY-002B2C4C2B2A is tracked in live #285. The unused pure classifier revalidates the exact frozen null-prototype #280 projection and one exact schema-1 capsule of own-data, non-Proxy, non-accessor memory-only primitives. Exact API-version 2023-10-16, idempotency-key, and optional-account equality plus safe projection categories returns only a fresh fixed untrusted_transport_binding_candidate; valid mismatch or insufficient evidence returns fixed reconciliation_required; malformed input throws one fixed redacted error. Raw Session ID, URL, request ID, API/key/account text, source objects, personal data, and secrets never enter output, logs, persistence, or artifacts. |
Equality is not Stripe origin/account control, same-SDK-promise provenance, dispatch/delivery, C4A/C4B/configuration binding, idempotent request proof, approved URL/callback, business/time state, retention/persistence, replay, C4C1 mapping, runtime adoption, deployment, or live proof. Both account values missing proves only matching absence. The word expected supplies no trust. A future controller must capture one synchronous call result, derive expectations from the active plan and protected configuration, validate the memory-only URL, attach current business/time evidence, and persist only approved server evidence. |
| Foundation only for registration/audit parts of RISK-003, RISK-020, and RISK-032 | PAY-002B2A is tracked in live #165. The unused server-only transaction creates one immutable checkoutRequests/{commandKeyHash} registered record and deterministic revision-1 auditEvents partner atomically. Exact retries are read-only. Same-key type/schema/payload conflicts and corrupt, orphan, future, or unexpected stored pairs fail closed without repair. Environment/caller-scope changes derive different B1 keys; a stored mismatch under one key is corruption, not a cross-scope lookup. Fixed results expose no raw identity, UUID, payload, hash, path, or Firestore detail. CI-001B3 #167 runs its dedicated exact demo-project/opt-in suite as a named hosted job and protected-release prerequisite. |
Registration is neither authorization nor execution permission. #169 keeps the root and revision-1 audit immutable, places lifecycle state in the separate server-only subcollection, fails closed on malformed partners, and proves in synthetic tests that stale workers cannot finalize. The hosted emulator result is still synthetic source proof, not deployment or provider proof. No TTL is safe: #110 must approve retention and a server-only tombstone or equivalent durable duplicate barrier before deletion. Source/tests/merge do not prove Firebase deployment, Stripe configuration, or live behavior. |
| RISK-008 | ABUSE-001A1 is tracked in live #159. The source-only slice replaces the browser reCAPTCHA v3 constructor with the pinned Firebase SDK's Enterprise provider and preserves local/test and initial capability-callback shutdown, missing-key behavior, token refresh, Analytics shutdown, and fixed redacted failure diagnostics. #443 separately adds only the source/test Strava clean-page readiness handoff. Synthetic tests use no real key or provider call. | No Enterprise key, allowed-domain policy, provider console setting, protected build variable, configured provider-backed token readiness or acceptance evidence, staged metric, callable enforcement, website/Firebase publication, or live behavior is configured or proven. Release 29254280177 failed closed before build because the public site-key variable was absent. Complete #113/#133, ABUSE-001A2, #136/WEB-001, staged metrics, and provider readback before enforcement or a live claim. |
| RISK-017 and RISK-018 | PAY-001A is complete in #157. PAY-001B1 is tracked in live #219: the website sends only the active field set and omits a volunteer tier; the race callable then adopts an exact bounded root/runner parser before Firestore and matches answers to the selected bounded server event/volunteer field schema before rate limits, role/capacity work, token creation, writes, or Stripe. Unknown fields, malformed prototypes/accessors/proxies, unsafe Unicode, oversized values, invalid participant/volunteer/tier combinations, non-literal waiver acceptance, malformed selected definitions, duplicate keys/options, missing/unknown/wrong-type answers, and invalid select choices return one fixed non-identifying failure. Opaque event IDs remain byte-identical and are encoded before callback URL use. PAY-001C1B is tracked in live #389: the merchandise callable strict-parses its exact request root and buyer before Firestore, rate limits, or Stripe, then matches the size and color selections against the stored catalog option lists before the price projection or Stripe, requiring one in-list selection per offered dimension, rejecting a selection for an absent dimension, and returning one fixed non-identifying failure that does not echo the field; the website sends only offered options. PAY-001C1C is tracked in live #393: stacked on C1B, the callable accepts an optional bounded quantity (absent means one unit; a present value must be a safe integer in [1, MAX_MERCH_QUANTITY] or it is the same fixed denial before any side effect) and persists an immutable server-owned price snapshot — the unit price is taken only from the stored product, the quantity is re-validated, and the total is recomputed under safe-integer and Stripe-minimum guards, collapsing any fault to unavailable — while the order stores the total amountCents the webhook money reconciliation requires, Stripe charges unit_amount × quantity, and the Stripe metadata is unchanged. |
The browser projection is compatibility hygiene, never authority. This is source/test evidence, not deployment or production protection. PAY-001B2 still must persist immutable event-field, price, and waiver snapshots and prove compatibility in protected staging. PAY-001C1C adds the merchandise price-snapshot and multi-quantity source contracts; a browser quantity selector, quantity-in-metadata reconciliation (PAY-005), the rest of PAY-001C (cart/multi-line, inventory, canonical variant IDs), and PAY-001D must still adopt their exact schemas. Request IDs, persistence-first records, capacity holds, provider idempotency, provider configuration, and live behavior remain open. Safe validation/log primitives do not replace approved privacy purpose, retention, access, monitoring, or provider controls. |
| RISK-001, RISK-002, RISK-034 | PAY-003A #101 source merged as 87bd1210575882cdd06214bcb98ed38ce00f71c2, adding durable Stripe Event deduplication, transactional transition/processed marking, paid/amount/currency/Checkout-mode/reference-shape and explicitly configured livemode checks, async outcomes, monotonic refund/terminal guards, quarantine, generic signature failures, and a webhook-secret-only binding. |
Firebase was not deployed. Complete CONFIG/PAY-001/PAY-002 contracts, metadata schema allowlist/migration, canonical reducers/reservations/outbox, retry/dead-letter/TTL/alerts, emulator integration, Stripe test-mode rehearsal, protected deploy, and reconcile. |
| RISK-006, part of RISK-024/RISK-032 | #100 merged at a7fc301e85b0aeabe396e771faea21d3fc8e7b2b. OAuth secrets and server-owned financial/operational collections are denied; current catalog writes are restricted to inert drafts/display fields. |
Firebase deployment skipped. Stage/deploy the exact Rules revision under #105, verify synthetic deny behavior, then move remaining catalog mutation to scoped APIs under ADMIN-001. |
| RISK-009 | #99 preserves same-origin path, query, and fragment state through the Pages fallback, clears temporary state, rejects unsafe targets, and suppresses monitoring startup on initial capability callbacks. #126 adds the standalone suite as the named blocking Run SPA callback safety tests hosted step. |
Keep exact hosted evidence on #126; verify exact website publication and later run approved test-mode Stripe/Strava callbacks; WEB-001 still owns controlled hosting. |
| RISK-010 | #99 uses a fully synthetic demo-mprc-local configuration, connects Functions/Auth/Firestore to loopback in development/test, routes the direct CSV Function URL locally, fails startup on connector errors, and completed a demo-only three-port CLI smoke. |
Current optimized previews still target production; provider calls are not isolated by Firebase. Add protected staging/config and test-provider/sink evidence before end-to-end flows. |
| Test-only foundation for RISK-010 and RISK-035 | TEST-001A1 is tracked in live #175. The Functions Jest setup installs a fixed-error, loopback-only guard around supported Node outbound transports and rejects any supplied production-like project/origin/emulator host or live Stripe mode/key. Shared deterministic deeply frozen fixtures use reserved values, and the existing webhook suite adopts one synthetic signed-payload helper. | This is a test-process boundary, not a runtime sandbox. It does not intercept every possible native/add-on transport, isolate an emulated Function process, configure a test provider, scan emitted artifacts, deploy Firebase, or prove live behavior. TEST-001A2 #177 separately adds the bounded artifact scrubber and named CI/release gate; later TEST-001 children must add emulator sagas and isolated staging evidence. |
| Emitted-test-output part of RISK-035 | TEST-001A2 is tracked in live #177. Its source adds a dependency-free read-only scanner for explicit narrow artifact roots, bounded deterministic traversal, fixed redacted failures, deliberate synthetic allow/deny tests, the exact Test artifact scrubber CI job, and that job in both protected-release CI rechecks. Current CI still uploads no test artifact. |
This scanner covers a documented bounded set of recognizable patterns; green is not a general data-loss-prevention guarantee or proof that every unknown/obfuscated value is absent. It does not inspect source, runtime logs, website builds, provider responses, production data, or an operating-system sandbox, and it does not sanitize a rejected file. Every later report producer must invoke the scanner before upload, and exact hosted evidence remains separate from source and local-test evidence. |
| Configuration part of RISK-001/RISK-010 | CONFIG-001A #149 merged through PR #150 as 2c62b8dd, requiring an exact environment/origin/expected Stripe mode, key-mode compatibility on the four key-bound Functions, no production-origin fallback, and no Stripe API key on webhook/mail. CONFIG-001B1 #151 adds exact COMMERCE_ENABLED parsing. A false ceiling denies new commerce before Firestore; otherwise new-commerce commands and every refund perform a fresh strict server-only global/domain/resource admission read. Missing controls/resource flags mean disabled; fixed errors expose no supplied value; Rules tests deny browser control; webhook/mail remain independent. |
Neither source result configures or deploys Firebase/Stripe. B1 cannot stop already-admitted commands or existing Sessions/Payment Links and has no safe operator writer. CONFIG-001B2, PAY-002/PAY-004/PAY-005, and #105/#133/#136 retain protected control, expiry/reconciliation, staging, deployment, provider readback, and drill. No live payment or email is authorized. |
| Part of RISK-007 | AUTH-001A #98 was merged through PR #107 as ce22c110f2132b157bd8a0d43b065585e0b43cb5; it rejects unverified targets at the two existing role-grant endpoints. AUTH-001B #196 merged as a8801770e97cf21d81f3307f5893734115140d8f and adds the companion Rules boundary. AUTH-001C #209 tracks one dependency-free decoded-token policy at the current Functions role consumers: shared admin guards, member-only/member-price checkout decisions, and CSV export. Exact email_verified === true plus the existing exact role is required; profile/request substitutes and malformed or hostile claim representations deny before business side effects. |
None of these slices proves Firebase deployment or live access. Verification mirror/refresh, demotion propagation, audit, authoritative membership, capabilities/MFA/recent-auth, safe roster projection, and legacy-sync retirement stay open. Verification alone never grants a role. |
| AUTH-002A1 / RISK-007 | AUTH-002A1 #270 contains only the legacy membership-sync Function's unexpected outer-failure log. Source now emits one fixed member_role_update_failed argument and the existing generic HTTP 500 result without retaining, inspecting, coercing, returning, or storing the caught value. A focused hostile-value test proves even a throwing message getter is untouched; the ordinary success and fixed per-member result paths remain unchanged. |
This source-only containment does not make the legacy endpoint safe. RISK-023's shared static key, public endpoint, missing caller identity, replay control, App Check, and durable audit remain, as do the request/result email lists. Complete canonical retirement #117, protected deployment, caller and credential cutover, and negative live verification. No Firebase, caller, credential, role, production-data, or live change is proven here. |
| RISK-013 | PROMO-001 #102 sets both current Checkout creators to promotion entry off and automatic tax off. Exact payload and signed event tests require an explicit all-zero breakdown; malformed or nonzero discount, tax, or shipping data creates durable review evidence while definitive failed/expired Sessions still cancel. | Privately inventory pre-change Sessions/provider promotion settings, keep the guard through protected staging/deploy, verify provider and live state separately, and implement an approved authoritative discount/tax/shipping contract before ever enabling it. |
| Part of RISK-014 | SUPPLY-001A #127 removes the unused direct gpxparser dependency and 50 lockfile entries without changing any retained package version, source URL, or integrity hash. A fresh Node 20 install lowers the root production audit from 33 findings (3 critical, 9 high) to 22 (1 critical, 8 high), and the optimized production asset hashes remain identical. |
#192 completes the separately tested router patch. Remediate or time-bound the remaining root and Functions findings through the Firebase, Stripe, build-system, and scanning slices; do not use a forced audit upgrade. |
| Part of RISK-014 | SUPPLY-001B #192 pins react-router-dom and react-router 6.30.4 with @remix-run/router 1.23.3. The only lockfile identity changes are those three nodes. A fresh Node 20 production audit removes all three router findings and lowers the root total from 22 to 19 and high findings from 8 to 5. The declarative router opts into v7_startTransition and v7_relativeSplatPath; focused tests cover warning removal, double-slash normalization, wildcard navigation, login/account harnesses, and the standalone callback boundary. |
The remaining 19 root and 9 Functions production findings are not accepted as safe or fixed. Patch Firebase in separate compatibility slices, replace the unmaintained build chain, and add continuous scanning. Source/tests/merge do not prove website publication or live route behavior. |
| Part of RISK-014 | SUPPLY-001C #202 pins the Firebase browser SDK to 11.10.0 and its Rules helper to 4.0.1. A fresh Node 20 production audit falls from 19 findings (1 critical, 5 high) to 6 (0 critical, 3 high); the root production protobufjs path resolves to 7.6.5 and Firebase's root undici path is removed. The upgraded Rules helper first failed closed because Jest lacked fetch; a dedicated Node 20 environment exposes only the host's native web APIs, after which 348/348 Rules cases pass. Focused Firebase/Auth/App Check tests pass 71/71, the full frontend passes 198/198, and applicable Functions units pass 1,077/1,077. |
The remaining 6 root and 8 Functions production findings are not accepted as safe. An older dev-only protobufjs remains nested under firebase-tools; the build chain still accounts for the root findings. Reproducible clean builds change the main optimized bundle from main.fdd058b4.js to main.56edbdb7.js, +8.29 kB gzip (about 3.3%); overall build output grows 180 KiB (about 1.0%). Complete SUPPLY-001D/F/G through separate reviewable changes. This source/test result does not publish the website, deploy Firebase, change a provider, or prove live behavior. |
| Part of RISK-014 | SUPPLY-001D1 #212 uses an exact Functions-only override to resolve the sole installed form-data node from 2.5.5 to the patched 2.5.6 release without changing the direct Firebase Admin, Firebase Functions, or Stripe versions. The exact lock change updates that node's registry URL, integrity, and hasown constraint. A clean Node 20 production audit falls from 9 findings (1 high) to 8 moderate findings (0 high or critical). A focused test requires one exact installed/locked resolution and uses only synthetic multipart names to prove carriage return, line feed, and quote characters cannot create an extra header. |
The remaining 8 moderate Functions findings are not accepted as safe. They stay in the Firebase Admin dependency chain and require separate compatibility work rather than a forced audit upgrade. No application source directly imports form-data; this source/test result changes no endpoint or business logic, does not deploy Firebase or the website, and does not change provider or production data. |
| Part of RISK-014 | SUPPLY-001D3 #306 resolves the root development-tool copies from form-data 4.0.5 and 3.0.3 to reviewed 4.0.6 and 3.0.5 releases, hoists the 4.x copy, and updates shared hasown from 2.0.2 to 2.0.4 without changing package.json. A dependency-free Node test pins both public-registry identities, installed paths, development-only flags, parent ranges, and the shared helper; fixed synthetic field names and filenames prove quote, carriage-return, and line-feed escaping in both copies. The blocking frontend repository-safety command runs that test, and its independent workflow contract rejects omission or a swallowed result. A fresh 2026-07-16 UTC Node 20.19.5/npm 10.8.2 full audit falls from 68 findings (4 critical) to 67 (3 critical), while the production-only snapshot remains 7 findings (1 critical, 3 high, 2 moderate, 1 low). |
Both repaired form-data nodes are development-only and no application source imports them. Clean before/after installs produce the same 59-file, 19,044-KiB optimized build with aggregate SHA-256 f267e9a923da64996d93c4ba3db3fe411a6d8d5be8a2f130a50ac4f21c263869. The remaining root findings, including the separate production websocket-driver critical finding, are unresolved and not accepted as safe. This source/test change does not publish the website, deploy Firebase, change a provider, inspect production data, or prove live behavior. |
| Part of RISK-014 | SUPPLY-001D4 #310 resolves the sole root production websocket-driver node from 0.7.4 to the patched 0.7.5 release without changing package.json; both transitive parents (faye-websocket >=0.5.1, sockjs ^0.7.4) already admit it, and the exact lock change touches only that node's version, registry URL, and integrity while its http-parser-js, safe-buffer, and websocket-extensions ranges and >=0.8.0 engine stay fixed. A dependency-free Node test pins the exact installed and locked identity, the single hoisted resolution, the unchanged parent ranges, and the non-development classification; a synthetic public-API draft-75 regression proves that a length header above a one-byte maxLength now fails the connection closed at the configured bound instead of skipping unbounded bytes. A fresh 2026-07-17 UTC npm audit falls from 67 findings (3 critical) to 66 (2 critical), and the production-only snapshot falls from 7 findings (1 critical, 3 high, 2 moderate, 1 low) to 6 (0 critical, 3 high, 2 moderate, 1 low), because that single node carried both GHSA-xv26-6w52-cph6 (critical message corruption via protocol length headers) and GHSA-mp7j-qc5w-4988 (moderate resource-limit bypass). |
No application source imports websocket-driver, and clean before/after production builds on the same toolchain are byte-identical: the same 59-file, 19,148-KiB optimized output with aggregate SHA-256 e468df5ff26f82b21ef3b299f7f293390b64b88f1d0c6665369fdd415a132d7d, containing no websocket-driver, Faye, or draft-protocol signature. The remaining root and Functions findings are unresolved and not accepted as safe. This source/test change does not publish the website, deploy Firebase, change a provider, inspect production data, or prove live behavior. |
| Part of RISK-014 | SUPPLY-001D5 #355 resolves the sole root development-only shell-quote node from 1.8.3 to the patched 1.8.4 release without changing package.json; both transitive development-tool parents (launch-editor ^1.8.1, react-dev-utils ^1.7.3) already admit it. The exact lock change touches only that node's version, public-registry URL, and integrity while its development flag, license, funding, and >= 0.4 engine stay fixed. A dependency-free Node test pins the exact installed and locked identity, one hoisted resolution, absent direct manifest/override declaration, and unchanged parent ranges; synthetic public-API regressions prove that object operators containing line feed, carriage return, or either Unicode line separator now throw before a shell can receive them, while an admitted operator and ordinary safe quoting remain available. A fresh 2026-07-18 UTC Node 20.20.2/npm 11.8.0 full audit falls from 66 findings (2 critical) to 65 (1 critical), while the production-only snapshot remains 6 findings (0 critical, 3 high, 2 moderate, 1 low). |
No application source imports shell-quote, and clean before/after optimized builds on the same toolchain are byte-identical: the same 59 files and 19,435,972 bytes. The sorted per-file SHA-256 manifest has aggregate SHA-256 b68017a13d8cc88ee9132606b87db60c97852c091e2bd8f12375d6e3f9099a0b. The remaining root critical protobufjs finding is in the old development-only firebase-tools chain and requires a separate reviewed major upgrade; all other root and Functions findings also remain unresolved and are not accepted as safe. This source/test change does not publish the website, deploy Firebase, change a provider, inspect production data, or prove live behavior. |
| Part of RISK-014 | SUPPLY-001D6 #381 resolves the sole root picomatch 2.x node from 2.3.1 to the patched 2.3.2 release without changing package.json; the existing jest-util ^2.2.3 and micromatch ^2.3.1 ranges already admit it. The exact lock change touches only that node's version, public-registry URL, and integrity while its non-development classification, MIT license, >=8.6 engine, parent ranges, and the separate development-only tinyglobby picomatch 4.0.4 copy stay fixed. A dependency-free Node test pins those identities and ranges; deterministic public-API regressions prove that inherited POSIX names cannot inject host method text into a compiled expression while ordinary [[:alpha:]] matching remains compatible. A fresh 2026-07-19 UTC Node 20.19.5/npm 10.8.2 full audit falls from 65 findings (1 critical, 32 high) to 64 (1 critical, 31 high), and the production-only snapshot falls from 6 findings (0 critical, 3 high, 2 moderate, 1 low) to 5 (0 critical, 2 high, 2 moderate, 1 low), removing GHSA-3v7f-55p6-f55p and GHSA-c2c7-rcm5-vvqj from the sole affected node. |
No application source imports picomatch, and clean before/after optimized builds on the same toolchain are byte-identical: the same 59 files and 19,475,953 bytes. The sorted per-file SHA-256 manifest has aggregate SHA-256 57f2db4d6217de2f4e158d357aa928cb0af2b6a18238d61d3d866ffd53f234ba. npm ls picomatch --all still reports the pre-existing hoisted fdir `^3 |
| Part of RISK-014 | SUPPLY-001D7 #385 resolves the sole root, non-development yaml 1.x node from 1.10.2 to the patched 1.10.3 release without changing package.json; the existing cosmiconfig ^1.10.0, cssnano ^1.10.2, and nested cosmiconfig ^1.7.2 ranges already admit it. The exact lock change touches only that node's version, public-registry URL, and integrity while the three separate development-only yaml 2.x copies remain fixed. A dependency-free Node test pins all four locked identities and development classifications, the three unchanged parent ranges, and the installed root version. Its fixed 601-byte collection input runs in a bounded child process with a reduced stack and proves the patched public parseDocument API reports one parser-owned YAMLSemanticError instead of exposing raw RangeError failures. A fresh 2026-07-19 UTC Node 20.20.2/npm 10.8.2 full audit remains 64 findings (1 critical, 31 high, 19 moderate, 13 low) because vulnerable development-only yaml 2.7.1 and 2.8.0 copies remain; the production-only snapshot falls from 5 findings (2 high, 2 moderate, 1 low) to 4 (2 high, 1 moderate, 1 low). |
No application source imports yaml, and clean before/after optimized builds on the same toolchain are byte-identical: the same 59 files and 19,489,263 bytes. The sorted per-file SHA-256 manifest has aggregate SHA-256 0207e35601232cd74febaa6e72b9e2322f2159276f32d9ce046f8b050f0f40ed. The two affected development-only yaml 2.x copies and all other root and Functions findings remain unresolved and are not accepted as safe; they require separate compatibility review rather than widening this lock-only production patch. Source, tests, and any merge do not publish the website, deploy Firebase, change a provider, inspect production data, or prove live behavior. |
| Part of RISK-014 | SUPPLY-001D8 #453 removes GHSA-fv7c-fp4j-7gwp / CVE-2026-44728 from the root production build graph. The sole @babel/plugin-transform-modules-systemjs node moves from affected 7.25.9 to reviewed 7.29.7 through @babel/preset-env 7.26.9's unchanged ^7.25.9 range; package.json is byte-identical and no direct dependency or override is added. The exact lock closure updates 13 Babel records, updates two @jridgewell mapping records, and removes one now-unused mapping helper. A dependency-free test pins the sole installed/locked public-registry identity and closure. Its safe upstream-derived string-export fixture fails before the patch because Babel emits malformed identifier syntax and passes afterward with a computed string property. A fresh 2026-07-22 UTC Node 20.19.5/npm 10.8.2 full audit changes from 65 findings (2 critical, 33 high, 17 moderate, 13 low) to 64 (2 critical, 32 high, 17 moderate, 13 low); the production-only snapshot changes from 4 findings (3 high, 1 low) to 3 (2 high, 1 low), with no SystemJS advisory. |
MPRC does not offer a user-input compiler endpoint, but compiling trusted source only is not a reason to retain a high-severity build-chain defect. Clean exact-lock builds keep all 49 non-map files byte-identical at 9,975,968 bytes with aggregate SHA-256 3fa30c6d66827277432dcf7d42b0ead9e9fd543c9907571b092cfa43ba3f1e46. Only main.85f5094a.js.map changes: its 477 source paths and contents remain byte-identical, while updated name/mapping tables add 2,574 bytes. The separate Babel-core low and brace-expansion/minimatch high findings, old development-only Firebase CLI critical paths, and all Functions findings remain unresolved and are not accepted as safe. Source, tests, and any merge do not publish the website, deploy Firebase, change a provider, inspect production data, or prove live behavior. |
| Part of RISK-014 | SUPPLY-001D11 #488 corrects the released #355 boundary after GHSA-395f-4hp3-45gv / CVE-2026-13311 made its former 1.8.4 target affected again. The sole root development-only shell-quote node moves to first-patched 1.9.0 through the unchanged launch-editor ^1.8.1 and react-dev-utils ^1.7.3 ranges. The lock changes only that node's version, public-registry URL, and integrity; package.json, classification, license, funding, engine, and every other record stay fixed. A dependency-free test pins the reviewed installed/locked identity and parents. In the same bounded isolated Node 20 child, the 64,000-token public parse() case times out on 1.8.4 and completes on 1.9.0 with the exact token count, while #355's separate quote() line-terminator regression remains unchanged. A fresh 2026-08-01 UTC audit changes from 60 full-root findings (24 high, 23 moderate, 13 low) to 59 (23 high, 23 moderate, 13 low), with no shell-quote result; the production-only audit remains 4 findings (1 high, 2 moderate, 1 low). |
No application or Functions source imports shell-quote, and clean before/after optimized builds are byte-identical across 59 files and 19,520,983 bytes, with aggregate sorted-manifest SHA-256 531c7009a33b78fbf9bd2d4d8200209694bbf88793bfd4a09dd846c7450cecbe. The remaining root and Functions findings are unresolved and are not accepted as safe. This source/test change does not publish the website, deploy Firebase, change a provider, inspect production data, or prove live behavior. |
| Part of RISK-014 | SUPPLY-001D12 #524 moves the sole Functions development-only brace-expansion node from affected 1.1.15 to reviewed follow-up 1.1.18. Only that lock record's version, public-registry URL, and integrity change; functions/package.json, the dev: true classification, dependency shape, and sole minimatch@3.1.5 parent range ^1.1.7 remain exact. A focused test pins the one locked and installed identity and proves neither manifest nor lock root declares a direct dependency or override. Bounded Node 20 children cover the real minimatch.braceExpand() consumer and the installed package API: the current node produces 1,024 results totaling 5,242,880 characters for the fixed consumer witness, while 1.1.18 stops at 781 / 3,998,720 under the default 4,000,000-character limit; explicit chained and comma-alternative bounds stop at 20 / 200 and 50 / 50; a long padded sequence stops after one 100,001-character result; 5,000 non-expanding groups and ordinary sequence, option, padded, and empty-option behavior remain compatible. A fresh 2026-08-01 Node 20 audit falls from 12 Functions findings (2 high, 9 moderate, 1 low) to 11 (1 high, 9 moderate, 1 low), removing both GHSA-3jxr-9vmj-r5cp and GHSA-mh99-v99m-4gvg from the sole node; the production-only audit remains 10 findings (0 high, 9 moderate, 1 low). |
This is a test/lint-tool lock repair, not a deployed Functions-runtime claim. The separate js-yaml high group and the Firebase Admin/Google SDK/body-parser/uuid findings remain unresolved and are not accepted as safe. Source, tests, and any merge do not deploy Firebase, publish the website, change a provider, inspect production data, or prove live behavior. |
| Part of RISK-014 | SUPPLY-001D13 #537 moves the sole Functions production body-parser node from affected 1.20.5 to first-patched 1.20.6 through Express 4.22.2's unchanged ~1.20.5 range. Only that lock record's version, public-registry URL, and integrity change; functions/package.json, the production classification, dependency and engine shape, sole consumer, and absence of a direct dependency or override stay exact. A focused test pins the installed and locked topology. All four public parser factories now throw at construction for an unparseable synthetic limit or NaN, while omitted, undefined, null, numeric, and valid string limits remain compatible. Fresh 2026-08-01 UTC Node 20.20.2/npm 10.8.2 audits remove only GHSA-v422-hmwv-36x6: production falls from 10 findings (9 moderate, 1 low) to 9 moderate findings, and the full Functions audit falls from 11 findings (1 high, 9 moderate, 1 low) to 10 (1 high, 9 moderate). |
The remaining Functions findings, including the development-only js-yaml high group and the production Firebase Admin/Google SDK/uuid groups, remain unresolved and are not accepted as safe. No endpoint configuration, application source, manifest, or provider setting changes. Source, tests, and any merge do not deploy Firebase, publish the website, change a provider, inspect production data, or prove live behavior. |
| Part of RISK-014 | SUPPLY-001D14 #544 moves both Functions development-only js-yaml nodes to their reviewed legacy security follow-ups: the @istanbuljs/load-nyc-config copy from 3.14.2 to 3.15.1 through unchanged ^3.13.1, and the shared ESLint copy from 4.2.0 to 4.3.1 through unchanged ^4.1.0 ranges. Only the two lock records' versions, public-registry URLs, and integrity values change; manifests, topology, development classifications, licenses, dependency shapes, and all three consumers stay exact. Focused tests pin both installed and locked identities. Both public loaders now reject a fixed three-key merge when maxTotalMergeKeys is 2, accept it when the limit is 3 or explicitly unlimited, and parse a fixed three-entry !!omap without the former three prior-key linear scans. Fresh 2026-08-01 UTC Node 20.20.2/npm 10.8.2 audits remove GHSA-52cp-r559-cp3m and GHSA-h67p-54hq-rp68: the full Functions snapshot falls from 10 findings (1 high, 9 moderate) to 9 moderate findings, while production-only remains 9 moderate findings. |
No application or Functions runtime source imports js-yaml; these nodes serve only lint/test tooling. The remaining nine production dependency findings in the Firebase Admin/Google SDK/uuid graph are unresolved and are not accepted as safe. No forced audit upgrade, endpoint, manifest, provider, or data change occurs. Source, tests, and any merge do not deploy Firebase, publish the website, change a provider, inspect production data, or prove live behavior. |
| Part of RISK-014 | SUPPLY-001D15 #573 moves the sole Functions production protobufjs node from affected 7.6.3 to reviewed 7.6.5 through six unchanged Firebase Admin/Functions and Google SDK parent ranges. The exact lock change updates that node's version, public-registry URL, integrity, and dependency map, and removes the now-orphaned @protobufjs/inquire record; functions/package.json, the non-development classification, license, install-script and engine shape, all six consumers, and every other lock record stay exact. Focused tests pin the one installed/locked resolution and consumer topology. A bounded Node 20 child proves the public parse('option foo') witness rejects promptly instead of looping until termination, while a fixed proto3 java_package option and message parse normally. Fresh 2026-08-01 UTC Node 20.20.2/npm 10.8.2 full and production-only audits each fall from 9 to 8 moderate findings and contain no protobufjs result. |
The 7.6.5 dependency map deliberately follows upstream removal of deprecated util.inquire; no remaining lock record, consumer, manifest declaration, or application import uses that helper. No application or Functions source directly imports protobufjs, but six production transitive consumers retain the patched parser, so this is lock/install compatibility evidence rather than an endpoint exploitability claim. The remaining eight moderate Firebase Admin/Google SDK/uuid graph findings are unresolved and are not accepted as safe. No forced audit upgrade, endpoint, manifest, provider, or data change occurs. Source, tests, and any merge do not deploy Firebase, publish the website, change a provider, inspect production data, or prove live behavior. |
| Part of RISK-024 | SEC-001 protects token documents from every browser client. | OAUTH-001 must still add transactional refresh/versioning, scope/account binding, disconnect/revocation, IAM/encryption decision, and redacted lifecycle audit. |
| RISK-009, part of RISK-024 | OAUTH-001C1H #441 replaces browser-generated/session-storage-authoritative Strava OAuth state with a 32-byte server-issued challenge. Source stores only a SHA-256 digest in one browser-denied per-UID secret record, binds it to exact UID and decoded Auth auth_time, expires it after ten minutes, and transactionally deletes one match before provider exchange or connection writes. A later begin overwrites the older record; mismatch, expiry, replay, and the concurrent loser stop with one fixed result before Strava. Synthetic tests use mocks and no real callback or provider value. |
This source does not deploy either Function or the website, configure Strava, prove the custom App Check guard is enabled, enable native runtime App Check enforcement, inspect production data, or prove live OAuth behavior. #443 separately supplies only the source/test clean-page readiness handoff while preserving initial callback suppression. Scope/account policy, refresh concurrency, provider/local reconciliation, revoke/audit, IAM/encryption, configured Enterprise evidence, native enforcement, and provider/live evidence remain open under #88. |
| RISK-008, RISK-009, part of RISK-024 | OAUTH-001C1I #443 preserves initial capability-callback App Check suppression, then permits only a document that began on a normalized Strava callback to initialize the existing Enterprise provider after both current callback locations are clean, Router page state is null, and native history state is absent or the exact matching BrowserRouter index/key/empty-user-state record. One single-flight readiness attempt precedes one existing exchange. Before exchange admission, the callback rechecks the clean path, Router entry key, and exact UID, service, Firebase resources, app, attempt, and page lifecycle; dirty or reinjected paths, extra or mismatched history state, missing configuration, provider/readiness failure, or stale mounted context use one fixed failure and make no exchange, while an unmounted completion is inert. Authentication, registration, and shop callbacks are ineligible and keep their existing flow. The method never receives callback values or returns, inspects, logs, or stores the token-ready result. Local/test remain off and ordinary production startup remains eager. |
This is source and synthetic-test ordering evidence only. It does not configure an Enterprise key or domains, prove a provider-backed token or Firebase acceptance, enable native enforceAppCheck, change a Function or Rule, publish the website, deploy Firebase, configure or contact Strava, inspect production data, or prove live OAuth behavior. App Check initialization is not reversible; later reinjection is scrubbed/discarded but earlier browser, hosting, provider, and network copies are not erased. A lifecycle or reinjection change after exchange admission cannot cancel server or provider work already started; it blocks another exchange and obsolete UI, but reconciliation may still be required. Complete ABUSE-001A2 only after protected provider and exact-revision staging evidence. Account/scope policy, refresh concurrency, reconciliation, revoke/audit, IAM/encryption, deployment, and live proof keep #88 open. |
| RISK-009, part of RISK-024 | OAUTH-001C1J #612 browser source admits a fulfilled Strava exchange result only when it presents exactly two own fields, each an enumerable data field: primitive ok equal to true and a positive safe-integer athleteId. It returns a fresh frozen confirmation. Every other fulfillment or exceptional result inspection throws one fixed local response error; a current callback uses the existing fixed failure screen without navigating, while existing lifecycle guards keep obsolete completions inert. Callable transport rejection remains uninspected and identity-preserving. Browser JavaScript cannot distinguish a transparent Proxy that faithfully presents the contract, so the received object is never retained. The check relies on the page realm's built-in reflection, numeric, and freeze methods; code that replaces those methods before admission can change what this browser-only boundary observes. Synthetic tests use no real callback, account, token, provider, or Firebase project. |
This client acknowledgement check does not prove Strava origin, provider exchange, paired Firestore persistence, athlete ownership, approved scopes, server integrity, Firebase or website deployment, or live behavior. A compromised server or browser realm can forge the exact result. Provider response-byte and timeout limits, scope/account policy, provider/local reconciliation, audit, IAM/encryption, configured Enterprise evidence, native enforcement, release order, and provider/live proof remain open under #88. |
| RISK-017, part of RISK-024 | OAUTH-001A2K #614 Function source admits a successful Strava authorization-exchange or refresh body only through the captured native Node Response, Headers, and stream-reader methods. It permits at most 65,536 content-decoded bytes and 4,096 chunks before copy, treats a valid Content-Length only as an early ceiling check, counts the actual chunks even when that header is missing or lower, best-effort cancels an unfinished failure without awaiting or exposing cleanup, then uses fatal UTF-8 decoding and captured JSON parsing before the existing strict projector. It never calls the response's json, text, arrayBuffer, or bytes method, clone, or tee. Invalid body transport/parsing keeps the existing fixed unavailable result; a bounded invalid schema keeps the fixed internal result. Exchange state stays consumed but no token/connection write follows a body failure; refresh keeps the prior secret and starts no bearer data request. The direct projector seam remains absent from functions/index.js and preserves hostile-JavaScript tests that real JSON bytes cannot construct. Synthetic tests use no real token, account, provider, or Firebase project. |
This application ceiling covers only content-decoded chunks consumed by this source. It does not bound headers, compressed-wire bytes, Undici buffers, one already-allocated provider chunk, parsed-object heap growth, CPU, or request/connect/read time. Cancellation cannot undo a provider exchange or rotation, so a lost acknowledgement can still require reconciliation and must not be retried blindly. Activities/statistics response bounds and their concurrently started body cleanup remain open, as do scope/account policy, refresh/revoke reconciliation, audit, IAM/encryption, Firebase deployment, provider configuration, production data, and provider/live proof under #88. |
| Part of RISK-027 | #99 blocks App Check, Firebase Analytics, and Sentry in local/test and on initial capability-callback startup. OBS-001A1 #134 source disables Sentry replay, tracing, browser session tracking, client reports, member user context, breadcrumbs, attachments, transactions, and unapproved default integrations. OBS-001A2 #139 source removes every application runtime Firebase Analytics import, initialization, and emission, preserves existing wrapper calls as provider-free no-ops, and removes the direct Waiver SDK bypass. OBS-001A3 #142 routed the remaining render, Auth-email, members-only, and App Check browser diagnostics through five fixed outcome identifiers and prohibited direct console calls elsewhere in application/public runtime source. OBS-001A5 #494 removes the dormant members-only identifier after its sole caller was retired, leaving the current helper with four fixed outcomes and requiring the old literal to be dropped as unknown input. Synthetic tests use mocks/canaries and make no provider call. | #110/#111 still own any future approved telemetry purpose, consent, event/parameter schema, provider settings, and residual hosted boundary. Source/tests and merge evidence do not prove website publication, the exact runmprc.com revision, provider transport/collection, IP/cookie behavior, console history, historical data deletion, retention, access, consent, deletion, or vendor terms. Sentry provider behavior is likewise unverified. #443 completes only the source/test Strava client handoff; native enforcement and the DATA-001A confirmation handoffs remain with ABUSE-001A and their canonical trackers. |
| Part of RISK-035 | The Jest environment is repaired, #124 adds the complete frontend suite as a named blocking hosted step, and #126 adds the standalone SPA callback suite. CI-001B3 #167 merged the lockfile-bound Node 20/Java 17 Commerce command journal emulator job using exact demo project demo-pay002b2-test, Firestore only, and explicit opt-in. CI-001B4 #186 merged through PR #189 as bec7d5e365eacb418563a172029f241f660d9768, adding the no-new-dependency exact-record checker, named blocking lint step, and clean-checkout step. Exact PR run 29291402007 and post-merge run 29291515653 passed every GitHub job. CI-001B4A #227, CI-001B4B #239, and PAY-004C1 #359 retire four errors and one warning in focused reviewed changes. PR #391 and PR #392 add three TypeScript files while retiring four TSX errors during reviewed functional work. CI-001B4C #449 retires exactly four stale unknown-rule directive records with comment-only same-line substitutions. #492 removes one finding-free dormant JSX file, #505 adds four finding-free TypeScript/TSX files, #506 adds four more, #618 adds one finding-free JSX page, and #621 adds one finding-free TypeScript availability module. |
The checker now scans 120 frontend files. Its reviewable baseline deliberately records 113 configured errors and 6 warnings as individual file/line/column/rule/message records; it is not a clean-lint claim. Any added, removed, moved, or changed record requires review, every fatal parser/configuration failure rejects with a fixed diagnostic, and the baseline must not be regenerated just to pass. The clean check covers tracked and non-ignored untracked files; ignored dependency/build outputs are outside this proof. Hosted source checks do not configure branch protection, deploy Firebase, or prove Stripe/live behavior. #169, #173, #182, #206, #226, #232, and #238 expand the focused commerce suite; configure/prove branch-required checks and add the broader TEST-001 suite. |
| DATA-001A3 / registration confidentiality | DATA-001A3 #374 limits the My Account registration callable to one exact stored-UID query. Source no longer reads token email or verification claims and no longer searches registrations by runner email. Matching-email records with no UID or another account's UID are excluded; exact-UID records retain their existing sanitized summary, event lookup, and sort. The page's empty result says only that no upcoming registration is linked and that a signed-out registration may not appear. Synthetic tests use getter and Proxy canaries and make no write or provider call. | This source containment is not deployed or live and does not associate, migrate, repair, or change a registration. Anonymous, historical, mistyped-email, shared-email, and differently owned records stay hidden until a separately approved, audited server association under #115. Keep the email index until separate inventory and migration review. Complete protected Function deployment/readback, website publication, exact runmprc.com verification, and a made-up account check before describing the boundary as live. |
| DATA-001A4 / registration response minimization | DATA-001A4 #553 removes the unused nested runner projection from the exact-UID My Account registration response and its browser type. Source does not access the matching document's runner property or serialize runner name, email, or shirt size; it preserves every non-runner registration field, the event summary, event reads, registration-created sort, and empty result. A synthetic accessor-backed record proves the runner property is untouched and invented canaries do not cross the response. |
This source is not deployed or live. The Function still retrieves the complete matching registration document from Firestore, and stored runner data plus separate event, admin, and confirmation contracts remain unchanged. This slice makes no retention, deletion, field-mask, schema, association, or migration decision. Complete a protected Function deployment/readback and made-up account check before describing the narrower response as live. |
| DATA-001A5 / registration-query failure containment | DATA-001A5 #560 catches without binding any exceptional Firestore construction, collection-group, exact-UID where, or query get outcome and returns only fixed unavailable / Registration data could not be loaded. before registration projection or event reads. Synthetic stage failures prove that no caught-value getter, coercion, serialization, formatting hook, or application logger is reached while App Check/Auth and successful query behavior stay unchanged. |
This source is not deployed or live. It does not contain exceptional registration snapshot iteration/projection or event-document reads, change stored data or field retrieval, add retries, or prove Firebase logging outside application code. Complete a protected Function deployment/readback and made-up account check before describing the fixed boundary as live; harden later data/event failure stages separately. |
| DATA-001A6 / registration snapshot-processing failure containment | DATA-001A6 #566 separately catches without binding every exceptional exact-UID QuerySnapshot.forEach and owned-document reference, ID, data, or retained-field projection outcome. It returns only fixed unavailable / Registration data could not be loaded. before event-ID derivation or event access. Synthetic iterator, reference-path, projection-time data(), and retained-field failures prove no caught-value getter, coercion, serialization, formatting hook, console/Firebase logger, event read, or later-document projection is reached while the exact query and successful/empty behavior stay unchanged. |
This source is not deployed or live. It does not contain exceptional event-ID derivation, event collection/document/get/snapshot/projection work, or later registration sorting; change stored data, field retrieval, or response shape; add retries; or prove Firebase logging outside application code. Complete a protected Function deployment/readback and made-up account check before describing the boundary as live; harden later event/sort stages separately. |
| DATA-001A7 / registration event-acquisition failure containment | DATA-001A7 #575 catches without binding every exceptional event collection, document, or get acquisition outcome and returns only fixed unavailable / Registration data could not be loaded. before a successful event snapshot or field projection for the failing ID. Synthetic stage failures prove no caught-value getter, coercion, serialization, formatting hook, console/Firebase logger, event-data read, or partial response is reached while App Check, Auth, the exact-UID registration query, registration projection, event lookup concurrency, successful/empty behavior, and created-time sort stay unchanged. |
This source is not deployed or live. It does not contain exceptional event-ID derivation, event snapshot exists, data(), retained-field projection, concurrent sibling cancellation/order, or later registration sorting; change full Firestore document retrieval, stored data, response shape, or permissions; add retries; or prove platform logging outside application code. Complete a protected Function deployment/readback and made-up account check before describing the fixed boundary as live; harden later event projection and sort stages separately. |
| DATA-001A8 / registration event snapshot-processing failure containment | DATA-001A8 #579 separately catches without binding every exceptional acquired event snapshot exists, data(), or retained slug, title, startAt, and location projection outcome. It returns only fixed unavailable / Registration data could not be loaded. and no partial callable response. Synthetic snapshot and field failures prove no caught-value getter, coercion, serialization, formatting hook, console/Firebase logger, or later registration sort is reached while App Check, Auth, the exact-UID query, runner minimization, A7 acquisition, missing-event success, valid event projection, lookup concurrency, successful/empty behavior, and created-time sort stay unchanged. |
This source is not deployed or live. It does not validate event IDs or malformed nonthrowing event values; cancel or serialize concurrent sibling reads; contain event-ID derivation, later registration sorting, response/platform serialization, or platform logging outside application code; change full Firestore document retrieval, stored data, response shape, permissions, or retries; or prove live behavior. Complete a protected Function deployment/readback and made-up account check before describing the fixed boundary as live; harden remaining derivation/sort/serialization residuals separately. |
| DATA-001A9 / registration-sort failure containment | DATA-001A9 #598 catches without binding every exceptional created-time comparison outcome in the final registration sort after event work completes and returns only fixed unavailable / Registration data could not be loaded. with no partial callable response. Synthetic _seconds access and numeric-coercion failures prove no caught failure-value inspection, formatting, serialization, logging, or raw escape while App Check, Auth, the exact-UID query, runner minimization, A5–A8 boundaries, event lookup concurrency, successful and empty behavior, and descending registration-created ordering stay unchanged. |
This source is not deployed or live. It does not validate malformed-but-nonthrowing timestamps or event IDs; alter fallback-to-zero or tie behavior; contain event-ID derivation or response/platform serialization; cancel or reorder already completed event reads; change stored data, response shape, permissions, or retries; prove platform logging outside application code; or prove live behavior. Complete a protected Function deployment/readback and made-up account check before describing the fixed boundary as live. |
| RISK-026, RISK-036 | #135 merged through PR #138 as 9eafab1217aff7058c42240aaba72d7b93f8ed24, replacing automatic frontend-first/fail-open GitHub deployment with a tested manual exact-current-commit gate. Post-merge staging and synthetic production probes failed closed before authentication or mutation, and published neither Firebase nor Pages. |
#133 must configure protected environments and least-privilege OIDC/WIF; #136 must prove staged/target deployment and clearing/readback of the existing Pages runmprc.com claim; a protected WEB-001 child must establish Netlify publication and rollback. No Firebase, Pages, live-host, or provider-setting change is proven by source/static tests alone. |
| RISK-039 | The #118 source slice uses an empty authenticated callable request, bounded Firebase Auth identity fields, one transactional create-only helper shared with signup, constant responses, generic failures, and a UI that hides Edit until setup and the Rules-protected read succeed. Signup and recovery never change custom claims; browser profile creation remains denied. | Source review/merge is not deployment. Under #105, deploy the exact #100 Rules plus both createMemberOnSignUp and ensureMemberProfile before the website, prove App Check policy, use a synthetic staged account, verify rollback, then record website, Function, Rules, and live behavior separately. Never repair a real profile manually. |
| AUTH-006G / source-only confirmed-save interface containment for RISK-039 | #651 gives an exact current full-name update plus successful non-null authoritative profile reread one fixed Profile name saved. result with status, polite live, atomic, programmatic-focus, bounded-layout, and scoped visible-outline semantics. Profile-header DOM order is heading, result, Edit. Separate pending and result tokens retain only the opaque profile generation and attempt ID. A token is armed only after validation and synchronous save admission while the exact connected Save button owns focus, and exact current confirmed success alone transfers it. One layout effect consumes the result token before target checks, requires the matching current generation and attempt plus the connected result, leaves retained result focus alone, restores absent, body, document-root, or disconnected focus, and preserves every other connected focus. Unfocused or programmatic success shows the result without moving focus. Initial load, validation failure, update rejection, missing or rejected confirmation read, reload, application/Firestore/identity/UID or generation change, newer attempt, stale work, unmount, and later rerender cannot show or focus stale success. Edit, a new admitted Save, profile load, and context change clear the result and obsolete tokens. Synthetic tests use made-up profiles and cover exact call counts, current reread projection, DOM/live/CSS semantics, lost and preserved focus, one-shot clearing, failure paths, stale authoritative reads, and lifecycle fences. | The fixed result and local focus are current-browser accessibility feedback, not independent proof of Firebase deployment, provider acknowledgement, identity or profile ownership beyond existing authentication, production persistence, membership, dues, role, payment, entitlement, directory eligibility, or live behavior. The tokens contain no name, email, UID, profile, revision, response, error, provider value, or photo data and create no read, write, request, retry, provider call, log, or stored value. Existing validation, name-only payload, authoritative reread, one-attempt/context fences, generic unconfirmed-change recovery, and exact service calls remain unchanged. AUTH-006H #653 separately owns unconfirmed-change alert focus; AUTH-006I #655 separately owns Edit-to-input focus; validation error association and Cancel-to-Edit focus remain open separate outcomes. Complete #118/#105/#133/#136 backend-first and publication proof before calling the profile repair or this result live. Directory availability remains false; #623 remains the inert deployed preview; #507 retains all optional-directory gates. No Function, Rule, schema, index, service, package, workflow, provider, account, sign-in, production-data, deployment, publication, photo-query, facial-recognition, matching, embedding, similarity, biometric, roster, or live action occurs. Active #616 OAUTH-001A2L and its RISK-024 source hunk remain unchanged. |
| AUTH-006H / source-only unconfirmed-save interface containment for RISK-039 | #653 reuses the existing full-name Save unconfirmed alert and Try profile again action. After validation and synchronous one-attempt admission, the pending focus token is armed only while the exact connected Save button owns focus and retains only the opaque profile generation and attempt ID. An exact current update rejection, null confirmation reread, or rejected confirmation reread creates separate unconfirmed result ownership with the same minimal generation-and-attempt pair and transfers the pending focus token only when it matches; no browser state classifies whether the update persisted. Only that exact current save-unconfirmed state gives the byte-exact fixed alert assertive, atomic, programmatic-focus, bounded-wrapping, and scoped visible-outline semantics. The message contains no member, profile, provider, response, or caught detail, immediately precedes the enabled retry button, and keeps that action as the next Tab stop. One layout effect consumes the intent before checks and requires the matching current generation and attempt, unavailable state, no profile/editor/success confirmation, exact fixed message, and connected alert. It leaves retained alert focus alone, restores absent, body, document-root, or disconnected focus, and preserves other connected focus. An unfocused/programmatic Save still shows the alert without moving focus. Initial setup/read failure, validation failure, reload, application/Firestore/identity/UID or generation change, newer attempt, stale update/reread settlement, unmount, and later rerender cannot focus a stale result; Try clears the old intents before the existing reload. Synthetic tests use only made-up profiles and cover all three unconfirmed paths, exact calls, fixed copy/DOM/live/CSS semantics, lost and preserved focus, programmatic invocation, initial and validation failures, retry clearing, lifecycle races, newer work, one-shot consumption, and unmount. |
The fixed alert and local focus communicate browser uncertainty; they do not prove the update failed or persisted, provider acknowledgement, profile or identity ownership beyond existing authentication, Firebase deployment, production data, membership, dues, role, payment, entitlement, directory eligibility, or live behavior. The state and refs retain no name, email, UID, profile, revision, request, response, error, provider value, or photo data and add no read, write, request, retry, provider call, log, stored value, page node, or data movement. AUTH-006G success, validation, name-only payload, update/reread behavior, context fences, fixed recovery copy/action, and exact service calls remain unchanged. AUTH-006I #655 separately owns Edit-to-input focus; later Try-profile-again result focus, validation error association, and Cancel-to-Edit focus remain separate. Complete #118/#105/#133/#136 backend-first and publication proof before calling the profile repair or this result live. Directory availability remains false; #623 remains the inert deployed preview; #507 retains all optional-directory gates. No Function, Rule, schema, index, service, package, workflow, provider configuration, account, sign-in, production-data, deployment, publication, photo query, facial recognition, matching, embedding, similarity, biometric processing, roster, or live action occurs. Active #616 OAUTH-001A2L and its RISK-024 source hunk remain unchanged. |
| AUTH-006I / source-only Edit-to-input interface containment for RISK-039 | #655 keeps Edit and Full name as their existing native current-profile controls and adds only two local refs: one retains the opaque current profile generation when the exact connected Edit event target owns focus, and one targets the existing input. Each Edit activation replaces the intent before opening the editor and clearing prior Save feedback. One layout effect consumes the intent before every check and requires the mounted matching generation, exact current application/Firestore/identity/UID profile context, ready profile, open editor, no save in progress, and connected enabled input. It leaves an already-focused input alone; restores only absent, body, document-root, or disconnected focus; and preserves other connected focus. A missing or disabled target, stale context, closed editor, pending save, or deliberate connected focus consumes the intent without delayed reuse. Unfocused/programmatic Edit opens the editor without focus movement. Profile load/reload, context change, Try, Save, Cancel, and unmount clear or consume the intent. Synthetic tests use only made-up profiles and cover current name and call counts, native input semantics and keyboard order, global focus styling, lost and preserved focus, programmatic activation, one-shot consumption, context boundaries, prior confirmed success, and unmount. | Local focus is accessibility behavior, not proof of Firebase deployment, provider acknowledgement, profile or identity ownership beyond existing authentication, production persistence, membership, dues, role, payment, entitlement, directory eligibility, or live behavior. The intent retains no name, email, UID, profile, revision, attempt, request, response, error, provider value, or photo data and adds no React state, validation, read, write, request, retry, provider call, log, storage, CSS, page node, or data movement. The existing authoritative current name, label, linked description, autocomplete, maximum length, enabled state, global visible-focus rule, input/Save/Cancel order, AUTH-006F/G/H behavior, payloads, and exact calls remain unchanged. Cancel-to-Edit focus, validation-error association or focus, Try-profile-again settlement focus, and save-pending announcement remain separate. Complete #118/#105/#133/#136 backend-first and publication proof before calling profile repair or this handoff live. Directory availability remains false; #623 remains the inert deployed preview; #507 retains all optional-directory gates. No Function, Rule, schema, index, service, package, workflow, provider configuration, account, sign-in, production-data, deployment, publication, photo query, facial recognition, matching, embedding, similarity, biometric processing, roster, or live action occurs. Active #616 OAUTH-001A2L and its RISK-024 source hunk remain unchanged. |
| RISK-040 | AUTH-MAIL-002A #145 merged as 46557c7: account creation returns accepted or unavailable without exposing provider details. AUTH-MAIL-002B #153 merged as 23bca8c8: My Account makes no false “sent” claim, blocks rapid repeats, and applies the same 60-second browser cooldown after either outcome. Its protected release run 29252492614 stopped before build because the required public App Check key was absent, so neither frontend revision is published. AUTH-MAIL-002C1 #155 tracks one byte-equivalent password-reset request result after provider success or failure. AUTH-MAIL-002C2 #194 tracks the source-only /auth/action verification path: the initial capability suppresses Sentry/App Check, the page removes native and router query/fragment state, a scanner-style page load makes no action-code check/apply and no account mutation, and one deliberate action requires provider VERIFY_EMAIL before apply. Fixed results expose no email, code, raw provider error, account identity, or provider-directed navigation. Synthetic tests use mocks and canary values only. |
Publish and verify each exact frontend revision separately. The existing #99 Pages bridge briefly uses tab-local session storage for the return route and deletes it before React; a failed root load can leave it until tab close, while direct-rewrite hosting avoids that residual. #194 adds no storage write. #118 profile source is merged but live behavior is unproven. Keep delivery, Spam, DNS, templates, provider handler choice, and private Firebase email-enumeration-protection readback under #119. Firebase uses one custom handler for verification, password reset, and email recovery; never point its global action URL at the verification-only #194 route until every enabled mode is safely handled, or keep the default multi-mode handler. Accepted requests do not prove delivery, browser cooldowns are not abuse controls, Auth verification does not grant membership, and the Firestore verification mirror remains unfinished AUTH-001 work. |
| RISK-041 | WEB-PRIVACY-001W #496 adds source-only containment to the Admin Product editor. One mounted page synchronously admits at most one valid save for its current route, Firestore reference, and exact authenticated admin UID. Pending and unknown results hide the complete form and actions; a rejection is discarded without binding or inspection and becomes one fixed accessible stop result. Missing identity or database state starts no save, obsolete or unmounted completions are inert, and the existing exact create/update projections plus current successful navigation stay unchanged. | This browser guard resets on navigation or reload and does not make a repeated write safe. Direct client writes, missing durable command identity, version fencing, audit, private readback, reconciliation, authorization hardening, backup, rollback, Firebase/Rules deployment, website publication, exact live revision, and production behavior remain unproven. Keep the Admin screen unavailable and replace it with a server-authoritative idempotent command before officer use. |
SUPPLY-001D9 #455 updates the current repository-owned emulator and release CLI boundary after the historical #167 Java 17 delivery. Source now pins Firebase CLI 15.24.0 and both hosted emulator jobs provision Java 21. Static guards bind both hosted emulator jobs to their complete parsed steps and read-only permissions, rejecting Java downgrade, floating/global CLI installs, unexpected environment or authority wiring, hosted projects, broadened emulator scope, skipped or ignored failure, and extra commands. Java 17 rejects before emulator startup; credential-inaccessible Java 21 proof uses only explicit demo-* projects. The two critical full-root audit groups under the old CLI are removed, but the remaining 24 high, 21 moderate, and 13 low full-root findings and every Functions finding remain open. No source check, merge, or green emulator run proves Firebase deployment, website publication, provider state, production data, or live behavior.
SUPPLY-001D10 #459 updates all fifteen root brace-expansion lock records through their existing parent ranges: twelve 1.x copies move to 1.1.18, two 2.x copies move to 2.1.4, and one 5.x copy moves to 5.0.9. These compatible follow-up releases close cumulative-output bypasses in the first GHSA-mh99-v99m-4gvg patches. Each record's version, public-registry URL, and integrity change; the 5.x record also adopts the maintained release's Node 20 || >=22 engine boundary. package.json and the Functions manifests/lockfile stay byte-identical. A dependency-free test pins all fifteen root identities, locations, development classifications, and parent ranges. Bounded child processes cover every installed major family: exact current main produces 1,024 values totaling 10,240 characters despite maxLength: 200, and 500 comma-alternative values despite maxLength: 50; the patched families cap those cases at 20/200 and 50/50. A 100,001-character padded sequence stops after one result under maxLength: 200000, while ordinary padded sequences, options, and result-limit behavior around empty options remain compatible. The fresh 2026-08-01 Node 20 advisory snapshot on exact current main changes from 61 full-root findings (0 critical, 25 high, 23 moderate, 13 low) to 60 (0 critical, 24 high, 23 moderate, 13 low); the production-only audit changes from 5 findings (2 high, 2 moderate, 1 low) to 4 (1 high, 2 moderate, 1 low). Clean before/after builds are byte-identical across 59 files and 19,521,335 bytes, with aggregate sorted-manifest SHA-256 2bf3ffb8e5d8d9e7ab6fc114a7a9f83c6dcd8a4098a93b2dad8c8d62bf4cf8ce. The remaining legacy minimatch high group is not accepted as fixed merely because advisory metadata labels later legacy releases patched; its maintained behavior remains unsafe and requires a separately reviewed modern-major compatibility migration. Source, tests, and any merge do not publish the website, deploy Firebase, change a provider, inspect production data, or prove live behavior.
MEMBERS-CONTENT-001B #492 removes the unrouted legacy MembersOnly component, its barrel export, and the direct html-react-parser dependency. The root lockfile drops only the parser's 12-record production closure; the shared domelementtype record remains for development tooling and is reclassified development-only. A dependency-free guard requires the renderer source, export, manifest entry, parser record, and exact orphan closure to stay absent. Follow-up OBS-001A5 #494 removes the now-dormant members_only_fetch_failed diagnostic identifier, pins the exact four remaining outcomes, and requires the retired literal to be dropped without console output.
This is source-only attack-surface reduction. The component had no runtime route or consuming import, so its removal changes no active member experience. It does not read, sanitize, migrate, publish, or delete legacy members_only data; change Firestore Rules; add the structured discount route governed by #83 and #413; deploy Firebase; publish the website; or prove live behavior. Future member content must use the bounded plain-text projection and a separately reviewed authorization/data boundary, not restore a raw-HTML parser.
- Stripe secret keys, webhook secrets, refunds, payout/account settings, and financial reports.
- Firebase/GCP service credentials, deployment authority, Auth role claims, and App Check configuration.
- Runner identity, date of birth, phone, emergency contact, waiver evidence, and attendance.
- Buyer identity, shipping address, order history, and tracking information.
- Member profiles and Strava access/refresh tokens and activity data.
- Race capacity, merchandise inventory, discounts, email reputation, and cloud budget.
- GitHub repository, workflows, DNS/custom domain, production artifacts, and audit history.
- Opportunistic unauthenticated bots testing public functions and credential stuffing.
- A customer manipulating price, member tier, custom fields, checkout retries, refunds, or inventory races.
- A compromised member or admin account.
- An insider with more access than their job needs.
- A leaked CI/cloud/Stripe/OAuth secret.
- A compromised dependency, GitHub Action, browser extension, or operator device.
- Accidental operator/developer action against production.
- Webhook duplication, delay, reordering, provider outage, or partial network failure without malicious intent.
- Submit a member tier while unauthenticated or using an unverified known member email.
- Race two last-seat/last-SKU requests.
- Reuse a client request after a timeout to create multiple Sessions or refunds.
- Pay a Session after the operator cancels the local record.
- Replay or reorder a valid Stripe event.
- Apply an unmodeled Stripe promotion and obtain goods while local records show another total.
- Steal an admin token and read OAuth secrets or directly rewrite financial records.
- Script public callables without App Check to create Stripe objects, Firestore writes, or cloud cost.
- Inject markup/formulas/oversized data through names, notes, custom fields, email, or exports.
- Cause local development to call production Functions.
- Firebase Auth verifies identity; email ownership must be verified before member/admin grants based on email.
- Privileged accounts require MFA and a documented account-recovery process.
- Sensitive operations require recent authentication where supported.
- Auth error messages do not reveal whether an account exists beyond Firebase's approved behavior.
- Role changes force token refresh/revocation and emit a durable audit event.
- Server code and Firestore rules check signed token capabilities; the UI is never the enforcement point.
- Separate catalog/content editing from financial state, identity administration, exports, and secrets.
- No client—including browser admins—can write webhook inbox, payment state, refund IDs, Stripe IDs, audit events, rate limits, or OAuth secrets.
- Server service accounts use IAM least privilege because Admin SDK bypasses Firestore rules.
- Use reCAPTCHA Enterprise for web App Check and observe metrics before enforcing.
- Set runtime
enforceAppCheck: trueon every sensitive callable instead of a custom fail-open environment test. - Consider limited-use/replay-protected tokens only for the most abuse-sensitive callable after measuring latency and SDK support.
- Keep rate limits as defense in depth; App Check is not user authorization and does not stop all valid-browser abuse.
- Apply cloud budgets/alerts and Stripe rate/error alerts so cost abuse is visible.
- Centralize strict request schemas with maximum object depth, field count, string length, array length, total bytes, integer ranges, enum values, and URL allowlists.
- Normalize email consistently; do not over-normalize names or assume ASCII.
- Validate event custom fields against the stored schema.
- Escape HTML email content, neutralize spreadsheet formulas, and let React escape browser rendering.
- Avoid rendering arbitrary HTML from Firestore. If business requirements demand it, sanitize with a maintained allowlist library and a restrictive CSP.
- Return generic errors to unauthenticated clients; log structured error codes without secrets/PII.
- Store server secrets in Google Secret Manager and bind each only to functions that need it.
- Never use
REACT_APP_*for a secret; CRA embeds those values in public JavaScript. - Keep local overrides ignored (
.secret.local,.env.local) and commit only name/format examples. - Rotate Stripe and webhook secrets independently, with an overlap plan where the provider supports it.
- Inventory owners, creation dates, consumers, last rotation, and emergency revocation steps.
- Remove long-lived GitHub service-account JSON in favor of workload identity/OIDC when feasible.
The binding design is in STRIPE_COMMERCE_DESIGN.md. Its mandatory controls are server price authority, persistence-first checkout, Stripe and application idempotency, atomic capacity/inventory, verified async-aware webhooks, allowed state transitions, idempotent refunds, and reconciliation.
| Class | Examples | Baseline controls |
|---|---|---|
| Public | Published event/product content, officer information intentionally on site | Integrity review; no secrets or hidden fields in same readable document |
| Internal | Catalog drafts, operational counts, non-sensitive audit metrics | Authenticated role-limited access; normal logs/backup |
| Confidential PII | Member email/phone, order contact, Strava profile metadata | Need-to-know access, redacted logs, retention limit, access/deletion process |
| Restricted PII | DOB, emergency contact, shipping address, waiver evidence | Narrow server/admin capability, export audit, encryption in transit/at rest, aggressive field-specific retention |
| Restricted secret | Stripe keys, webhook secrets, OAuth refresh tokens, deploy credentials | Secret Manager or server-only encrypted store, no browser read, rotation, access audit |
| Financial metadata | Expected/paid/refunded totals, Stripe IDs, disputes | Finance/server capability, immutable audit, accounting retention, reconciliation |
Proposed retention values must be approved by counsel, finance, insurance, and operations. A reasonable minimization starting point for discussion is:
- Unpaid/expired checkout PII: delete or anonymize within 30 days after final reconciliation.
- Emergency contact and DOB: delete after the event plus a short operational/incident window unless insurance/counsel requires longer.
- Shipping address: remove from routine operator access after fulfillment/return window; retain only fields legally required for accounting/tax.
- OAuth tokens: delete immediately on disconnect/account deletion; rotate on suspected exposure.
- Rate-limit identifiers: expire shortly after the enforcement window and store HMACs, not raw values.
- Webhook inbox: retain non-PII identifiers long enough for reconciliation/audit, then expire under policy.
- Payment/accounting and waiver evidence: retain only for the approved legal/accounting/insurance period, separately from unnecessary operational PII.
Backups and exports must honor eventual deletion/anonymization schedules and have documented access.
- Correlation/request ID, function name, environment, code version, local business ID, Stripe Event/type/object ID, state transition, latency, and sanitized error code.
- Authentication UID for privileged actions, capability used, target resource, reason, and result.
- Reconciliation counts and anonymous mismatch categories.
- Stripe or OAuth secrets, bearer tokens, full webhook payloads, Checkout URLs, password/reset data, raw ID tokens, session cookies, full addresses, DOB, emergency contacts, or arbitrary request bodies.
- Full customer email/IP in rate-limit and security logs; use a keyed pseudonymous identifier where correlation is needed.
- Invalid webhook signature spikes.
- Event inbox failed/quarantined/old items.
- Paid Stripe Session with no local record or amount/currency mismatch.
- Local paid record not verified in Stripe.
- Refund/dispute creation and failed refund reconciliation.
- Capacity/inventory counter drift or negative availability.
- Elevated callable rejects, App Check failures, Auth abuse, cloud budget anomalies, deploy failures, secret access, and role grants.
- Protect
mainand production environments with pull-request review and required passing checks. - CI must run non-mutating lint, frontend tests, Functions tests, Firestore Rules emulator tests, production build, secret scan, and dependency scan.
- Do not use
lint:fixas the CI lint command or mask it with|| true. - Pin GitHub Actions to reviewed major versions or immutable SHAs according to the project's maintenance capacity.
- Dependabot/Renovate updates are small, reviewed, and tested; do not blindly
npm audit fix --force. - Generate an SBOM/release dependency inventory for live commerce builds.
- Restrict production deployment to protected environment approval; use short-lived federated cloud credentials.
- Deploy backward-compatible backend/rules/indexes before dependent frontend changes.
- Record release commit, migration, function version, Stripe webhook/API version, test evidence, and rollback point.
- SEV-1: Stripe/GCP/GitHub secret exposure; unauthorized refund/payout/role change; confirmed PII exfiltration; active payment misclassification at scale.
- SEV-2: Webhook backlog or reconciliation mismatch affecting customers; capacity/inventory oversell; admin account compromise without confirmed data access.
- SEV-3: Bounded availability failure, attempted abuse blocked by controls, or non-sensitive data issue.
- Name the incident commander and scribe; preserve timestamps and evidence.
- Stop harm: disable new checkout, revoke/rotate affected credentials, revoke sessions/roles, or isolate the deployment.
- Do not destroy logs or silently rewrite financial records.
- Compare Stripe and Firestore using the reconciliation runbook.
- Notify Stripe/Firebase/GitHub or other vendors through their security/support channels when relevant.
- Determine legal, insurance, customer, and regulatory notification with qualified advisers.
- Restore through an audited repair, test it, monitor, and document every changed record.
- Complete a blameless post-incident review with owners and deadlines.
Specific playbooks live in OPERATIONS_RUNBOOK.md.
- Passing CI link and commit SHA.
- Dependency scan with zero unaccepted critical/high runtime findings.
- Firestore Rules test report proving browser admins cannot read secrets or directly mutate financial records.
- App Check metrics and enforcement proof for each callable.
- Stripe test-mode event delivery showing valid, duplicate, invalid, async, expired, refund, and dispute paths.
- Concurrency test showing no capacity/SKU oversell.
- Reconciliation report with zero unexplained mismatches.
- Secret/IAM/role inventory reviewed by two owners.
- Approved policies/waiver versions and retention matrix.
- Backup restore exercise and checkout-disable drill.
- Limited live pilot report and finance confirmation that Stripe payouts/totals match local records.
- Stripe webhook best practices
- Stripe Checkout fulfillment and delayed methods
- Stripe idempotent requests
- Firebase App Check enforcement
- Firebase Secret Manager and environment configuration
- Firestore IAM and least privilege
- Firestore Security Rules conditions
Re-check first-party documentation during implementation because provider behavior and recommended controls change.