Security
The walls are in the database, where the application cannot forget them.
Payroll is the most sensitive data a small company owns. Most systems protect it in the interface, which means a single missed check is a leak. Momiji puts the boundary one layer lower, in the database itself, and then runs a suite against production to re-prove it after every change. Here is exactly how, and exactly where the limits are.
- 01
Tenancy enforced in the database
Row-level security is enabled on every table in the application schema, without exception. Even the handful that hold no tenant data at all have it switched on and carry no policy, which means the database refuses them to every signed-in user rather than being trusted to go unasked. Your company's rows are separated by the database itself, not by a filter the application remembers to apply. References between companies are impossible by schema: keys are composite over the record and the company together, so a row in one company cannot point at a record in another, even by mistake.
- 02
Salary is revoked, not hidden
Salary and pay rate are revoked at the database column level. A direct read of the employee table fails for every signed-in user, including your HR admins. Compensation is released only through a gated call that checks People or Payroll authority first and records who read it and when, never the amount. If that record cannot be written the release still goes ahead, because a logging failure must never stop a pay run, and the system raises an integrity flag an administrator has to clear. New columns on that table are unreadable until explicitly granted: a fail-closed default we have had break a payroll run rather than leak a field.
- 03
Numbers we hold only if you ask us to
There is no SIN column in the schema. It was removed early and never returned: a SIN is keyed in only at the moment a T4 slip is generated, appears on that slip in your private vault, and is never written to a database field. Bank details were the same until direct deposit, and that reversal is narrow and yours to make. Leave direct deposit off, which is the default, and we hold no payment destination for anyone: net pay leaves your own bank against a printed wire sheet. Turn it on and each employee enters their own account on the web; the institution, transit, account number and name on the account are stored only as ciphertext under a key held in a vault outside the database, the table has row-level security on and no policy at all so no signed-in role can read a row, and the only way to the numbers is a call the account owner alone can make which writes an audit line naming who read whose account before it returns anything. The mobile app holds no grant on any of it.
- 04
An audit log with no eraser
The audit log carries a read policy and nothing else. There is no update path and no delete path from the application, for any role, including your own owner account. Entries are written only by a privileged database function that verifies membership before it inserts. What happened stays on the record.
- 05
A payroll register that cannot be edited
Once a run is committed, its register rows are immutable. There is no update or delete path from the application: an owner attempting either changes nothing, which our test suite asserts directly rather than assuming. A correction is a new reversing run that negates the original and leaves both on the record, the way an accountant would expect.
- 06
Every figure carries its own provenance
Each run and each row is stamped with the calculation engine version and the CRA tax table edition in force when it was produced, so a figure can be reproduced years later against the rules that actually applied. Preparer and approver are recorded separately, and where one person is both, the system forces a written self-approval note instead of allowing a silent one.
- 07
A training record that cannot be rewritten
A compliance completion is written once and never changed. It carries the version of the document the person read, the evidence they gave, the address the completion came from, and a sha256 over the whole record, and a database trigger refuses every later update and every delete, for an administrator and for the table's owner alike. The single permitted change is the employer's own countersignature moving from absent to present, once, with every other column identical; that field is deliberately outside the hash, because the second half of an acknowledgement is signed later by a different person. An exemption is its own record with its reason on it rather than a blank, and an evidence figure a manager reads on the board opens to the named people behind it.
- 08
A knowledge check whose answers nobody can read
The answer key to a compliance knowledge check lives in its own table with row-level security on and no policy at all, and every privilege revoked from both signed-in database roles, so no browser session reads a row from it however it asks. A submission is marked inside the database against that table, and the answers never enter the question paper staff can open. A question may not be marked required either, because the form engine treats a required tick box as an attestation and refuses a submission without it, which would publish the answer. The administrator who wrote the key reads it back only through a server route that checks their membership, their plan, their role and the ownership of the template, and writes an audit line naming them.
- 09
A record of exactly the file that was handed over
When an organization produces its training records for one location and one period, the pack is rendered on the server, stored in a private bucket under that organization's own prefix, and hashed there: the SHA-256 written into the record is taken over the very bytes that were stored and served, never over an intention to render them. The row carrying that digest, the line count, the day and the person who produced it cannot be changed afterwards by anybody, refused by a database trigger rather than by the absence of a screen, so a correction is a new pack and the old one stays on file. Each line inside carries the fingerprint of the completion it came from, which changes if a single field of that record changes. Being handed the file is checked again rather than assumed: before the bytes are released Momiji counts the people the pack covers against the people the person asking is allowed to see, and where those differ the file is not served and the refusal says nothing about why.
Every claim above is re-proven against production, on every change.
Security that is only reviewed once is security as of that day. Momiji ships with 151 automated suites, 7,028 assertions, that run against the production schema itself rather than a copy: tenant isolation, privilege escalation, confidential record visibility, and the payroll lifecycle. They run inside a transaction that is rolled back, so they test the real thing without touching it.
One of them signs in as your owner account, attempts to edit and then delete a committed payroll row, and asserts that nothing changed. That is the difference between a policy and a proof.
100%
of tables under row-level security
151
automated security suites
7,028
assertions every run
0
SIN or bank columns
How we review ourselves
Five rounds of adversarial review, run internally.
Before each major surface shipped, we ran a security round: a reviewer with no knowledge of how the thing was built, briefed only to break it. Five rounds so far, across tenancy, onboarding, expenses, payroll, and the people record.
None of them passed cleanly the first time, and we think that is the point. The third round returned a conditional failure: an ownership check that fell open when an identifier was null, which would have let a confidential record be written by someone who could not see it. It was fixed in a versioned migration with 42 assertions covering it, and the assertions still run today. The fourth round found compensation readable as a plain column, which is why salary is now revoked at the column level.
Every finding is recorded with a severity and closed by a migration, not a patch note. We say internal because that is what it is. It is a real practice and it has caught real defects, but it is not an external assurance report, and we will not let the word “audit” do work it has not earned.
A promise you cannot check is just marketing.
What we do not claim
The gaps, before you have to find them.
Every security page lists strengths. This is the part that tells you what we have not built, because the fastest way to lose your trust is to have you discover one of these yourself.
We have not been audited by anyone else
Momiji holds no SOC 2 report and has not had an external penetration test. Our security reviews are run internally, and we would rather say so than let a phrase imply otherwise. When a customer's own auditor wants to test us, we will take the engagement and publish what it finds.
Two-factor is one-time codes, and nothing stronger yet
Two-factor sign-in is in the product. It is a time-based one-time password, the six digits any authenticator app produces, and anybody can turn it on for themselves from their own profile. An organization can require it: owners and administrators are required by default, the owner adjusts which roles that covers, and somebody in a required role sets it up before they can use Momiji. We do not send codes by text message, because a text message can be redirected to somebody else without your phone leaving your pocket. What is not there yet is anything stronger or more self-serving than that: no passkeys and no hardware security keys, and no recovery codes you keep yourself, so a lost phone is cleared by an owner or an administrator at your own organization rather than by you. Every sign-in, setup, and invitation form is still behind a CAPTCHA and sign-in attempts are still rate-limited.
Some records are deliberately permanent
Receipts and generated T4 slips are retained on purpose, because expense and payroll records need to survive the person who filed them. The consequence is real and worth stating: a document filed in error cannot be deleted through the product today. If you need something removed, it is a conversation with us, not a button.
Documents are read by a model, off our servers
Setup and receipt extraction send your uploaded files to Anthropic, our named subprocessor, using our own enterprise key. Commercial API data is not used to train models. Spreadsheets and CSV files are scanned first: any column holding a SIN, bank account, transit, institution, routing number, or IBAN has its values replaced with a placeholder before the file leaves our servers. PDFs and photographs are sent whole, because reading them is the entire job we are asking the model to do. The extractor is also instructed to return none of those fields. So the honest summary is that structured files are redacted in advance and scanned ones are not, and you should know which of the two you are uploading.
Subprocessors
Who else touches your data.
Momiji is not the only company involved in running it. These are every one of them, what each holds, and why it is there. If a vendor is not on this list, it does not receive your data.
All of them are established providers under their own security and privacy terms. We hold no other processor, and we do not sell or share your data with anyone. If we add one, this table changes before the vendor does.
Asking harder questions
If you are running a security review, or your auditor is, write to hello@mymomiji.com and ask for the detail behind any line on this page. We will show you the migration, the policy, or the test that backs it. If the honest answer is that something is not built, that is the answer you will get.
Proof covers how the rest of Momiji is checked, including the defect that checking found in our own payroll engine. Privacy covers what this site collects. Terms covers the agreement.