SECURITY
Security claims you can verify in the code
Orosphinx handles real gold and real money. Our security posture is built on Postgres row-level security, audit-logged mutations, signed webhooks, and idempotent procedures.
The claims register — 01
Every claim names the mechanism and the file that implements it.
| Claim | Mechanism | Verify at |
|---|---|---|
| Tenant isolation | Postgres row-level security on every table | scripts/wave-12/lint/rls-tenant-filter.mjs |
| Audit log | Before/after state on every change | utils/audit.ts |
| Signed webhooks | HMAC v1 with timestamp + nonce | services/webhook-outbox.ts |
| Duplicate-protected sale posting | X-Idempotency-Key · pg_advisory_xact_lock | middleware/idempotency.ts |
| Role-based access | Enforced on the server, on every request | auth/assert-permission.ts |
| PIN-protected counter actions | scrypt-hashed PINs with lockout | routers/user.ts |
| Scoped API keys | Hashed at rest, shown once | auth/api-key-verify.ts |
| Point-in-time recovery | Continuous database backups | workers/backup-scheduler.ts |
| Rate limits | Always on in production | middleware/rate-limit.ts |
The exhibits — 02
Exhibit A — Tenant isolation
Every business operates inside its own row-level security boundary in Postgres. The application role does not bypass RLS. Cross-tenant workers (PDPL, retention) use narrow SECURITY DEFINER functions reviewed in migrations.
- RLS policies on every tenant-scoped table
- Tenant context bound per request via app.current_tenant_id
- Application database role has no BYPASSRLS privilege
- Cross-tenant workers limited to one auditable function
ALTER TABLE sales.sales
ENABLE ROW LEVEL SECURITY;
ALTER TABLE sales.sales
FORCE ROW LEVEL SECURITY;
CREATE POLICY tenant_isolation ON sales.sales
USING (tenant_id = NULLIF(current_setting(
'app.current_tenant_id', true), '')::uuid);Exhibit B — The audit log
Every UPDATE, TRANSITION, and DELETE writes the pre-mutation state. Auth failures, PIN lockouts, key rotations, and break-glass actions write security events with severity.
- Append-only audit log on mutations
- Filterable by user, action, entity, branch
- Security events with severity for review
- Exportable for internal and external review
{
"action_type": "UPDATE",
"entity_type": "inventory.item",
"entity_id": "IT-04129",
"actor_role": "manager",
- "before": { "price_making": 120.00 },
+ "after": { "price_making": 135.00 },
"trace_id": "01a7f2…"
}Exhibit C — Signed webhooks
Scoped API keys hashed at rest. Webhook deliveries carry an HMAC v1 envelope with timestamp and nonce. Outbox-based delivery with retries.
- HMAC v1 envelope on every delivery
- Hashed API keys, shown once
POST /your-endpoint HTTP/1.1
X-Webhook-Topic: sale.completed
X-Webhook-Event-Id: 0197f3a2-4bd1…
X-Webhook-Timestamp: 1767014402
X-Webhook-Nonce: 5b2fc0e1a94d…
X-Webhook-Signature-V1: 9c41d8f2e6…
v1 = HMAC_SHA256(secret,
timestamp + "." + nonce + "." + body)Exhibit D — Recovery
Continuous Postgres backups with point-in-time recovery. Shift close triggers an additional logical backup. Optional encrypted local backups are written by the store agent.
PITRworkers/backup-scheduler.tsBACKUP_RETENTION_DAYSExhibit E — Counter discipline
The UI reflects permissions but never decides them. Every tRPC procedure checks role and entitlement before reading or writing.
- Roles: admin, manager, cashier, viewer, plus custom
- Per-route permission checks on every mutation
- Break-glass actions log a security event
- Entitlement-mode logging when enforcement is off
pin_hash = scrypt(pin, salt, 64)
stored = "9f2c…31c8:a41b…77e0" -- salt:derived
compare = crypto.timingSafeEqual(a, b)
legacy = sha256 -> scrypt -- dual-read upgradeExhibit F — The perimeter
Tenant data sits behind Postgres row-level security. Every mutation is server-enforced and audit-logged. The application database role does not have BYPASSRLS. Webhooks are signed with HMAC v1 carrying timestamp and nonce.
Data requests — 03
Deletion and export request workflows are built in, with a worker that processes them under each tenant's own isolation.
What to ask for in a walkthrough — 04
Ask us to change a price and show you the before/after row.
Ask what the database returns for another shop's data: nothing.
Ask to see a webhook envelope and verify its signature yourself.
Contact — 05
Want a security walkthrough?
We'll cover tenant isolation, audit log, webhook signing, and operational controls in detail.