Can someone quietly edit history?
No. Each audit row is hashed together with the previous row for the same record, the table triggers reject an update and refuse a delete, and a nightly job re-walks the chain and fails loudly if it does not verify.
Access in ComOps is a capability check that runs on the server before the action does. Behind it sits a record that is hard to alter after the fact: every audited change is hashed together with the change before it, and the database refuses an update or a delete on that table outright. The sections below cover those controls and the ones around them.
ComOps ships 120 permissions and nine seeded roles that draw on them: administrator, manager, salesperson, warehouse, accountant, viewer, purchasing, and two portal roles, one for customers and one for suppliers. The roles are yours to reshape. The check itself is server-side, at the action layer: the code that creates an invoice asks whether the signed-in user may create invoices before it writes anything, and refuses if the answer is no. Hiding a menu entry is a courtesy to the user; it is not the control.
Passwords are hashed with bcrypt at cost 12. A user can turn on two-factor sign-in with any standard authenticator app, to RFC 6238, and gets single-use recovery codes that are themselves stored hashed. Repeated failures are rate limited both per address and per account, and an account locks after a threshold you set, so a correct password against a locked account is still refused. Session cookies are set httponly and secure and restricted with SameSite, and the session handler runs in strict mode, so a session identifier the server never issued is not accepted.
Where an action needs somebody else to agree, the approval is a workflow instance rather than an email. An approver who is away delegates their step to a named colleague, and a step that sits too long escalates on its own: each instance carries the moment it becomes overdue, and a background job picks those up and moves them along. An absence therefore slows an approval down instead of stopping it, and both the delegation and the escalation stay in the record.
Each audited change is written with a SHA-256 hash taken over the canonical row together with the hash of the previous change to that same record, so the entries form a chain: alter one and every hash after it stops matching. Enforcing that is a separate job and sits lower down: the audit table carries database triggers that reject an update and refuse a delete, so the guarantee does not depend on the application behaving well. A verification job re-walks the whole chain nightly, keeps the verified tip in an anchor file outside the web root, and fails loudly when the chain or the anchor disagrees. The lighter activity feed people read day to day is a different table entirely, so trimming it can never reach the audit record.
A closed accounting period is locked, and unlocking it is gated on a permission most people do not hold, requires a written reason, and is itself written to the audit log with that reason attached. Alongside the locks run the invariant checks: read-only passes that recompute what the books should say and exit non-zero when they disagree, so a control that has been worked around surfaces as a failure rather than as a surprise at year end. Data reaches the database through prepared statements as the house rule, and reaches the screen through a single escaping helper the views call, so neither path depends on somebody remembering.
An uploaded file is accepted only if its sniffed type is one ComOps allows, and that type is read from the file itself rather than from the name the browser supplied; it is then stored under a random name, and downloading it takes a permission. The application interface loads nothing from a third party: no analytics, no tag manager, no font or script from somebody else's domain, which means an outside change can neither watch your people nor execute in their session. Database credentials and keys live in a file outside the web root, so a web server misconfiguration cannot serve them. The rest of the operating picture is on the hosting page.
No. Each audit row is hashed together with the previous row for the same record, the table triggers reject an update and refuse a delete, and a nightly job re-walks the chain and fails loudly if it does not verify.
Only someone holding the company settings permission, and only with a written reason, which is stored in the audit log along with who unlocked what and when.
Want to see the permission matrix against your own roles?