Skip to content

Latest commit

 

History

History
66 lines (61 loc) · 13.5 KB

File metadata and controls

66 lines (61 loc) · 13.5 KB

Security notes

简体中文 · English

Account enrollment

  • Editing a root user's profile or credentials requires a currently enabled root actor, checked inside the same batch as the user, credential, and session writes. A concurrent root-role assignment cannot bypass this check. settings:read remains a sensitive permission that can read runtime secrets, not a monitoring role.
  • Payment review approval, payment accounting, approval audit, and notification outbox commit together. A concurrent rejection rolls back the approval's payment writes; failed verification or persistence leaves the review pending for retry.
  • Administrator cancellation/refund commits the order, audit, and durable outbox atomically. Queue dispatch is post-commit and recoverable from the outbox.
  • Payment scans use the order snapshot's asset and target, with a cache keyed by receiving method, asset, and target; connection failover never changes the asset.
  • Provider-event migration 0007_sparkling_wallflower.sql adds a nullable lease token without rewriting existing rows. A claim lasts five minutes (eight bounded 30-second EVM lookups plus headroom). Token and expiry checks fence accounting, completion, failure, and source-health updates; recovery invalidates old owners. Apply the normal D1 migrations before deploying the Worker; Bun applies migrations on startup. Old processing rows remain recoverable after their existing lease expires.

Public email registration is disabled. A newly deployed instance intentionally exposes /install so its operator can atomically create the first protected root user, and all later accounts must be created from the authenticated user-management screen by an actor with the required dynamic RBAC permission. Direct requests to Better Auth's email sign-up endpoint are rejected and do not create a user or credential account.

  • Monetary values cross boundaries as decimal strings and are converted to integer minor units or asset units represented by bigint before arithmetic.
  • GMPay requests require an enabled PID credential, a valid sorted-parameter HMAC-SHA256 signature, the exact scope, and a rate-limit check. The EPay compatibility boundary retains its legacy MD5 signature. D1 is the authoritative atomic rate-window counter; optional KV entries are observational caches and are never trusted for concurrent enforcement.
  • API credentials use explicit scopes: orders:create, orders:read, orders:update, and assets:read. Unknown or aggregate scopes are rejected.
  • API credential Secrets are encrypted with AES-GCM at rest and identified by a non-secret numeric PID. GMPay/EPay callbacks resolve the current credential Secret by order identity; there is no second callback-secret store.
  • Better Auth owns passwords and sessions. Passwords are length-validated but never trimmed or otherwise normalized before hashing. Root-user initialization is atomic under concurrent requests; ordinary user/credential creation and password-update/session-revocation pairs also commit through D1 atomic batches. Disabled users cannot create sessions, every disable path revokes existing sessions, and re-enabling clears the disable timestamp. Conditional D1 mutations prevent concurrent user-disable, user-delete, or role-removal requests from eliminating the last enabled root. Historical audit actors and settings editors become NULL when a user is deleted, preserving the records without dangling foreign keys. Sign-in is rate limited, and host, origin, CSRF, and Better Auth trusted-origin checks share the validated security.allowed_hosts list. Non-local hosts become HTTPS origins; localhost loopback entries use HTTP for development.
  • User and custom-role creation, updates, enable changes, deletion, password-change indicators, and role assignments record the authenticated actor, request ID, source IP, target, and a sanitized change summary. Password values and hashes are never written to audit metadata.
  • Runtime settings follow the single-deployment operating model and are stored as D1 values so authorized administrators can inspect and rotate them without redeploying. They are never exposed by public APIs; an empty update preserves the existing value. Audit metadata is recursively redacted when written and sanitized again when listed or exported; malformed payloads are never echoed verbatim.
  • Custom roles can be disabled without deleting their assignments. Server authorization joins only enabled roles on every request, so disabling a role removes its effective permissions immediately and re-enabling restores the existing bindings. Each role-module permission row stores one validated permission_mask; module and action registries are code-owned, and malformed modules or unknown bits fail closed. The built-in root role cannot be edited, deleted, or disabled.
  • TOTP setup remains unverified until the first valid authenticator code, preventing an abandoned QR setup from locking the user out. Backup codes are one-time, disabling TOTP requires the current password, trusted-device cookies are signed and bounded to 30 days, and repeated failed challenges trigger the configured 15-minute account lockout.
  • TOTP is optional. Password recovery uses Better Auth's one-time 15-minute email link, returns the same response for existing and unknown addresses, revokes every existing session after reset, and records the completed reset without storing the token or password.
  • D1 uniqueness constraints enforce credential PIDs, external order IDs within their API-credential scope, uncredentialed internal external-order IDs, user-role bindings, transaction events, Webhook events, and deliveries.
  • Merchant notify_url values require public HTTPS and reject local/private literals and embedded credentials before they are snapshotted with the order. Delivery rechecks resolved A/AAAA addresses, fails closed on private/reserved or mixed answers, does not follow redirects, streams at most 512 response bytes, and never logs signing material. Queue and DLQ messages use explicit allowlisted kind/version envelopes and contain only delivery/event identifiers; unknown, malformed, or unsupported versions are acknowledged without entering a handler. The consumer resolves the order payload and current API credential Secret from D1. The D1 attempt counter is authoritative and each failed application attempt schedules a fresh delayed Queue message, so the configurable application maximum is independent of a single Cloudflare message's retry limit. Cron-driven outbox recovery re-enqueues both initial queued deliveries and due failed deliveries after producer failures.
  • API Secrets are revealed only once at creation or rotation. Rotation updates the same credential row in place, preserves its PID, records actor/request/IP metadata without the Secret, and causes all later GMPay/EPay deliveries—including retries already queued by identifier—to resolve and use the new Secret from D1. No retired credential row is retained.
  • Each order owns its immutable notification target and creating API credential identity. Explicit notification resend creates a new event/delivery history row without creating a mutable global destination or broadcasting across orders.
  • Return URLs must use HTTPS. Runtime signing values are generated during installation and managed in the admin console. Payment connection API keys are stored in the separate AES-GCM encrypted credential table; legacy plaintext values migrate and clear on first use. Credentialed exchange and wallet calls are pinned to their built-in official origins, while configurable chain endpoints must be public HTTPS/WSS without user information. Payment-provider, Telegram, and API credential Secrets are encrypted before storage and never returned by list APIs. The runtime Better Auth secret must be backed up securely before moving a D1 database because changing it invalidates authentication material.
  • Public merchant, provider, Telegram, and checkout-review bodies have streaming byte limits. Every inbound HTTP attempt receives a new server receipt ID; a provider-supplied X-Request-ID is retained only as non-unique metadata.
  • Telegram Bot Tokens can be rotated without deleting subscriptions. A replacement is verified through getMe; enabled bots receive the existing secret-token Webhook on the new credential before D1 changes atomically, and the old credential's Webhook is removed afterward. If Telegram cannot clean up the old Webhook, the admin UI reports the partial cleanup and operators must revoke the old token with BotFather. Tokens are never returned by list APIs or written to audit metadata.
  • Creating an enabled Telegram Bot verifies getMe and registers its secret-token Webhook before the Bot and audit row commit atomically. Telegram rejection leaves no hidden D1 record; if the D1 commit fails after registration, GMPay Edge compensates by deleting the new external Webhook.
  • Telegram Bot enable and disable requests are idempotent. A real change updates D1 state and its before/after audit together; if that commit fails after Telegram changes the Webhook, GMPay Edge restores the previous external Webhook state.
  • Manual operations tasks require operations:update independently from the read-only health dashboard. Operators run exactly one bounded task—order expiration, Webhook outbox recovery, RPC health refresh, or exchange-rate synchronization—per request. Successful results are attributed to actor/request/IP; failures store only a stable error code and never the underlying provider response or exception text.
  • Checkout can change an asset/network only while the order is pending, unexpired, has no attributed payment or pending payer review, and has no active hosted-provider order. The D1 update is guarded by the order version and target-address availability; address reservation, old-address release, and audit rows are tied to the successful new version. Concurrent orders therefore cannot acquire the same final address.
  • Worker responses apply HSTS on HTTPS, frame denial, MIME sniffing protection, a strict referrer policy, a restrictive Permissions Policy, and same-origin resource policy.
  • Every JSON API success and error response sets Cache-Control: no-store and carries an X-Request-ID, preventing order, credential-state, validation, or infrastructure responses from being retained by browsers or intermediary caches.
  • Late payments follow the configured accept, review, or reject policy. Review payments remain outside the order balance until an authorized administrator explicitly accepts them, and every decision is audited.
  • Payment-review uploads require an exact same-origin Origin header and are rate limited per order and client address when KV is available. The server ignores the browser MIME claim, recognizes bounded JPEG/PNG/WebP bytes, validates basic image structure and dimensions, rejects files above 5 MiB, and records a SHA-256 digest.
  • Review evidence uses private R2 object keys. Only an enabled user with orders:read can access the Worker evidence route, which returns private, no-store, nosniff, and a sandboxed CSP. Approval requires the configured payment adapter to retrieve and match a transaction; screenshots never authorize a direct order-status override.
  • Review descriptions and evidence can contain personal information. Restrict orders:read, define a deployment-specific R2 lifecycle retention policy, and remove retained objects when decommissioning the instance.
  • Telegram order search, Inline creation, callbacks, and I have paid checks authorize the numeric sender ID against an enabled private subscription for the selected Bot. A group or channel Chat ID never grants private order access. Payment-check callbacks use the Telegram update ID as a D1 idempotency key before enqueueing a provider scan, so retries cannot amplify queue work. The button is only a scan request and never marks an order paid.
  • Amount-only Telegram Inline queries expose only enabled and ready receiving methods with a usable current quote. The selected method is encoded in the chosen result, validated again by the normal order service, and is not locked until Telegram sends chosen_inline_result. Forged or stale option IDs therefore fail closed.
  • Queue consumers validate the complete versioned envelope before dispatch. Unsupported or malformed messages are audited without their body or credentials and are acknowledged only after that audit succeeds, preventing poison-message retry loops without making rejected input invisible.
  • Merchant authentication verifies the sorted-parameter signature and required scope before incrementing the D1 rate window, so invalid signatures consume no rate state. An external order ID is unique within the creating API credential, preventing replay without coupling independent credentials.
  • API credential revocation is a one-way, first-writer-wins transition. D1 commits the revocation and its attributed audit in one batch; duplicate, unknown, or concurrent revocations cannot report success or append a misleading audit.
  • Administrative order cancellation is recoverably idempotent across its state and notification boundary. It commits the guarded state transition, receiving-target release, and attributed audit record in one D1 batch; a failed optimistic update writes none of them. Retrying an already-cancelled order idempotently persists any missing Webhook event without creating a second transition or audit entry.
  • Binance and OKX public exchange-rate polling requires no private credential. A stale observation is never used for a new order.
  • No private key or mnemonic is required by any receiving method or the simulator. Live provider credentials must never enter source control.