Security

How this is defended

Controls that exist in code, described by what they do rather than by what they are called — and a section naming the ones that are not built, because a security page listing only strengths tells you nothing except that it left things out.

Getting in

Passwords are Argon2id hashes

Hashed, not encrypted — there is no key that turns one back into a password, so a forgotten one is replaced rather than retrieved. Nobody here can read yours.

Two-factor is compulsory for owners and managers

Not offered — required. Somebody in one of those roles who has not enrolled is held on the account screen and cannot reach the console. These are the roles that can void a bill and release a table with money owed.

A terminal PIN is a different secret

Separately hashed, never the password. A password is typed once per shift in a back office; a PIN is typed on a tablet in front of guests, where it will be watched. Shoulder-surfing one does not yield the other, and three wrong PINs lock the terminal and say for how long instead of pretending the PIN was merely wrong.

A guest holds no credential at all

Scanning a table card opens a session on the server naming that table. The browser holds a session cookie and nothing else. A token the guest carried would be a token the guest could edit.

Every sign-in is written down, so you can see where your account is signed in and end any of those sessions — and so can we, during an incident, without changing your password out from under you. An integration token is stored as a hash; the token itself is shown once, at the moment it is created, and never again.

Rate limits

Held in the database rather than in memory, so they survive a restart and every process sees the same counter. A real person mistypes a password three times; a script tries three thousand.

Signing in 8 attempts per 5 minutes

Per account.

Two-factor codes 10 per 5 minutes

Per account.

Opening an account 5 per hour

Per address.

A guest ordering 12 per 10 minutes

Per table.

A guest calling a waiter 10 per 10 minutes

Per table.

A guest rating a dish 4 per 10 minutes

Per table. The tightest of the three, because it is the only one that raises a manager's notification directly.

A terminal pushing offline work 30 per minute

Per device.

A limit is not a substitute for a write that behaves. The same change that tightened the rating bucket also deduplicated the write — one rating per sitting — and moved the manager's notification so that it fires on a changed rating rather than on every press. A bucket that permits four presses should still not produce four rows and four warnings.


What you can reach once you are in

Two gates, and they answer different questions. One asks whether this person's role may press this thing. The other asks whether this row belongs to this venue at all. A system with only the first is a system where a correctly-signed-in manager can read another restaurant's takings.

The venue gate is enforced at the query layer rather than in each screen, so it applies to a path nobody remembered to think about. A build-time rule flags a read on a venue-facing screen that reaches past its own venue, which is how the class of mistake gets caught rather than the individual mistake.

A control that is drawn but refuses when pressed is a bad control. A button a role may not press is not rendered for that role in the first place, and the refusal behind it exists anyway — because a screen is a convenience and the server is the rule.


The audit trail

Every privileged or financial action writes a row, and each row's hash is computed over the row before it. Signing in and failing to, a PIN failure, a role or permission change, a price change, a void, a refund, a discount above the ceiling, releasing a table unpaid, a drawer adjustment, a shift closed with a variance, a bill reprint, a data export, a support session starting and ending — all of it, with who, what, how much, before and after, the reason, the approver, the terminal and the time to the millisecond.

Verification does not merely walk the chain. It recomputes each row's hash from that row's own contents and compares, so an edited row is reported as tampered rather than slipping through because its link still matches. Deleting a row or reordering two of them breaks the chain and is reported separately, naming the row it broke at.

The boundary, stated so you know which side you are on

Recomputing needs the exact bytes that were hashed, and one field was not stored before the migration that added it. Rows older than that keep link-only cover: deletion and reordering are detected, an edit is not. They are deliberately not rewritten — re-hashing history to make a report look clean is the one operation an append-only ledger must never perform.


When support needs to look

The most dangerous capability any platform has, so it is constrained hard — and every constraint below is enforced in code, not promised in a document.

You approve it, with your own second factor

An operator asks. Only an owner of your venue can say yes, and the request lapses if nobody answers within fifteen minutes. A refusal is recorded too — a request that was refused should still be something you can point at.

Thirty minutes, checked on every request

The grant carries its own expiry and it is enforced when it is read, not when a sweep happens to run. A session cannot outlive its approval by waiting.

You can end it from inside it

The controls that deny or revoke a session keep working while the session is live. A control the subject cannot operate is not consent.

Money is off limits, whatever the role

Recording a payment, voiding, refunding, cancelling a settled bill, releasing a table unpaid and adjusting a drawer are all refused for the duration. Ask the venue to do it, or leave first.

Break-glass is read-only and needs two people

Where no owner is reachable, a second operator co-signs, the session may look and never touch, and it runs fifteen minutes instead of thirty. The requester cannot be their own second pair of eyes.

It lands in your ledger, not only ours

The request, the answer, the entry and the exit are written into your venue's own audit trail, where you see them.


Data at rest

Guest telephone and email AES-256-GCM under the deployment's own key. Staff read them on screen; a copy of the database without the key does not.
Guest telephone, for lookup A one-way keyed hash. A returning guest is recognised by number without the number being stored readably, and erasure clears the index as well as the value.
Two-factor secrets AES-256-GCM, same key.
Passwords and PINs Argon2id. Hashed, not encrypted — there is nothing to decrypt.
Card numbers Never stored. The last four digits and an authorisation reference are all this application ever receives; payment runs through the gateway's own hosted fields or a certified terminal, and we get a token back. Removing card data from scope is worth more than any control that could protect it.

There is no key-management service and no external secret store. The application key is a file on the host, denied over HTTP, and what depends on it is written down. That is a smaller claim than most security pages make, and it is the true one.


The application itself

SQL injection Prepared statements exclusively. A value is never interpolated into a query, and a build rule fails on a raw concatenation rather than trusting the next person to remember.
Cross-site scripting Output is escaped by default on the server and again in the browser helpers, under a content security policy that admits scripts from this origin and a per-request nonce.
Inline event handlers Blocked at commit. An attribute handler cannot carry a nonce, so permitting one would mean permitting inline script generally and losing the policy. Every listener in this product is attached in script.
Cross-site request forgery A per-session token on everything that changes state, and cookies that do not travel to another site.
Clickjacking The site refuses to be framed at all.
Mass assignment Explicit allow-lists per endpoint. Nothing hydrates a record straight from a request body.
Third parties on the render path In production the stylesheet is compiled and self-hosted, so no external origin is contacted to draw a page — which is also what lets the policy above stay strict.

What is still open

This is the section most security pages do not have. It is here because the rest of the page is only worth reading if this one exists — and because a buyer weighing us against something larger deserves the real comparison rather than a flattering one.

Detection is not automated

There is no alerting system yet. An incident is found by a person looking, which means the time between something happening and somebody noticing is not bounded by anything. This is the largest open item on the page.

No public status page

Incidents are recorded internally and there is a screen for them, but nothing public reads it. So today you would hear from us directly rather than see it on a page.

Nothing measures uptime

Which is why the terms carry no availability commitment. A percentage would be invention dressed as telemetry.

The application key cannot be rotated in place

There is no re-encryption path. Rotating would mean every enrolled authenticator re-enrolling, so it is a planned operation rather than a routine one.

No archive tier

One database, no cold storage. The retention sweep is written so that the missing tier costs data nobody agreed to lose rather than quietly losing it — the privacy policy explains the arithmetic.

The payment gateways have never run live

The drivers are written and tested against published documentation. None has been exercised against a live merchant account, and going live is refused until credentials have been verified with a real read against the acquirer.

Two things this page does not claim, which similar pages usually do: an independent penetration test, and a tested disaster-recovery target. Neither has happened. When one does, it will appear here with a date on it.


Reporting something

If you have found a vulnerability, tell us before you tell anybody else and we will not come after you for it. That safe harbour has two conditions and they are the only two: do not access another venue's data, and do not degrade the service for the rooms running on it.

Disclosure mailboxsecurity@rosflow.invalid
security@rosflow.invalid

Acknowledgement within 24 hours, triage within 72, and a fix timeline that follows the severity rather than the calendar.

Everything else about reaching us, including which channels genuinely exist, is on Contact.