Skip to main content

07 — Security, Compliance & Multi-Tenant Isolation

Companion: 02 Enterprise Architecture · 03 Microservices · 05 API Design · 06 Data Models · 08 AI Architecture · 09 Lock & Key Integration · 10 Payments Architecture · 12 Desktop Spec · ADR-0002 Multi-Tenancy · ADR-0003 Electron Offline-First

This document is the canonical security, compliance, and tenancy enforcement reference for Ghasi Melmastoon. Every other document defers to this one for the rules. The approach is defense in depth at every layer — domain, application, database, network, edge, client — with the assumption that any single layer can fail and the others must hold.


1. Threat Model (STRIDE per surface)

We apply STRIDE to each major surface separately because each has a different audience, attack profile, and blast radius.

1.1 Consumer meta layer (bff-consumer-service + Next.js meta site + React Native consumer app)

STRIDEThreatMitigation
SpoofingBot scraping availability + priceAnonymous rate limiting at Cloud Armor + Kong; CAPTCHA on suspicious traffic; aggressive caching to absorb scrape load
TamperingSearch payload manipulation to forge low pricesServer is the only source of truth; client-supplied price in payload is ignored; price re-derived per request
RepudiationAnonymous booking attempts repeatedly with no auditEvery search + booking-intent emits an event with anonymous session id + IP fingerprint; rate-limited by (ip, ua, session)
Information disclosureCross-tenant data leak in meta searchMeta layer only reads from search-aggregation-service projection — no PII, no payment data, no internal financials in that projection
Denial of serviceMap-bound query floodBounding-box queries are capped at zoom level + max-results; results capped per tenant
Elevation of privilegeAnonymous → authenticated bypassNo authenticated routes on the meta surface; all booking-protected actions require a tenant booking session

1.2 Tenant booking surface (bff-tenant-booking-service + tenant booking site + consumer app per-tenant flow)

STRIDEThreatMitigation
SpoofingPretend to be a different tenant via header manipulationTenant resolved server-side from URL/slug + signed booking session; never from client header
TamperingModify quote between display and payQuote signed (HMAC) with server-side TTL; payment refuses if signature/quote mismatched
RepudiationGuest claims they didn't bookBooking session + signed quote + payment provider receipt + audit chain
Information disclosureOther-tenant theme/policy leakageTenant booking BFF resolves tenant once per request and refuses cross-tenant fetches in the same request
Denial of serviceHold-and-abandon flooding inventoryHold TTL 10 min; per-IP rate limit; abandoned-hold reaper
Elevation of privilegeGuest → staff via JWT manipulationJWT is signed (RS256); tenant role claims verified per request; tenant booking surface never accepts staff JWTs

1.3 Backoffice Electron desktop (bff-backoffice-service + Electron app)

STRIDEThreatMitigation
SpoofingUnpaired/imposter device sync pushRefresh token bound to device public key registered at pairing; mismatched key → 401 + re-pair
TamperingLocal DB extraction from a lost laptopSQLCipher with device-derived key; OS keychain (keytar) holds the key fragment; user passphrase optional second factor
RepudiationStaff denies a folio adjustmentAppend-only audit_events per service + central BigQuery + daily Merkle anchor
Information disclosureRenderer XSS exfiltrating datanodeIntegration: false, contextIsolation: true, narrow contextBridge API; CSP default-src 'self'; remote module disabled
Denial of serviceSync flood from a buggy or compromised clientPer-device rate limits at the sync surface; max-batch enforced; revoke device on policy breach
Elevation of privilegeCashier escalates to GM-only refundRBAC + ABAC enforced server-side; UI hides but server checks; refunds above policy threshold require a second authorizer

1.4 Mobile consumer app (React Native)

STRIDEThreatMitigation
SpoofingStolen device sessionRefresh token in OS keychain; pinned cert; force-reauth on root/jailbreak detection (best-effort)
TamperingAPI client modified to bypass policyServer-only enforcement; client policy is UX, not security
RepudiationBooking disputeSame provenance trail as the tenant booking surface
Information disclosurePlaintext storage of guest dataEncrypted async storage for any cached PII; never store payment PAN
Denial of serviceApp-level abuseBacked by Cloud Armor + tenant booking BFF rate limits
Elevation of privilegeFake admin endpointsThe app talks only to BFFs; no admin endpoints exist on the consumer BFF

1.5 Internal services + east-west traffic

STRIDEThreatMitigation
SpoofingService A pretends to be Service BService-to-service mTLS where supported; otherwise short-lived service-account JWTs verified at the receiver
TamperingPub/Sub event payload tampering at restCloud Pub/Sub at-rest encryption + CMEK on PII topics; payload schema validated by consumer; dead-lettered if invalid
RepudiationService mutates without traceOutbox pattern + audit emit; dependency-graph CI fails any mutation without a paired audit emit
Information disclosureLogs leak PIIpino redactors strip declared PII keys at serialization; CI scans for new fields without redaction
Denial of serviceCascading retry stormsPer-port circuit breakers; exponential backoff with jitter; per-request fail-fast budget
Elevation of privilegeService account misusePer-service service account with least privilege; KMS keys scoped per service; ops bypass requires JIT elevation

1.6 Top-of-list cross-cutting threats

  1. Cross-tenant data leak — a single missed WHERE tenant_id = ? clause becomes a breach. Mitigation: RLS on every table + tenant_id value object in domain + outbox tenantId assertion + integration "two-tenant simulator" tests on every service.
  2. Lock credential theft — vendor credentials are the keys to physical doors. Mitigation: dedicated KMS key, isolated namespace inside lock-integration-service, no other service has read access, never logged, audit chain immutable.
  3. Payment fraud — synthetic accounts and chargebacks. Mitigation: PCI scope minimization (Stripe Elements / PayPal SDK tokenize client-side), AI anomaly detection on payment patterns with HITL gates for auto-block.
  4. AI prompt injection — guest-supplied content reaches the model and exfiltrates secrets. Mitigation: input length cap, system prompt isolation, output schema validation, PII redaction, output moderation.
  5. Offline DB extraction from lost laptop — Electron SQLite stolen. Mitigation: SQLCipher; device-derived key; remote-revoke on next sync attempt; minimal data scope per device.
  6. Supply-chain compromise (npm) — malicious package update. Mitigation: lockfiles + Dependabot + Snyk + SLSA provenance attestations + container scan + signed commits + signed-tag releases.

2. Authentication Architecture

2.1 JWT (primary)

  • Algorithm: RS256 (asymmetric); JWKS published from iam-service at /.well-known/jwks.json. Key ID (kid) rotation quarterly with grace overlap.
  • Access token: 15 minutes lifetime. Carries sub (UserId), tenant_id, roles, property_ids, device_id, iat, exp, jti.
  • Refresh token: 30 days, rotating (single-use rotation). Family-revoke on detected reuse — if a refresh token is presented after it has already been rotated once, the entire chain is revoked and the user is forced to re-authenticate.
  • Refresh on Electron + mobile: refresh token is bound to the device public key registered at pairing. Refresh request signs a challenge with the device private key (held in OS keychain via keytar); the IAM service rejects refreshes whose signature does not verify against the bound public key. Stolen refresh tokens without the device key are useless.
  • Storage: access token in process memory only (never localStorage); refresh token in OS keychain on Electron + mobile, in httpOnly; Secure; SameSite=Strict cookie on the web.

2.2 OIDC / SAML SSO (chain operators)

  • Phase 2 capability: OIDC + SAML 2.0 federation for chain operators with their own IdPs (Auth0, Keycloak, Okta, Azure AD, Google Workspace).
  • Implemented in iam-service via the passport-oidc and passport-saml strategies behind a single ExternalIdentityProvider port.
  • Per-tenant provider configuration; iam-service provisions a local User and a Membership on first login.
  • SCIM 2.0 endpoint (Phase 3) for centralized user provisioning from chain HRIS.

2.3 WebAuthn / Passkeys (Phase 2)

  • Optional second factor (or sole factor for staff with hardware keys). FIDO2 level 2.
  • Public keys persisted on users.webauthn_credentials; private keys never leave the user's device.
  • Backoffice users can be required by tenant policy to enroll a passkey for refund + lock-credential operations (step-up auth).
  • Guests booking on the tenant site can opt into an account by receiving a one-time magic link (iam-service issues, notification-service delivers via email/SMS).
  • Magic link is a 30-minute single-use token; on click, iam-service issues a normal access + refresh pair.
  • Used to access booking history, modify reservations, and receive digital keys without a password.

2.5 Service-to-service

  • Cloud Run services authenticate to each other via short-lived Google service-account ID tokens verified at the receiver (or mTLS where the topology supports it).
  • No service-to-service traffic uses static API keys.

3. Authorization Architecture

3.1 RBAC roles (canonical)

Roles are coarse, hierarchical, and tenant-scoped (except platform roles). The canonical list:

RoleScopeTypical permissions
platform.super_adminPlatformTenant lifecycle; emergency support; ops elevation. Audited.
platform.supportPlatformRead-only across tenants for support tickets, with explicit per-ticket elevation + audit
platform.compliance_officerPlatformDSAR fulfilment; audit log read; AI provenance review
tenant.ownerTenantAll tenant operations including billing, property creation, plan upgrades, member invites
tenant.gmTenant (or property)All operational + reporting; cannot change billing/plan
tenant.front_deskPropertyReservations, check-in/out, folio operations within policy, key issuance
tenant.housekeeping_leadPropertyManage HK tasks, assignments, room status overrides
tenant.housekeepingPropertyUpdate assigned tasks; flip room status; raise flags
tenant.maintenancePropertyTriage and close maintenance tickets
tenant.financeTenantReconciliation, invoices, refunds above threshold (with approval), tax exports
tenant.marketingTenantTheme + content blocks; promotion + rate-plan creation
chain.operatorCross-tenant within a chain accountRead across owned tenants; cross-property reporting; cannot mutate ops directly
guestSelfOwn reservations, own folio, own profile

3.2 ABAC attributes

Beyond role checks, fine-grained access uses ABAC predicates evaluated by the policy engine. Attributes:

  • tenant_id — must match for any tenant-scoped resource.
  • property_id — staff scoped to one property within a multi-property tenant cannot read other properties.
  • data_residency — request region must satisfy the tenant's residency pin; cross-region requests rejected with explicit error.
  • step_up_recent — boolean; some actions (refunds above threshold, lock credential bulk revoke) require a recent passkey/WebAuthn step-up within the last 5 minutes.
  • time_of_day — optional per-role policy that limits sensitive actions to business hours.

Predicate examples:

resource.tenant_id == ctx.tenant_id
resource.property_id IN ctx.user.property_ids
resource.amount_micro <= role.refund_threshold_micro OR ctx.step_up_recent
ctx.region IN tenant.residency_allowed_regions

3.3 Decision flow

  1. JWT verified at Kong → claims loaded.
  2. bff-*-service resolves the route's required (resource:action).
  3. Policy engine evaluates (role grants action) AND (all ABAC predicates true).
  4. Decision logged with decisionId; UI may pre-check via POST /api/v1/authz/check to avoid showing forbidden actions.
  5. Domain layer re-checks the tenantId invariant on every aggregate operation as a final defense.

4. Tenant Isolation at Every Layer

LayerMechanismWhat it catchesWhat happens on breach
DomainAggregates carry tenantId: TenantId; cross-aggregate refs validated at constructionWrong tenant id passed in a use-case parameterCrossTenantReferenceError thrown before persistence; 403 returned
Application (use-case)Use-cases require explicit TenantId parameter; not implicit via ALSForgetting to pass tenant contextType-checker rejects the call site; CI fails
API middlewareRequest-scoped middleware sets app.tenant_id from JWT claim before any DB callHeader spoofing (X-Tenant-Id differs from JWT)403 at the gateway; audit log entry
Database (RLS)tenant_id column + FORCE ROW LEVEL SECURITY policy USING (tenant_id = current_setting('app.tenant_id')::uuid)Bug in app code that omits WHERE tenant_id = ?Query returns 0 rows; insert with wrong tenant_id throws RLS violation
Connection poolPgBouncer transaction-mode init script sets app.tenant_id per checkoutConnection reuse across tenantsapp.tenant_id cannot be unset between checkouts
OutboxWriter asserts payload.tenantId == current_setting('app.tenant_id') before commitWrong tenant in event payloadAborted transaction; alert
Pub/Sub envelopetenantId in envelope; consumer asserts on receiveCross-tenant event bleed via mis-routingEvent dropped; DLQ; alert
Search projectionsearch-aggregation-service is the only cross-tenant store; PII excluded by projection schemaAccidental PII inclusion in projectionCI fails the projection schema diff
StorageCloud Storage objects under gs://melmastoon-<env>-tenant/<tenantId>/...; signed URLs are scoped to that prefix and the requesting callerCross-tenant URL guessing403 at signed URL validation
AIPer-tenant prompt namespace; per-tenant vector namespace (RLS on pgvector tables); per-tenant model cache keyOne tenant's RAG context bleeding into another's promptPredicate failure → no rows returned; explicit alert

4.1 Two-tenant simulator (CI requirement)

Every service must include an integration test suite that:

  1. Provisions two tenants A and B.
  2. Inserts data via the public API for both.
  3. Asserts that every read endpoint, with A's JWT, returns only A's data, and vice versa.
  4. Asserts that every write endpoint refuses cross-tenant references (A's JWT writing to B's resource → 403/CrossTenantReferenceError).

A new service ships only after this suite is green.


5. Secrets Management

5.1 Storage

  • All secrets (provider keys, signing keys, lock vendor credentials, payment provider keys, AI provider keys, JWT signing keys) live in Google Secret Manager.
  • Never in env files committed to git. Never in client bundles. Never in CI logs (CI uses workload identity federation; secrets are mounted to runners just-in-time and redacted).

5.2 Service accounts

  • Per-service service account with the minimum set of secret accessors required.
  • Lock vendor credentials live in dedicated secrets named secret-lock-<vendor> (e.g., secret-lock-ttlock, secret-lock-salto). Only the lock-integration-service service account can read them. No other service has accessor permission.
  • Payment provider keys live in secret-payment-<provider> (e.g., secret-payment-paypal, secret-payment-stripe). Only payment-gateway-service can read.

5.3 Envelope encryption

  • KMS-wrapped DEKs for application-level field encryption. Each per-record DEK is generated once, wrapped under the service's KMS key, persisted alongside the ciphertext, and unwrapped only on read in process memory.
  • KMS keys are environment-scoped and never shared across environments.

5.4 Rotation

  • Quarterly rotation policy for application-level secrets (lock keys, payment keys, AI keys).
  • Annual rotation for KEKs (with grace window — old DEKs still unwrap during the window; background re-encryption job rolls records onto the new key).
  • Immediate rotation on suspected compromise (incident response runbook).
  • Rotation is a one-button operation in iam-service's ops console; the saga handles distribution and verification.

5.5 Audit

  • Every secret access is logged to Cloud Logging. Anomaly detection alerts on unexpected access patterns (out-of-hours, by an unexpected service account, from an unexpected region).

6. Payment Security

6.1 PCI scope

We deliberately keep PCI scope minimal:

  • Cardholder data never touches our servers. Stripe Elements / PayPal SDK / equivalent tokenize client-side; only the resulting opaque token reaches payment-gateway-service.
  • Our PCI scope is SAQ A (the smallest applicable level).
  • payment-gateway-service runs in its own Cloud Run service with a dedicated service account and a dedicated KMS key.
  • The schema-per-tenant carve-out (tenant_<uuid>_payments) provides structural isolation per tenant; cross-tenant payment-data leakage is impossible inside Postgres.

6.2 Token storage

  • Stored: provider token + provider reference + amount + currency + status + metadata (PII-redacted).
  • Never stored: PAN, CVV, expiration, cardholder name (we display the last-4 from the tokenized response only when the provider returns it).

6.3 Cash on arrival

  • Cash flow lives entirely inside billing-service (schema-per-tenant) and payment-gateway-service cash rail. No card data involved.
  • Cash payments require metadata.received_by (StaffId) and metadata.location for reconciliation.
  • Daily reconciliation report compares payments.cash rows to staff cash drawer counts; deviations raise audit.cash.deviation.v1.

6.4 Refund authorization

  • Refunds above a configurable per-tenant threshold (default: 50,000 micro AFN-equivalent USD) require a second authorizer with tenant.finance or tenant.gm role and a step-up auth (passkey or fresh password) within the last 5 minutes.

6.5 Webhook integrity

  • Provider webhooks (Stripe, PayPal) are HMAC-signed and timestamped. We verify both signature and timestamp window (5 minutes); replays are detected via nonce store in Memorystore.

7. Lock & Key Security

7.1 Vendor credential isolation

  • All vendor credentials live in Secret Manager under secret-lock-<vendor>, accessible only by lock-integration-service's service account.
  • Vendor credentials are loaded once at startup into process memory and never logged. CI fails any log statement that references a known credential variable name.

7.2 Key issuance audit chain

  • Every lifecycle event (issue, update, revoke, suspend) is appended to key_credentials.audit_chain (see 06 Data Models §4.11).
  • The chain is append-only at the SQL level (no UPDATE / DELETE).
  • Each entry carries (at, actor, event, vendorRef); the chain is replicated to audit-service's BigQuery dataset.

7.3 Revoke-on-checkout (mandatory)

  • reservation.checkout.v1 is not considered final until lock.key.revoked.v1 is observed for every credential issued for that reservation.
  • If the vendor is unreachable, the revoke is queued; the reservation enters checkout_pending_revoke substate; the front desk is alerted.

7.4 Lost-key flow

  • Front desk staff trigger key.lost from the desktop app. The use-case:
    1. Revokes the lost credential immediately.
    2. Issues a new credential with the same validFrom/validTo.
    3. Logs both events on the audit chain with revoke_reason='lost'.
    4. Notifies the guest by SMS/email.
  • Failed revocation paths surface a high-priority alert; the room may be flipped to oo until resolved.

7.5 Offline key issuance

  • The desktop app can request key issuance while offline only for vendors that support offline credential generation (e.g., TTLock OfflinePassword API).
  • The credential is generated using a tenant-scoped key derivation chain seeded from the property's lockSeed provisioned at vendor onboarding. The seed is sealed in the OS keychain and is never written to disk.
  • The local issuance event is queued in the outbox; on next sync, the cloud lock-integration-service reconciles, registers the credential, and emits lock.key.issued.v1.

8. AI Safety

8.1 Prompt injection mitigations

  • Input length cap per use case (e.g., 4 KB for guest-facing chat; 16 KB for admin-side analysis). Truncation with explicit notification to the model.
  • System prompt isolation — the system prompt is injected by ai-orchestrator-service from the prompt registry; it is never composed from user input. Prompt templates are stored separately from runtime variables.
  • Output schema validation — every model response is validated against a JSON schema declared by the prompt template. Non-conforming outputs are rejected and either retried (once) or returned as null with MELMASTOON.AI.OUTPUT_SCHEMA_VIOLATION.
  • Tool-call isolation — the model can call only the tools declared by the use case; tool definitions are server-side and authenticated.

8.2 HITL gates (irreversible / high-impact actions)

The following AI-suggested actions must sit in draft_ai state until accepted by a user with the appropriate role:

  • Auto-cancel a reservation flagged as fraudulent.
  • Auto-refund any amount.
  • Dynamic price publish when AI-suggested price deviates >5% from the BAR baseline.
  • Anomaly-flagged booking auto-block beyond temporary hold.
  • Bulk lock-credential revoke initiated by anomaly detection.
  • Guest-facing message dispatch drafted by the model.

Acceptance is recorded as a Decision (dec_…) with (actor, at, reason) and linked to the resulting state-change event.

8.3 Content moderation

  • All guest-facing AI output passes through the post-moderation step: profanity, dangerous content, PII, prompt-leak markers.
  • Failed moderation → output blocked, fallback to deterministic template (e.g., a canned acknowledgement), incident raised if classification is "harmful".

8.4 Provenance metadata (mandatory)

Every AI artifact persists with the AIProvenance envelope (see 02 Enterprise Architecture §9.3 and 08 AI Architecture §6). No AI artifact may be displayed to a user without provenance metadata; the UI surfaces an "AI" badge with a click-through to provenance details.

8.5 PII redaction

  • Before any cloud-bound payload, ai-orchestrator-service runs a PII-redaction pass: emails, phones, government IDs, credit-card-shaped strings, IBANs.
  • Redaction emits ai.redaction.applied.v1 with counts (no content) for ops dashboards.

9. Compliance Posture

Standard / RegulationPostureEvidence
GDPRFull data subject rights (access, erasure, portability, rectification, restriction); DPIA template per processing activity; 72-hour breach notification runbook; DPO role; lawful-basis registeraudit-service DSAR saga; DPIA template under docs/standards/; breach response in §13
Afghanistan / regional data residencyPer-tenant residency pin (me-central1 / europe-west1); data plane never crosses pinned region without explicit opt-in; logs locale-taggedtenant-service settings; egress audited
PCI-DSSSAQ A scope; cardholder data never on our servers; quarterly ASV scan on the booking + payment surfaces; annual SAQ self-assessmentStripe Elements / PayPal SDK; payment-gateway-service schema-per-tenant; ASV reports
SOC 2 Type IIPhase 3 target. Continuous controls (access reviews, change management, vulnerability management, incident response). Observability + audit log are SOC-2-aligned from day 1Roadmap §Phase 3; control catalog under docs/compliance/ (Phase 3 deliverable)
ISO 27001Phase 4 target. ISMS scope, risk register, control statement of applicabilityRisk register under docs/risks/; ISMS docs (Phase 4)
WCAG 2.2 AAFull AA on all guest-facing surfaces; AA on critical operational paths in the desktop appAccessibility CI checks; periodic audit

10. Data Residency

  • Primary region: me-central1 (Doha) when available for the relevant Cloud SQL / Pub/Sub / Cloud Run feature set; otherwise europe-west1 (Belgium) as the closest practical region serving Afghanistan with full feature parity.
  • Per-tenant region pin (Plus + Enterprise plans, Phase 3): tenant data is stored in the pinned region; data plane operations refuse to cross the pin.
  • Cross-region replication: off by default; enabled only with explicit tenant opt-in.
  • Backups stay in the same region as the primary; cross-region DR is opt-in and additionally KMS-wrapped.
  • CDN: static assets and theme tokens are cacheable globally (no PII), so CDN edges may serve any region.
  • Logs: Cloud Logging is region-pinned per project; tenant_id tagged on every line; per-tenant retention pins in Plus + Enterprise.

11. Audit & Forensics

11.1 Append-only logs

  • Every service writes to its local audit_events table (append-only, see 06 Data Models §9).
  • Every row is also published to Pub/Sub and lands in the central BigQuery melmastoon_audit_log dataset.

11.2 Retention

ClassRetentionStorage
Authentication events2 yearsBigQuery + Cloud Storage Coldline archive
Reservations + housekeeping events3 yearsBigQuery + tenant-scoped archive
Financial events (folio, payment, refund)7 years (regulatory)BigQuery melmastoon_audit_log_financial + Cloud Storage immutable bucket (object lock)
AI provenance7 yearsBigQuery melmastoon_audit_log_ai
Lock-credential lifecycle5 yearsBigQuery + immutable bucket
DSAR fulfilment proofs10 yearsImmutable bucket

11.3 Tamper evidence

  • Daily Merkle root computed over yesterday's audit events; anchored to a public RFC 3161 timestamp authority. Anchors persisted in melmastoon_audit_anchors.
  • Quarterly verification job re-derives roots and checks anchors.

11.4 Forensic readiness

  • Incident timeline reconstruction is supported by trace_id correlation across services + audit events + Pub/Sub message archive (7 days default; tunable).
  • Full request replay possible from request_id for non-mutating reads.

12. Vulnerability Management

12.1 Static + supply chain

  • Snyk + GitHub Dependabot for npm dependencies. PRs blocked on critical vulnerabilities; high vulnerabilities require explicit acceptance with a remediation plan.
  • Lockfiles (package-lock.json / pnpm-lock.yaml) committed. CI verifies --frozen-lockfile.
  • SBOM generated per build (CycloneDX); SLSA provenance attestations for built artifacts.
  • SAST via CodeQL on every PR; secret scanning at the GitHub level + locally via gitleaks pre-commit hook.

12.2 Container scanning

  • All container images built into Artifact Registry are scanned by Container Analysis. Critical CVEs block deploy; the deploy gate consumes the scan result before promotion.

12.3 Pen test + bug bounty

  • Quarterly external penetration test on the public surfaces (consumer meta, tenant booking, mobile, the Electron auto-update channel).
  • Bug bounty opens in Phase 3 with scoped surfaces and reward tiers.

12.4 IaC + change review

  • Terraform for all GCP infra; terraform plan posted as PR comment; required reviewer for the security-sensitive resources (KMS keys, IAM bindings, Secret Manager).
  • Pre-merge tflint + tfsec checks.

13. Incident Response

13.1 Severity classes

SeverityExamplesSLA
Sev 1Confirmed cross-tenant data leak; payment data exposure; lock-credential exposure; consumer surface fully down >5 minPage on-call within 5 min; communications + remediation continuous
Sev 2Single-tenant data exposure; backoffice down for one tenant; payment failure rate >10%Page on-call within 15 min; remediation within 4 hours
Sev 3Single property impacted; degraded AI; sync lag > 30 min for one device fleetNotify on-call within 1 hour; remediation within 24 hours
Sev 4Cosmetic, single-user impact, recoverable workaroundTracked in next sprint

13.2 On-call rotation

  • 24/7 primary + secondary rotation across regions during business growth.
  • Escalation path documented in PagerDuty (or equivalent); each service has a designated on-call owner.
  • Runbook links live in docs/observability/runbooks/.

13.3 Communication templates

  • Internal status update template (every 30 min during a Sev 1).
  • Tenant-facing notice template (translated into supported locales).
  • GDPR breach notification template (72-hour deadline tracking).

13.4 Post-incident review

  • Mandatory blameless PIR within 5 business days for Sev 1/2.
  • Output: timeline, root cause, contributing factors, action items with owners + dates, and a "what would prevent this entirely" section.
  • Action items tracked to closure in the engineering backlog with the pir-followup label.

14. Secure SDLC

ControlHow
Branch protectiondevelop and main protected; PRs require ≥1 review + green CI; force-push disabled
Signed commitsRequired on main for ops + release engineers; recommended elsewhere; gpg or Sigstore-based
Mandatory reviewCode review by someone other than the author; security-sensitive paths (auth, payment, lock, AI gateway) require a security reviewer in the CODEOWNERS
SASTCodeQL on every PR; Semgrep rules tuned per service for known anti-patterns (e.g., raw child_process in renderer, missing tenant_id, string-concatenated SQL)
Secret scangitleaks pre-commit + GitHub secret scanning + push protection
IaC reviewTerraform changes reviewed by SRE + security; tfsec blocks on high-severity findings
Change advisory for productionProduction deploys require a CHANGE record in the deploy log, including risk assessment, rollback plan, and observability checkpoints
Two-person rule for sensitive opsTenant erasure, KMS key rotation, financial schema migration require two operators

15. Electron Security Hardening

The Electron desktop is the highest-leverage attack surface — it holds offline data, has Node-level capability, and is widely deployed on staff laptops. We harden it aggressively. See ADR-0003 for the full rationale.

15.1 Process model

  • nodeIntegration: false on every renderer. The renderer is plain Chromium; it cannot require('child_process').
  • contextIsolation: true. The renderer's JS context is isolated from preload's. Preload exposes only what contextBridge declares.
  • sandbox: true on every BrowserWindow. The renderer process is a sandboxed Chromium child.
  • webSecurity: true (default; never disabled).
  • allowRunningInsecureContent: false.
  • enableRemoteModule: false (the remote module is gone in modern Electron; we explicitly verify it is not re-added).

15.2 contextBridge API surface (narrow)

window.melmastoon is the only object exposed to the renderer. It exposes a small, typed surface:

window.melmastoon = {
api: {
request: (route: string, payload: unknown) => Promise<unknown>, // typed routes only
},
sync: {
pullNow: () => Promise<SyncSummary>,
pushNow: () => Promise<SyncSummary>,
onStatus: (cb: (s: SyncStatus) => void) => Unsubscribe,
},
ai: {
infer: (capability: EdgeCapability, input: unknown) => Promise<unknown>,
},
keys: {
issueOffline: (req: OfflineKeyRequest) => Promise<KeyCredentialDraft>,
onCredentialUpdate: (cb: (e: KeyCredentialEvent) => void) => Unsubscribe,
},
printer: { /* receipt-printer integration */ },
app: { version: string, locale: string },
};

No fs, no child_process, no eval, no arbitrary IPC channel — all renderer requests go through api.request(route, payload) which is dispatched to a switch in the main process; routes that the main process does not recognize are logged and rejected.

15.3 CSP

The renderer enforces a strict CSP via the <meta http-equiv="Content-Security-Policy"> tag and via Electron's webRequest.onHeadersReceived so it cannot be removed at runtime:

default-src 'self';
script-src 'self';
style-src 'self' 'unsafe-inline'; // Tailwind generates inline styles in dev; tightened in prod build
img-src 'self' data: https://storage.googleapis.com https://cdn.melmastoon.app;
connect-src 'self' https://api.melmastoon.app https://sync.melmastoon.app;
font-src 'self' data:;
frame-src 'none';
object-src 'none';
base-uri 'self';
form-action 'none';

15.4 Auto-update integrity

  • Built with electron-builder; updates served via electron-updater.
  • Code signing mandatory:
    • Windows: EV code-signing certificate; SmartScreen reputation maintained.
    • macOS: Apple Developer ID + notarization.
    • Linux (AppImage): GPG-signed; signature verified on update.
  • electron-updater verifies the publisher signature on every update before applying. A signature mismatch refuses the update and logs to telemetry.

15.5 Token + secret storage

  • Refresh token + device key fragment stored in OS keychain via keytar (Credential Manager on Windows, Keychain on macOS, Secret Service on Linux).
  • Plain-text tokens are never written to disk.
  • Process memory carrying secrets is zeroed after use where the language allows; we do not over-promise here (Node + V8 do not give precise control), but we minimize the surface.

15.6 Local DB encryption

  • SQLite database file (melmastoon.db) is encrypted with SQLCipher (AES-256-CBC). Key is derived from (deviceKey ⊕ userPassphrase?); the deviceKey is generated at first install and held in keytar.
  • Key derivation parameters: kdf_iter = 256000, cipher_page_size = 4096.
  • Loss of keytar entry forces re-pair (no recovery — intentional). The user re-pairs from a fresh login + device-binding flow.

15.7 Other hardening

  • Disable Node integration in subframes (nodeIntegrationInSubFrames: false).
  • Restrict navigationwill-navigate and new-window handlers refuse navigation to anything outside the allowlist (api.melmastoon.app, sync.melmastoon.app, our own help URLs).
  • Disable DevTools in production builds; webContents.on('devtools-opened', () => webContents.closeDevTools()) as a hard guard.
  • Crash reporter scrubs paths + tokens + tenant ids from crash dumps before upload.
  • Telemetry is opt-in, anonymized, and never includes user-identifiable content.

Cross-references: per-service security details live in services/<service-name>/SECURITY_MODEL.md. Lock-specific deep dive in 09 Lock & Key Integration. Payments deep dive in 10 Payments Architecture. AI safety deep dive in 08 AI Architecture.