Security

Tenant isolation you can verify.

Framique runs on self-hosted Postgres with row-level security on every tenant table, scoped and rotatable API keys, and an audit trail you can export. This page explains the mechanisms, not just the promises.

request → session → membership → policy → row

The tenancy isolation model

The database says no before the row leaves storage.

For a non-engineer

Every merchant's orders, customers, products and payouts live in the same physical database as every other merchant's — that is normal, efficient, and how almost every serious SaaS platform works. What matters is what stops one merchant's application code, one buggy report, or one careless staff query from ever returning a row that belongs to someone else.

We do not rely on our own code remembering to add a filter to every query, on every route, forever, across every engineer who ever touches the codebase. That approach fails eventually — not because engineers are careless, but because “remember to add a filter every time” is a rule a human will eventually forget under deadline pressure, and forgetting it once is a data breach.

Instead, the database itself refuses to return a row unless the request is provably allowed to see it. The check lives in Postgres, underneath the application, so even a route that has a bug, a report that joins tables incorrectly, or a new hire's first pull request cannot leak another merchant's data. This is called row-level security (RLS), and it is turned on for every table that holds merchant data. A table with no isolation policy returns nothing to anyone — a loud failure we notice immediately, not a silent leak we discover later.

For an engineer

  • Every tenant-scoped table carries a merchant_id column and an ENABLE ROW LEVEL SECURITY policy. There is no opt-out table for tenant-scoped “internal” data.
  • Policies call security-definer helper functions — is_merchant_member(), has_merchant_role(), staff_has(), is_platform_admin() — rather than inlining logic per policy, so a fix or audit touches one function, not forty policies.
  • Roles never live on the profiles row. Entitlement lives in a separate merchant_members(user_id, merchant_id, role) table, so revoking access is a delete on a membership row — auditable, reversible, and impossible to confuse with “the user no longer exists.”
  • The request path is uniform for every actor, including our own staff: request → session identity (GoTrue JWT) → merchant membership lookup → row-level policy evaluation → row. There is no back-door service-role connection wired into a dashboard.
  • Isolation is a test suite, not a design intention: a negative-assertion end-to-end spec runs on every release — logged in as merchant A, every attempt to read, list, update or delete a row belonging to merchant B must fail.
  • GRANTs are explicit per role in addition to policies. A table with RLS enabled but no grant for a role is unreachable by that role at the connection layer, before policy evaluation even runs.
is_merchant_member()has_merchant_role()staff_has()is_platform_admin()

Request path

  1. Request
  2. Session
  3. Membership
  4. Policy
  5. Row

Every request walks this path, including our own staff tooling. The policy node is Postgres row-level security evaluating whether the caller may see the row at all — a check the application cannot bypass by forgetting a filter.

Where each isolation control lives
Where each isolation control livesMechanismFails how, if misconfigured
ConnectionExplicit GRANT per role, per tableNo grant → connection-level denial, before RLS even evaluates
RowENABLE ROW LEVEL SECURITY + policy per tableNo policy → zero rows returned to any role, a loud break not a leak
Policy logicSECURITY DEFINER helper functions, not inlined per-policy SQLCentralised, so a fix or audit touches one function, not forty policies
Identity vs entitlementmerchant_members(user_id, merchant_id, role) — never a column on profilesRevocation is a row delete; escalation cannot happen through a profile update
Privileged operationsService role loaded inside the handler, after caller verification, audit-loggedNever the default client; never reachable before authorization
Regression protectionNegative-assertion E2E suite as a release gateA broken policy fails CI, not a customer's trust

Authentication and session handling

সেশন টোকেন সংক্ষিপ্ত মেয়াদী, রিফ্রেশ টোকেন প্রতি ব্যবহারে রোটেট হয়, এবং প্ল্যাটফর্ম-অ্যাডমিন অ্যাকাউন্টে দুই-স্তর যাচাই বাধ্যতামূলক।
  • Short-lived access, revocable refresh

    Access tokens are short-lived; a stolen one has a narrow window before it expires and must be refreshed against a token that is itself revocable. Refresh tokens are rotated on use and stored in an httpOnly, Secure, SameSite=Lax cookie — never in localStorage.

  • Server-side session revocation

    Sessions can be revoked server-side — password change, “log out everywhere,” suspected compromise, staff offboarding — without waiting for natural expiry.

  • No readable passwords

    Passwords are hashed with a modern adaptive hash inside GoTrue; we never see or store a plaintext password, and support staff cannot look one up because there is nothing readable to look up.

  • Two-factor authentication

    Optional TOTP is available for merchant accounts and required for platform-admin accounts — the small set of internal roles with cross-merchant visibility.

  • Membership re-derived, not cached

    Every authenticated request re-derives merchant membership from the database on the request path, so a revoked member cannot keep acting on a stale token until it expires.

  • Edge rate limiting

    Brute-force and credential-stuffing protection sits at the edge ahead of the auth service, keyed by IP and by account, with backoff rather than a hard lock a bad actor could use to lock out a legitimate merchant.

Roles and least-privilege permission matrix

Every staff member operating inside a merchant's account holds exactly one role per merchant, and every route/serverFn checks a specific permission via staff_has(permission) rather than a role name directly — so a permission can be re-assigned across roles without touching call sites.

অর্ডার ও গ্রাহক দেখুন · পণ্য ও ইনভেন্টরি সম্পাদনা · রিফান্ড ইস্যু করুন · স্টাফ ও ভূমিকা পরিচালনা · পেআউট অ্যাকাউন্ট বিবরণ
Roles and least-privilege permission matrix
Roles and least-privilege permission matrixOwnerManagerStaffSupport (viewer)Platform admin
View orders & customersread-only, auditedaudited, break-glass
Edit products & inventory not granted not granted
Issue refundslimit-capped not granted not granted
Manage staff & roles not granted not granted not granted not granted
View payout account detailsmasked not granted not grantedmasked
Rotate / revoke API keys not granted not granted not granted
Export customer data not granted not grantedaudited, on request only
Delete store / close account not granted not granted not granted not granted
Access billing & plan not granted not granted not granted not granted
Cross-merchant visibility not granted not granted not granted not grantedaudited, scoped, time-boxed
  • Masked payout details: a manager can confirm a payout method is on file and its last four digits, never the full account number.
  • Refunds, limit-capped: staff can issue refunds up to a merchant-configured ceiling; anything above it requires a manager or owner.
  • Platform-admin cross-merchant visibility is not a standing permission — it is granted per-incident, time-boxed, and every access is written to the audit log with the justifying ticket ID. There is no always-on “view any store” toggle.

API key scoping and rotation

API keys authenticate server-to-server integrations — custom apps, couriers, accounting exports — not end users. Each key is scoped to a single merchant; there is no platform-wide key that reaches across tenants.

Scope it

Explicit scopes at creation (orders:read, inventory:write, webhooks:manage). A key minted for a shipping integration cannot read payout details because the scope was never granted.
orders:read · inventory:write · webhooks:manage

Watch it

A last-used timestamp and calling IP range show in the dashboard, so an owner can spot a key being used somewhere unexpected before it becomes an incident.
last-used timestamp + IP range, visible per key

Rotate it

Rotate without downtime: a new key issues alongside the old one, the old one revokes once traffic has moved. Revocation is instant — no propagation delay measured in hours.
recommended cadence: 90 days, or immediately on suspicion

Keys are stored hashed, not in reversible form, so a database compromise does not hand over usable keys — the same principle applied to passwords.

Secret handling — what we never log

Secrets — API keys, session tokens, payment credentials, service-role keys, webhook signing secrets — follow one rule: read inside the handler that needs them, never at module scope, never passed further than necessary.

টোকেন, কার্ড নম্বর ও অথরাইজেশন হেডারের মতো তথ্য কখনো লগে, এরর বার্তায় বা মেট্রিক লেবেলে প্রকাশ পায় না — লগ লেখা হওয়ার আগেই তা স্ক্রাব করা হয়।
  • Never: a secret survives into a log line — scrubPayload / scrubText redact known secret-shaped values before a line leaves the process.
  • Never: a secret appears in an error message returned to a browser, a Sentry error body, or a metric label.
  • Never: a secret sits in a URL query string that could end up in access logs or browser history.
  • Never: a secret is committed to the repository — environment secrets are read at runtime, and a pre-commit / CI secret-scan step exists to catch the case where someone tries anyway.
  • Never: a client bundle ships a secret — bundles are scanned for accidental inclusion before release.
  • Never: a merchant is asked for their password “to help debug.” Support access goes through the audited, time-boxed platform-admin path, not credential sharing.

Payment data handling

Payment data handling and tokenisation boundary

Framique supports cash on delivery, mobile financial services (bKash, Nagad, Rocket), bank transfer and BNPL, routed through an in-house payments aggregator with provider adapters behind one idempotent charge / refund / payout contract.

  • Card and MFS credentials are handled by the payment processor's own hosted flow, not typed into a Framique-controlled form field — the sensitive credential never transits our application servers.
  • What we store is a reference token, masked details (last four digits, method type) and transaction state — never enough to replay a charge or reconstruct the original credential.
  • Idempotency is structural, not best-effort: charges, refunds and payouts are keyed through Redis/DB idempotency keys, so a network retry or a doubled webhook resolves to the original result rather than a second charge.
  • Money is stored as currency_code plus integer minor units (paisa), never a float.
  • Webhook deliveries are HMAC-signature verified before any privileged read or write happens on their contents.
  • Refund authority is capped per role so a single compromised low-privilege account cannot issue unlimited refunds.
No PCI attestation held today — see the roadmap band
BuyerProcessor-hosted flowTokenFramique

Roadmap: A formal PCI attestation is not something we hold today and this page does not claim one; our architecture keeps raw card data off our servers precisely so that scope stays as small as an eventual assessment would need it to be.

Encryption in transit and at rest

  • In transit

    TLS terminates at the OpenResty edge with ACME-managed certificates auto-renewed ahead of expiry; internal service-to-service traffic runs inside a private network boundary, not exposed to the public internet.

  • At rest

    Postgres volumes and storage buckets are encrypted at the disk layer; recoverable secrets and credentials use envelope encryption rather than a single static key baked into configuration.

  • Backups

    Backups inherit the same at-rest encryption as the primary store — a stolen backup is not a shortcut around the controls on the live database.

  • Security headers and CSP

    Enforced on every response: a strict Content-Security-Policy, Strict-Transport-Security, X-Content-Type-Options, and a locked-down frame-ancestors to prevent clickjacking of merchant admin surfaces.

  • Rate limiting

    Applied on all public, unauthenticated endpoints (webhooks, storefront checkout, auth) to shed abusive traffic at the edge before it reaches application logic.

Backups, retention and restore testing

A backup that has never been restored is a hope, not a control. Backups run hourly, retained on a rolling window sized for point-in-time recovery, with restore drills rehearsed into an isolated environment and verified against expected row counts before a drill is called successful.

BackupEncryptStoreScheduled restore drillVerify
Retention by data class
Retention by data classBackup frequencyRestore drill cadenceDeletion on request
Transactional (orders, payments)HourlyScheduled, documentedRetained per legal tax record requirement, then purged
Operational (products, inventory, staff)HourlyScheduled, documentedDeleted on confirmed request
Observability (metrics/logs/traces)N/A — bounded retentionN/AAged out automatically
Customer PII (profile, address)HourlyScheduled, documentedDeleted on confirmed request, subject to legal hold if applicable

Observability and alerting contract

Our observability stack is entirely self-hosted — Prometheus, Grafana, Loki/Promtail, Sentry, Alertmanager — for the same reason our data layer is self-hosted: no vendor can throttle our visibility into our own platform, and retention is a decision we make, not a plan tier we buy.

Observability signals
Observability signalsWhat it capturesPathRetention
MetricsRequest rates, error rates, latency, queue depthApp → Prometheus scrape (30s)30 days / 20GB
LogsStructured JSON, PII-scrubbed, carrying trace_id/span_idApp stdout → Promtail → Loki30 days
Errors & tracesExceptions and distributed tracesApp → self-hosted SentryPer configured Sentry quota
AlertsBurn-rate and threshold rulesPrometheus → Alertmanager → PagerDuty / Slack
GrafanaLokiSentry
  • severity=page is reserved for user-visible loss or imminent data risk. Everything else opens a ticket, not a page.
  • Every alerting rule carries a runbook annotation — the person paged at 3am gets a link to the exact steps, not a bare metric name.
  • Pages are driven by burn-rate, not raw error counts, so a brief self-healing blip does not wake anyone.
  • The stack watches itself: an exporter down or ingestion stalled is its own alert — the monitoring system failing silently is treated as seriously as the product failing.
Storefront availability 99.9%P95 checkout settle under 2.5 secondsIngest-to-visible analytics lag under 5 minutes

Incident response

Incident response runbook

On-call classifies severity within minutes of acknowledgement, not after full root-cause is known. The fastest safe action that stops ongoing harm — revoke a key, disable a route, roll back a deploy — happens before root cause is fully understood. Affected merchants are notified directly for anything SEV-1 or SEV-2 with merchant impact: what we know, what we don't yet know, and what we're doing next.

  • Affected merchants are notified directly — email plus in-dashboard notice — not left to discover impact themselves.
  • Timelines in the severity table are commitments, not aspirations; if we miss one, the post-mortem says so.
  • We disclose what we know when we know it, and follow up as the picture completes.
  • A material incident gets a public-facing write-up; we do not go quiet after the immediate fire is out.
SEV-1: direct notification within 1 hour of confirmation
  1. 1Detect
  2. 2Triage & assign severity
  3. 3Contain
  4. 4Notify affected merchants
  5. 5Eradicate & fix
  6. 6Recover
  7. 7Public/customer write-up
  8. 8Post-mortem
গুরুতর ঘটনায় (SEV-1/SEV-2) প্রভাবিত মার্চেন্টদের সরাসরি জানানো হয় — ইমেইল ও ড্যাশবোর্ড নোটিশে — নির্ধারিত সময়সীমার মধ্যে।
Incident severity classification
Incident severity classificationDefinitionExamplePaged?First merchant update
SEV-1Data breach, cross-tenant data exposure, or platform-wide outageRLS bypass discovered; checkout down platform-wideImmediate page, on-call + security leadWithin 1 hour of confirmation
SEV-2Significant degraded service or a contained security issue affecting a subset of merchantsElevated checkout error rate; one integration's key leakedImmediate page, on-callWithin 4 hours
SEV-3Limited-impact bug or a vulnerability with no evidence of exploitationA dependency CVE with no known exploit path in our usageTicket, next business dayIncluded in routine disclosure if applicable
SEV-4Cosmetic or non-security operational issueA dashboard chart mislabels a unitTicketNot applicable

Vulnerability disclosure policy

We welcome good-faith security research and would rather hear from you first.

  1. 1Email security@framique.com (placeholder pending final domain configuration) with a description, reproduction steps and impact assessment.
  2. 2Do not test against live merchant stores you do not own or operate; use a test account or ask us to provision one.
  3. 3Give us a reasonable window to investigate and remediate before any public disclosure — we aim to acknowledge within 2 business days and provide a remediation timeline within 10 business days for confirmed issues.
  4. 4We will not pursue legal action against good-faith research conducted under this policy.
security@framique.com
In scope
The production application and API surfaces at *.framique.com and merchant subdomains.
Out of scope
Third-party subprocessor infrastructure (report to them directly), denial-of-service testing, and social engineering of staff or merchants.

Out of scope for now: a paid bug bounty program is not yet running (see the roadmap band); we still want the report, and we will credit researchers publicly on request.

Dependency and supply-chain scanning

  • Automated dependency scanning runs against every change, flagging known-vulnerable packages before merge.blocks release
  • A confirmed high or critical severity finding with a known exploit path blocks release until patched or explicitly risk-accepted by a named engineer, in writing, with a remediation deadline.blocks release
  • Lockfiles are committed and CI verifies the resolved dependency tree matches the lockfile.verified in CI
  • Client bundles are scanned before release for accidental inclusion of server-only code or secrets.blocks release
  • Base images and infrastructure containers are pinned to specific versions rather than tracking latest.pinned, not latest
  • Internal code review requires at least one other engineer's approval before merge to main.reviewed change

Self-hosting and data residency options

Framique is built self-hosted-first: Postgres, Redis, auth, storage and the entire observability stack run on infrastructure we control, and the application only ever talks to them through swappable, provider-agnostic connection strings — nothing in the application code names a specific hosting provider.

ফ্রেমিক শুরু থেকেই সেলফ-হোস্ট-প্রথম — পোস্টগ্রেস, রেডিস, অথ ও পর্যবেক্ষণ স্ট্যাক আমাদের নিয়ন্ত্রণাধীন অবকাঠামোয় চলে, এবং প্রয়োজনে নির্দিষ্ট আঞ্চলিক বা সম্পূর্ণ সেলফ-হোস্টেড স্থাপনার বিকল্পও আলোচনাসাপেক্ষ।

Standard hosting

Your data runs on our managed self-hosted infrastructure, located and operated to support Bangladesh-first residency expectations.

Dedicated / regional hosting

For merchants with a specific residency requirement, infrastructure can be provisioned in a specific region on a case-by-case basis — contact us to scope this before committing to a contract.

Fully self-hosted (enterprise)

A merchant with the operational capacity to run their own Postgres, Redis and observability stack can deploy Framique on infrastructure they own and control entirely. Discussed directly with our team, not a self-serve toggle.

Subprocessor transparency table

We minimise third parties in the data path by design — most of the stack is self-hosted precisely to avoid an ever-growing subprocessor list. Full, current subprocessor names and jurisdictions are listed in our data processing agreement, kept current as agreements change; this page describes the categories and boundaries.

Subprocessor categories
Subprocessor categoriesPurposeData involvedLocation commitment
Payment processing (MFS/bank/BNPL partners)Processing charges, refunds and payouts initiated by the merchantTransaction reference, amount, masked payment detailsPer processor's own regulatory jurisdiction; raw credentials never transit our servers
Courier partnersFulfilling shipments the merchant createsRecipient name, address, phone, order referenceBangladesh-based courier operations
Infrastructure hostingRunning self-hosted Postgres, Redis, and the applicationAll merchant data, self-hostedPer hosting/residency option selected
Email/SMS delivery (transactional)Order confirmations, OTPs, account notificationsRecipient contact detail, message contentDisclosed in the data processing agreement on request

Full, current subprocessor names live in our legal documents, the contractual source of truth.

Your side of the security checklist

Security is shared: we harden the platform, and these ten habits close the gaps only the merchant controls.

আপনার পক্ষের নিরাপত্তা চেকলিস্ট
  1. Turn on two-factor authentication for every staff account with owner or manager access.
  2. Use the least-privileged role for each staff member.
  3. Rotate API keys on a schedule (90 days) and immediately after any staff departure who had access to one.
  4. Scope every API key narrowly — a courier integration needs order and shipping scopes, not payout access.
  5. Review the audit log periodically, especially after a staff change, for actions you don't recognise.
  6. Never share login credentials over chat, email or phone — including with anyone claiming to be Framique support.
  7. Set a refund cap appropriate to staff trust level rather than leaving it unlimited by default.
  8. Verify webhook endpoints you configure are HTTPS and validate the signature we send.
  9. Keep a designated security contact on file with us so incident notifications reach a real person.
  10. Test your own restore/export process at least once — know how to get your data out before you ever need to.

Compliance roadmap — clearly marked

This band lists work in progress. Nothing here is a current certification or audit status. Items move out of this band only once genuinely completed, and this page is updated at that point — not before.

Compliance initiatives in progress
InitiativeStatusWhat it means when complete
Formal penetration test by an independent third partyRoadmapExternal validation of the isolation model and API surface, with findings remediated and summarised publicly
Bug bounty programRoadmapA standing paid incentive for external researchers, replacing the current goodwill disclosure process
SOC 2 Type II readiness reviewRoadmapNot a claim of certification today; a scoped effort to align controls with SOC 2 Type II criteria ahead of a future audit
PCI DSS scope reduction reviewRoadmapFormal confirmation of the tokenisation boundary, ahead of any assessment
ISO 27001 gap assessmentRoadmapStructured comparison of current practices against the standard, as a precursor to a certification decision

Frequently asked

Do you hold SOC 2 or ISO 27001 certification today?
No. We describe our controls and practices on this page; formal third-party certification work is listed as roadmap and we do not claim a status we have not achieved.
Where does merchant data physically live?
On self-hosted infrastructure we operate, with regional and fully self-hosted options available for merchants with specific residency requirements.
Can one merchant ever see another merchant's data through a bug in your app?
Isolation is enforced by Postgres row-level security, underneath our application code, and verified by a negative-assertion test suite that runs on every release. No system is provably bug-free, which is exactly why isolation is enforced at the database layer rather than trusted to application logic alone.
Do you ever see our customers' full card numbers?
No. Card and MFS credentials are handled through the processor's own hosted flow; we store a reference token and masked details, never the underlying credential.
What happens if there's a data breach?
It is classified SEV-1, contained immediately, and affected merchants are notified directly within the severity table's timelines, followed by a public post-mortem for material incidents.
Can we run Framique entirely on our own infrastructure?
Yes, as an enterprise option — the platform has no hard-coded dependency on our specific hosting, by design. Contact us to scope it.
How do we report a security vulnerability we found?
Email security@framique.com with details; see the disclosure policy above for scope, response-time commitments, and safe-harbour terms.
What do you log, and could our secrets end up in a log file?
We log structured, PII-scrubbed request data; known secret-shaped values are redacted before a log line is written, and secrets are never used in metric labels or error bodies.
How often are backups tested, not just taken?
On a scheduled cadence, restoring into an isolated environment and verifying integrity — a backup that has never been restored is not treated as a working backup.
Who can access our store's data on your side, and is it logged?
Only staff with a permission granted for a specific reason, mostly none at all — platform-admin cross-merchant access is granted per-incident, time-boxed, and every access is written to the audit log with the justifying ticket ID.

Send us your security questionnaire.আপনার সিকিউরিটি প্রশ্নপত্র আমাদের পাঠান।

Most vendor security reviews map directly onto the bands above. Send us yours and we'll respond with citations back to this page and our data processing agreement, not a generic template.