Security¶
mycashless is a payment platform — cardholder PII and stored balance are the
crown jewels. Card capture is delegated end-to-end to PCI-validated processor
iframes and SDKs (Stripe, Adyen, Mercado Pago, PayPal), so no PAN ever lands
in our application code, logs, or database. What we do hold — account
identity, balance, transaction history, refund payout details — is gated
behind tenant-aware FastAPI dependencies, scoped JWTs from cashless-auth,
and an audit trail in MySQL. This page is the public-facing posture
summary; the per-topic audits it links to are the source of truth.
Threat model¶
-
Actors
Cardholders, venue operators (
User), platform admins (Admin), paired POS devices, cluster cron, anonymous attackers. -
Assets
Cardholder PII, stored wallet balance, refund-payout instruments, operator + admin credentials, processor secrets.
-
Trust boundaries
Browser / mobile app ↔ GCE Ingress ↔ FastAPI service ↔ Cloud SQL. Service ↔ GCP Secret Manager via ESO. Service ↔ payment processor over the public internet, signed both ways.
-
Out-of-scope (delegated)
Card capture (processor iframes / SDKs), card storage (tokenized to
pm_*/pi_*/pspReference), card-present EMV decryption (Stripe Terminal P2PE).
The threat model assumes any authenticated actor can attempt to escalate
across the tenancy hierarchy (Vendor → Event → User). Every audit
below is framed against that assumption.
Audit status¶
Each row links to the internal audit document. Status reflects the audit's own conclusion as of its publish date — not a re-test.
| Audit | Status | Top finding | Link |
|---|---|---|---|
| OWASP Top 10 consolidation | ⚠ gaps | B− posture; legacy operator-token shape and outdated cryptography pin flagged for P0 remediation. |
security-audit-2026-05.md |
| Authorization (AuthZ) | ✅ clean | 599+ endpoints surveyed; zero missing-auth endpoints — every route either authenticated or intentionally public with a rate limit. | security-authz-audit.md |
| CSRF | ✅ clean | Bearer-token (JWT) auth only — CSRF is structurally not applicable. Unused Angular XSRF config flagged as dead code to remove. | security-csrf-audit.md |
| Multi-tenant isolation | ⚠ gaps | 97.7% of 786 routes verified tenant-safe; localized gaps on admin customer listing and modifier mutation routes. | security-multi-tenant-isolation-audit.md |
| PII redaction | ⚠ gaps | structlog pipelines lack global PII processors; several AuditLog.new_values writes persist email/phone/IP unredacted. |
security-pii-redaction-audit.md |
| Pydantic strict input | ⚠ gaps | 52.6% of request models declare extra="forbid"; cashless-auth request models at 0% — strictness work scheduled before launch. |
security-pydantic-strict-audit.md |
| Rate limit | ⚠ gaps | cm_v2 fully covered (default 600/min/admin + per-route overrides); pa_v4 and cashless-auth have unlimited endpoints flagged for default-limit rollout. |
security-rate-limit-audit.md |
| SQL injection | ✅ clean | 6,368 Python files scanned, 47 dynamic-SQL patterns triaged — zero unsafe constructions. All text(...) uses bound parameters. |
security-sql-injection-audit.md |
| XSS (frontends) | ✅ clean | 6 [innerHTML] bindings across four Angular apps — all routed through DomSanitizer.sanitize(SecurityContext.HTML, …). No bypassSecurityTrust*, no eval. |
security-xss-audit.md |
Active findings
The ⚠ audits above carry open work items tracked in the consolidated
security-audit-2026-05.md remediation table. The summary table at the
top of that document lists the next-up P0 / P1 items and rough effort
estimates. None of the items are runtime-exploitable in production today
(the platform is pre-launch), but they are blockers on the path to
launch.
PCI posture¶
Card data is handled exclusively by PCI-DSS Level 1 processors. Application code only ever sees opaque tokens; no PAN, no CVV, no expiry, no track data crosses any service boundary we own.
| Capture surface | Tokenization layer | What our app receives | Scope |
|---|---|---|---|
| Web e-commerce (Stripe) | Stripe Elements iframe | pm_* payment-method ID |
SAQ A |
| Web e-commerce (Adyen) | AdyenCheckout drop-in iframe | pspReference |
SAQ A |
| Web e-commerce (Mercado Pago) | MP Bricks / Checkout-Pro hosted page | MP payment_id, preference_id |
SAQ A |
| Web e-commerce (PayPal) | PayPal Smart Buttons hosted iframe | PayPal order_id, capture_id |
SAQ A |
| Mobile e-commerce (iOS / Android) | Stripe SDK PaymentSheet | pm_* |
SAQ A |
| Apple Pay / Google Pay | OS-level DPAN via Stripe Elements | pm_* |
SAQ A |
In-person card present (mPOS) |
Stripe Terminal SDK + P2PE-listed reader | pi_* / charge_id |
SAQ B-IP / P2PE |
| Inbound processor webhooks | Signed HTTPS callbacks (Stripe, Adyen, MP, PayPal HMAC) | Token IDs + amounts | Out-of-CDE |
What lives in our database is intentionally narrow: transaction.last_four
(four digits — PCI-permitted display data), stripe_customer_id,
payment_method token references, and pspReference-style processor IDs.
The recommended SAQ scope is SAQ A for the e-commerce flows and SAQ
B-IP (or P2PE if attestation is available) for the mPOS in-person
flow — both to be confirmed with the acquirer.
Full PCI walk-through, requirement-by-requirement evidence, and the
recommendation queue:
pci-posture-2026-05.md
(supersedes the earlier
security-pci-posture.md).
Pre-launch PCI work
Legacy Flask runtimes (cm/, pa/) still expose refund-payout fields
that overlap with PCI scope on paper. The launch cutover plan retires
those public routes before SAQ submission and runs the column-drop
migration in the same window. Tracked in pci-posture-2026-05.md
finding 4.1.
GDPR posture¶
The platform was designed around LATAM (Argentina, Mexico) but Art. 3(2)(a) extra-territorial scope applies the moment we accept EU data subjects. Lawful-basis mapping is documented per-field; data-subject rights are partially implemented and tracked.
| Data-subject right (Art.) | Status | Surface / endpoint |
|---|---|---|
| Art. 15 — access (SAR) | ⚠ planned | No /v4/customers/me/data-export yet — backlog. |
| Art. 16 — rectification | ✅ partial | PUT /v4/users/me for editable fields; name-change goes through an ops workflow. |
| Art. 17 — erasure | ⚠ partial | DELETE /v4/users/me exists and nulls top-level PII; cascade-redaction across EventConnection + denormalized Transaction.user_email is open work. |
| Art. 18 — restriction | ⚠ planned | Out-of-band via privacy@mycashless.com (TBD — see below). |
| Art. 20 — portability | ⚠ planned | Piggy-backs on the Art. 15 export. |
| Art. 21 — object (marketing) | ✅ implemented | Customer.send_email / send_sms / send_app_notifications flags; cookie banner on all four frontends with explicit Decline. |
| Art. 22 — automated decisions | n/a | No profiling or automated individual decisions. |
Lawful-basis matrix (engineering inference; legal counsel owns the authoritative Art. 30 RoPA):
| Category | Lawful basis |
|---|---|
| Account identity (name, email, phone, password) | Art. 6(1)(b) — contract |
| Profile (address, language, photo) | Art. 6(1)(b) where event-required, otherwise Art. 6(1)(a) — consent |
Health-adjacent (allergy) |
Art. 9(2)(a) — explicit consent (special category) |
| Emergency contact | Art. 6(1)(d) — vital interests |
National identifiers (id_number, clabe_account) |
Art. 6(1)(c) — legal obligation (refund payouts) |
| Marketing flags | Art. 6(1)(a) — consent |
| Transaction history | Art. 6(1)(b) + Art. 6(1)(c) — financial records |
| Audit / security logs | Art. 6(1)(f) — legitimate interests |
Full GDPR walk-through, retention-matrix proposal, and DSAR-intake plan:
gdpr-posture-2026-05.md
(supersedes the earlier
security-gdpr-posture.md).
Retention + erasure remediation
No automated retention purge job runs today, and the erasure path only
nulls a subset of PII columns. Both are launch-blocking for EU-subject
traffic and tracked as HIGH-severity items in gdpr-posture-2026-05.md
§6.1 / §6.3.
Tenant isolation model¶
Tenancy is hierarchical and enforced at the FastAPI dependency layer, not in raw queries:
Vendor
└─ Event
└─ User (operator) / Customer (cardholder)
└─ Transaction · Transfer · Refund · Token · NFC chip
Three primitives cover the matrix:
| Dependency | Caller identity | Scope rule |
|---|---|---|
require_tenant_access |
Admin |
Admin must be connected to the target event_id via EventConnection. |
require_user_event_access |
User (operator) |
Operator JWT is pinned to a single event_id; mismatch → 403. |
CurrentCustomer |
Cardholder | Resolves the JWT sub; downstream queries filter by customer.id. |
The row-level rule across cm_v2 and pa_v4 is: every state-mutating
query loads the target row, derives its event_id (directly or via the
nearest parent), and calls the matching require_*_access predicate
before mutation. The multi-tenant isolation audit covers 786 routes —
security-multi-tenant-isolation-audit.md
documents the patterns plus the localized gaps still on the remediation
queue.
Customer-scoped reads in pa_v4 follow Pattern 4 of the audit: the
CurrentCustomer dependency resolves the cardholder from the JWT sub
claim and post-load filters scope to customer.id — there is no
admin-style cross-event traversal on the cardholder surface.
Reporting a vulnerability¶
No published disclosure policy yet
There is no SECURITY.md in the parent monorepo as of 2026-05-12 and
no public bug-bounty program. TBD — a security@mycashless.com
mailbox + 90-day coordinated-disclosure policy is on the pre-launch
backlog and will land here.
In the interim, route security reports through the platform owners on the
parent monorepo
(Cashless-Media/platform)
via a private GitHub Security Advisory rather than a public issue. Do
not include exploitable proof-of-concept payloads in any public-facing
channel until the policy lands.
See also¶
- Architecture — service boundaries, payment-processor inventory, deploy flow.
- API reference — Swagger / OpenAPI for every authenticated surface.
- Operations — cluster topology, secret-manager wiring, runbook index.
OPERATIONS.md— full cluster runbook.