Security in ZMOS: attacked first, then hardened
Before ZMOS shipped its security layers, a red-team suite ran 17 real attacks over HTTP against a live database and a browser. Twelve succeeded or were unguarded. Once seven layers were hardened, the same suite was run again: 17 of 17 defended. The suite now runs in the build pipeline so it stays that way.
- 17 attacks, run before and after the fix
- Two-factor on every new sign-in
- Tenant from the session, never from the request
- Roles down to single fields; audit log of every change

What was tested
Attack first, fix second: every weakness was proven by a test that attempts the attack, so the fix could be proven the same way.
The 17 attacks cover the usual ways a web application loses data: clickjacking and header leakage, cross-site requests, session theft and fixation, memory exhaustion and API floods, credential and one-time-code brute force, privilege escalation through user management, reading another company's records by guessing ids (tested on 28 endpoints), mass assignment of protected fields, field-level permission bypass, server-side request forgery through workflow URLs, double posting under concurrency, stored scripts in rich text, disguised uploads and information leaks in error messages.
Twelve of the seventeen succeeded or had no guard before the work. The ones that already held, such as cross-tenant reads, mass assignment and disguised uploads, were kept under test rather than assumed.
Seven layers, what each one does
The hardening is written up layer by layer in the audit report. The parts that matter to a shop owner:
- Edge and transport
HTTPS with HSTS for two years, a CORS allow-list that only grants your own app origin, and a Cloudflare WAF ruleset with SQL injection, script and traversal blocks, bot challenges on sign-in routes and rate limits that mirror the API's.
- Headers and content policy
A strict Content Security Policy with no inline scripts, X-Frame-Options DENY, nosniff, a referrer policy and a permissions policy that allows the camera only for the scanner. Request bodies are capped at 1 MiB.
- Sign-in and sessions
An e-mail code on every new sign-in, an optional authenticator app with single-use backup codes, five attempts a minute per address and account, a 15-minute lockout after five failures, and sessions destroyed on any role, status or password change.
- Authorisation and tenant isolation
The company is resolved from the session, never from the request. Every table carries the tenant id and every query filters on it, enforced by a lint. Bodies that try to set a tenant or a role are rejected. Permissions apply down to single fields on custom forms.
- API and business logic
A flood limit of 600 requests a minute per address, a server-side request forgery guard on workflow URLs that resolves DNS and refuses private addresses, and row locks taken before idempotency checks so a double-clicked stock posting books once.
- Data and storage
Rich text and custom layouts are sanitised on the server. Uploads are verified by their bytes, not their file name. SQL is parameterised everywhere.
- Monitoring
Error messages never expose framework internals. Server errors are logged server-side only. The dependency audit reports zero vulnerabilities at every severity and the build fails on any high or critical advisory.
What stays with you
Software cannot replace habits. The audit report ends with the four things an owner should do.
Give each person the smallest role that fits their job, review the audit log now and then, keep API keys scoped and rotate them, and verify webhook signatures in anything you connect.
- Roles and permissions: /zmos/roles-audit-api/
- Zudo AI reads within the role and never writes on its own
- Your data exports as files at any time
- The full report lives in the product repository as SECURITY_AUDIT.md
The numbers
- attacks defended after hardening; 12 of 17 succeeded before
- 17 of 17
- endpoints swept for cross-company reads
- 28
- request limit per address
- 600 a minute
- sign-in attempts per minute, then the lockout
- 5 then 15 min
- vulnerabilities in the dependency audit at every severity
- 0
- HSTS with preload
- 2 years
Figures are from the red-team audit on the hardening branch of the ZMOS repository. If the suite changes, this page changes with it.
Watch it work
See this part of ZMOS on video
Recorded in the product itself, narrated in English, start to finish.
2:06:3236. Make it yours, and lock it down
Making the workspace yours and locking it down: roles, sign-in and settings. The clip starts at this chapter of the full two-hour build; every step is on sample data and the current version.
Is ZMOS safe for a small shop's data?
The product was attacked with a 17-test red-team suite before and after hardening; after, 17 of 17 were defended. Every new sign-in needs an e-mail code, data is isolated per company at the query level, and every change is logged. The suite runs in the build pipeline.
Does ZMOS have two-factor authentication?
Yes. Every new sign-in continues with a code sent by e-mail. An authenticator app can be added, with single-use backup codes, and sessions on other devices can be signed out from Settings.
Can another company see my records?
No. The company is resolved from the signed-in session and never from the request. Every table carries the company id, every query filters on it, and a sweep of 28 endpoints with another company's ids returned nothing.
Who can see what inside my company?
Roles set permissions by module, and custom forms go down to single fields: editable, read-only or hidden per role. Hidden fields are stripped from responses and cannot be charted or filtered.
Where is the audit?
The report is SECURITY_AUDIT.md in the product repository: the 17 attacks, the result before and after, and the fix for each layer. Ask on WhatsApp and we will walk you through it.
Test it with your own data
Six months free, no card, your data exportable at any time. Security questions welcome on WhatsApp before you sign up.