How to Scope the First Release of Enterprise Software
A first release should let one team run one complete workflow. What to decide now, what to postpone, and how to define done.
Software Development
An audit trail records what people did; a permission model decides what they may do. Both are cheap to design in early and very hard to retrofit.
Most internal business applications start life with two security features: a login screen and a single "admin" checkbox. That is enough for a pilot. It stops being enough the first time someone asks one of two questions: "Who changed this record, and why?" or "Why could that person see this at all?"
Both questions are about the same underlying design: a permission model that decides what each person may do, and an audit trail that records what they actually did. This article sets out how to design both for internal operational software such as approval systems, case management tools and finance workflows.
NIST's glossary defines an audit trail as a chronological record that reconstructs and examines the sequence of activities surrounding an operation, procedure or event "from inception to final result". The important word is reconstructs. An audit trail is not a debug log, and it is not the "last modified by" column on a table. It is a record complete enough that someone who was not present can work out what happened, in what order, and who was responsible.
That sets a practical test. Pick any important record in your system, such as a payment approval, a customer credit limit or a contract status, and ask whether you could answer the following from stored data alone:
If the answer to any of these is "we would have to ask around", the system does not have an audit trail.
The OWASP Logging Cheat Sheet puts the requirement simply: application logs must record "when, where, who and what" for each event. For an internal business application, that translates into a small, consistent event structure:
The same cheat sheet is equally clear about what should usually stay out of logs: passwords, session identifiers, access tokens, encryption keys, payment card data and sensitive personal data should be removed, masked, hashed or encrypted rather than recorded directly. An audit trail that copies full customer records into every event creates a second, less protected copy of your most sensitive data. Record the identifier and the changed fields, and apply the same data protection thinking you apply to the main database.
Write audit events at the application layer, in the language of the business process, and in the same transaction as the change they describe. A database log can tell you a row was updated; only the application knows that a manager approved a payment while covering for a colleague.
An audit trail that administrators can edit is an opinion, not evidence. OWASP's guidance on protecting logs at rest recommends building in tamper detection so you know if a record has been modified or deleted, copying log data to read-only storage as soon as possible, recording and monitoring all access to the logs, and restricting and periodically reviewing the privileges to read them.
In practice, for an internal application this usually means:
If the audit trail records what happened, the permission model decides what is allowed to happen. Three principles from the security literature do most of the work.
NIST defines least privilege as restricting the access privileges of users, and processes acting on their behalf, to the minimum necessary to accomplish assigned tasks. OWASP's Authorization Cheat Sheet adds a useful nuance: least privilege must be applied horizontally as well as vertically. Seniority is not a permission.
The same OWASP guidance recommends that an application deny access by default: when no rule explicitly permits a request, the answer is no. A new record type, report or API endpoint added in a hurry should be invisible until someone decides who may use it, not visible to everyone until someone remembers to restrict it.
NIST's glossary describes separation of duty as the principle that no user should be given enough privileges to misuse the system on their own, with the classic example that the person authorizing a paycheck should not also be the one who prepares it. It notes this can be enforced statically, by defining roles that cannot be held by the same person, or dynamically at the time of access, as with a two-person rule where the second approver must be a different user from the first.
Dynamic enforcement is usually what internal applications need. It is not enough that "requester" and "approver" are different roles if one person holds both; the system should check, at the moment of approval, that the approver is not the person who raised the request.
Most internal applications start with role-based access control (RBAC): permissions are attached to roles such as "Viewer", "Editor" and "Approver", and users inherit them by holding a role. RBAC is easy to explain and easy to review, which is a genuine advantage.
Its weakness shows when "approvers can approve" becomes "approvers can approve requests from their own department, up to their limit, unless they raised the request". Pure RBAC handles that by multiplying roles until nobody understands them. OWASP's Authorization Cheat Sheet recommends preferring attribute-based (ABAC) and relationship-based (ReBAC) access control over RBAC, and stresses that the choice has implications for the whole development life cycle and should be made as early as possible.
A pragmatic approach for most internal systems is a hybrid:
Whatever the model, keep the rules in one place. Authorization logic scattered across screens, API handlers and reports is where inconsistencies hide.
Hiding a button is a user interface choice, not a control. OWASP's guidance is that permissions should be validated on every request, whatever its source, because an attacker only needs to find one way in; checking correctly on most requests is not enough. Two specific failure patterns account for a large share of real-world problems in internal tools:
/requests/1041 to /requests/1042 and see someone else's record, the application is checking whether the user is logged in, not whether they may see this record. Every lookup must check the relationship between the user and the specific object.OWASP also recommends automated unit and integration tests for authorization logic. A simple and effective pattern is a permissions matrix: for each role and each important action, a test that asserts both what should succeed and what should be refused. Refusal tests are the ones teams forget.
The permission model is only as good as the process for changing it. Three situations deserve explicit design:
A periodic access review closes the loop, and the audit trail makes it evidence-based: it shows who has actually used a permission, which is often a shorter list than who holds it.
Internal applications increasingly include AI features that draft, classify or recommend. The same rules apply to them, with one addition: the audit trail should distinguish between an action taken by a person and one proposed or taken by an automated component. If a model suggests an approval and a person accepts it, record both. If an agent acts automatically, it needs its own identity and a permission scope no wider than the task requires. Our articles on human oversight in autonomous AI systems and AI governance for enterprise software cover the oversight side of this in more detail.
These decisions belong in the first release, as the article on scoping the first release of enterprise software argues: they are cheap to build in and very hard to reconstruct later. More on how we approach this in our own engineering is on the software development page.
No. Application and infrastructure logs help engineers operate and debug a system. An audit trail is a business record, written at the application layer in the language of the process, that lets someone reconstruct who did what to which record and why. The two can share infrastructure, but they have different audiences, retention and protection requirements.
As few people as practical, typically the system owner, internal audit and security. Access to the audit trail should itself be logged and reviewed periodically, and nobody who can read it should be able to alter it.