SECURITY_MODEL — payment-gateway-service
Sibling: DATA_MODEL · API_CONTRACTS · SYNC_CONTRACT · AI_INTEGRATION
Strategic anchors: 07 Security, Compliance & Tenancy · 10 Payments Architecture · ADR-0002 Multi-tenancy
This document defines the security posture of payment-gateway-service. The single most important property is that the platform stays in PCI-DSS SAQ A scope: no card data (PAN, full magnetic stripe, CVV/CVV2, PIN/PIN block) ever traverses or rests on Ghasi infrastructure. Card capture is delegated entirely to processor-hosted UI (Stripe Elements, PayPal hosted fields, HesabPay hosted page); we only ever see and store opaque processor tokens plus display metadata (brand, last4, expiry month/year).
1. PCI-DSS scope
1.1 SAQ A applicability
We qualify for SAQ A because all of the following hold:
| Criterion | Evidence in this service |
|---|---|
| Card data accepted only via processor-hosted UI | API §4 — clientSecret flow; no endpoint accepts PAN |
| No card data stored, processed, or transmitted by us | DATA_MODEL §2.6 — only encrypted opaque tokens; CI scanner blocks PAN-shaped patterns |
| All processing, storage, transmission outsourced to PCI-DSS validated providers | Stripe (L1), PayPal (L1), HesabPay (regional validation) |
| Only e-commerce channel | No POS or card-present hardware on our servers; cash-on-arrival is non-card |
| Service-provider compliance evidence on file | Vendor AOCs collected annually by tenant-service; pointers in vendor_credentials_pointer.account_ref |
1.2 Out-of-scope by design
- No
pan,card_number,full_pan,cvv,cvc,cv2,track1,track2,pin,pin_blockcolumns in any schema, log, event payload, or test fixture. - A repository-wide grep gate (
pnpm pci:scan) fails CI on any commit introducing those identifiers, including in comments. - Logs go through a redaction proxy that rejects anything matching
\b(?:\d[ -]*?){13,19}\bwith Luhn validity — the service emits aMELMASTOON.PAYMENT.PAN_EXPOSURE_BLOCKEDerror and pages the on-call security engineer.
1.3 Annual evidence
- Quarterly ASV scan of
webhooks.payments.melmastoon.ghasi.ioandapi.melmastoon.ghasi.io. - Annual SAQ A self-assessment, signed by the SecOps lead.
- Annual penetration test of the public surface (separate from the ASV scan).
2. Tenant isolation
2.1 Schema-per-tenant enforcement (per ADR-0002)
- Each tenant has its own Postgres schema
tnt_<tenantId>. - The application connects as role
payments_app, which has no GRANTs on any tenant schema. - Every request opens a transaction and calls
payments_central.set_tenant_context(tenantId), aSECURITY DEFINERfunction that:- Validates the
tenantIdagainsttenant_schema_registry. - Sets
search_path = tnt_<id>, payments_central, publicfor the transaction. - Sets a session GUC
app.tenant_id = '<tenantId>'. - Issues
SET LOCAL ROLEto a per-tenant role withUSAGEon that schema only.
- Validates the
- Cross-tenant query is structurally impossible: even a SQL-injection finding cannot reach another tenant's tables because the connection role lacks the privilege.
2.2 Tenant resolution at the edge
iam-serviceissues JWTs containingtenantIdin a signed claim.- The API gateway rejects requests where header
X-Tenant-Id≠ JWTtenantIdwithMELMASTOON.TENANT.NOT_A_MEMBER. - Webhook receivers resolve
tenantIdfrom the processor-issued event metadata (e.g., Stripeaccountfield on Connect events, or per-tenant secret index for HesabPay) and persist it on the inbox row. Mis-resolved webhooks are quarantined to DLQ.
3. Vendor credential management
3.1 Storage
- All vendor secrets (API keys, webhook signing secrets, OAuth refresh tokens) live in GCP Secret Manager, namespaced as
payment-gateway-service/<tenantId>/<processor>/<env>/<secret_name>. - The application receives only a secret URI, resolved at request time and cached in process for at most 5 minutes.
- Workload Identity binds the GKE service account to a Google service account with
secretmanager.secretAccessorscoped to that namespace prefix.
3.2 Rotation
- Webhook signing secrets rotate on a 90-day schedule, with overlapping validity windows (24 h grace) so neither vendor nor platform sees an outage.
- Application API keys rotate on a 365-day schedule, or immediately on any HSM-detected anomaly.
- Rotation is automated via
tenant-serviceand tracked inaudit-log-service.
3.3 Tenant-deleted state
- Tenant deletion triggers
secrets:disableon all related secrets (24-h grace), thensecrets:destroyafter the 30-day soft-delete window. The corresponding row invendor_credentials_pointerisenabled = FALSE.
4. Webhook security
4.1 Signature verification
- Each receiver loads the per-tenant webhook secret from Secret Manager.
- Signature verification runs on the raw request body before any parsing. Tampered or replay-suspicious signatures (timestamp drift > 5 minutes from server clock) are rejected with
MELMASTOON.PAYMENT.WEBHOOK_SIGNATURE_INVALID. - The receiver never trusts a signature for tenant resolution; tenant resolution uses an out-of-band field (account id) and is then cross-checked against the secret used for verification.
4.2 Replay protection
webhook_inboxhas a unique constraint on(processor, external_event_id); duplicates are dropped silently and reported aswebhook.duplicate_dropped.v1.- A
Date/Idempotency-Keywindow of 24 h is enforced; older signed events are accepted only via a manual admin endpoint with explicit operator approval.
4.3 Network controls
- Webhook receivers live on a separate subdomain behind Cloud Armor with:
- WAF policies blocking known malicious bots and OWASP Top 10 patterns.
- IP allowlists per processor (Stripe, PayPal, HesabPay publish their CIDRs; pulled hourly into the policy).
- Per-tenant rate limit of 1000 events/min; bursts beyond that quarantine to DLQ.
5. Authentication, authorization, RBAC
5.1 AuthN
- Inter-service calls: mTLS plus JWT (workload-issued by
iam-service) carrying caller service identity. - Operator calls (BFFs): JWT with user identity, tenant id, property scope, and roles.
- Webhook calls: vendor signature only — no JWT.
5.2 AuthZ (RBAC)
| Action | Required permission |
|---|---|
POST /payments/intents | payments.intent.create (system or front_desk_manager) |
POST /payments/intents/:id/refunds | payments.refund.create (front_desk_manager or accountant) |
POST /payments/cash/receipts | payments.cash.receive (front_desk_clerk+) |
POST /payments/cash/refunds (above tenant threshold) | payments.cash.refund.elevated + dual sign-off (two operators with this perm) |
POST /payments/reconciliations/run | payments.reconciliation.run (accountant or tenant_admin) |
POST /payments/chargebacks/:id/evidence | payments.chargeback.submit (accountant or tenant_admin) |
Permission resolution is delegated to iam-service via the standard policy-decision call; we cache positive decisions for 60 s, negative for 0 s.
6. Input validation & abuse
- All bodies validated against Zod schemas; unknown fields rejected (strict mode).
- Idempotency-key reuse with different body returns 409 (no replay-attack laundering).
- Rate limiting: per-tenant 30 RPS authorize, 10 RPS refund, 100 RPS reads. Exceeding triggers
MELMASTOON.RATE_LIMIT.EXCEEDED. - Numeric overflow:
amountMicroisbigint; values > 10^15 micro units rejected as invalid.
7. Encryption
| Data | At rest | In transit |
|---|---|---|
payment_methods.processor_token_enc | Envelope-encrypted with per-tenant CMEK in Cloud KMS; rotated annually | TLS 1.3 only |
webhook_inbox.raw_payload_ref (GCS) | CMEK on bucket; bucket lifecycle to Coldline at 30 days | TLS 1.3 only |
| Cloud SQL backups | CMEK | TLS 1.3 |
| Pub/Sub messages | Google-managed encryption + per-message envelope for high-sensitivity events | TLS 1.3 |
| Local desktop SQLite | SQLCipher AES-256, key sealed in OS keychain | n/a |
8. Logging & observability hygiene
- Structured JSON logs only; never
console.log. - A central log filter strips fields named
cardNumber,pan,cvv,cvc,processorToken,clientSecret,apiKey,webhookSecretregardless of nesting depth. - Trace spans never include request bodies; only field names and shapes.
- Audit-relevant events flow to
audit-log-servicevia a dedicatedoutboxtopic and are immutable for 7 years.
9. Threat model (highlights)
| Threat | Mitigation |
|---|---|
| Compromised tenant credentials | Workload Identity + signed JWTs; secrets never on disk; IP allowlisting on admin endpoints |
| Webhook spoofing | Signature verification + IP allowlist + replay window |
| Tenant boundary bypass via SQLi | Schema-per-tenant + role separation + parameterized queries via Drizzle ORM |
| Insider exfil of tokens | Tokens encrypted with CMEK; access requires elevated break-glass procedure with paged approval |
| Operator double-charge attempt | Idempotency-Key required; same-body duplicates collapse, different-body collisions rejected |
| Card-not-present fraud | Optional 3DS at intent time; AI-assisted post-authorize scoring (advisory only) |
| Refund-policy bypass via direct API call | Refunds always include reason; reservation-service policy decisions are logged and required as audit.policyDecisionId |
| PAN exposure attempt | Logged as MELMASTOON.PAYMENT.PAN_EXPOSURE_BLOCKED; CI scanner; redaction proxy; SAQ A scan |
10. Incident response
- Suspected card data exposure: invoke the PAN exposure runbook (paged P0). Stop affected adapter, snapshot logs, notify processors within 24 h, notify SecOps and legal, run a forensic schema dump under 4-eyes.
- Webhook signing secret leak: rotate immediately, replay last 24 h of events from raw inbox storage to validate state, audit for fraudulent state changes.
- Tenant cross-talk: theoretical; the schema-per-tenant model + role separation makes a hard break required to surface this. Runbook still exists: freeze writes, snapshot, restore from backup, re-issue all sessions.
11. Compliance with regional regulations
- EU/UK GDPR — guest payment metadata is personal data; subject access requests served via
tenant-serviceSAR pipeline; right to erasure honored except for legal-hold financial records (10-year retention overrides). - MFS markets (e.g., Afghanistan, Pakistan): adhere to local KYC and AML reporting where Ghasi acts as a payment facilitator (we do not — we are a merchant on record's processor); any transition to facilitator status triggers a re-scope to PCI SAQ D and a separate compliance program.
- Sanctions screening: out of scope for this service; processors handle OFAC at their layer. Future: a
compliance-servicemay pre-screen high-value transactions.
12. Boundary with iam-service, tenant-service, audit-log-service
iam-serviceowns all human and machine identity, JWT issuance, RBAC policy decisions.tenant-serviceowns tenant lifecycle including schema provisioning and Secret Manager namespacing.audit-log-serviceis the canonical sink for security-relevant events; this service is a producer only.