Part 1 of 4 · Simplicity Is Security
Simplicity Is Security: Intentions and Unintentions
A program can do what we asked and still do something dangerous.
In the 1990s, when I tried to contribute to OpenBSD, Theo de Raadt described secure software to me: it does what it’s specified to do, and it doesn’t do what it’s not specified to do. The first half sounds like ordinary software development. The second half is where things get interesting.
We can demonstrate that a program does something we wanted. We give it an input, observe the result, and compare that result with an expectation. But what else can it do? What happens with a different input, a failed dependency, an unexpected sequence of requests? What can someone persuade it to do with the permissions we gave it?
Those questions change the meaning of a working program. They also change the meaning of simplicity. A program isn’t simple merely because it’s short or easy to write. For security, we need to understand what behavior exists, what authority makes it possible, and what prevents that authority from being misused. We need it to be simple in what it can do.
This is the first of four essays in Simplicity Is Security. It begins with the part of a security contract that ordinary functional tests can most easily overlook: what the software must not allow.
The successful request
Imagine an API that returns a document when given its identifier. The response is well-formed. The content matches the requested document. Every test passes.
Then someone changes the identifier and retrieves another customer’s document.
Both identifiers are valid. Both documents exist. The database lookup succeeds. The server sends another ordinary 200 OK, this time containing information the caller should never have received. Nothing had to crash for the system to fail.
The program hasn’t failed to retrieve a document. It has failed to respect a boundary. If the specification said users could retrieve only documents they were permitted to read, the implementation is wrong. If the specification said nothing about access, the specification is inadequate. Either way, retrieval did not sufficiently establish what may be retrieved.
This isn’t a distinction between correctness and security as competing goals. Authorization belongs in the account of what correct behavior means. The difficulty is that a successful example can leave a missing requirement invisible. A test that says a request returns a document by ID also needs to establish who is making that request and why they may receive it.
The negative space of security
Saltzer and Schroeder identified this problem in their 1975 paper, The Protection of Information in Computer Systems. They described this example as “the negative quality of the requirement to prevent all unauthorized actions.”1 One successful request tells us something useful. It doesn’t tell us that unauthorized requests will fail.
We can test the opposite case: a user asks for a document belonging to another customer and receives nothing. We can test an unauthenticated request, or a former administrator whose privileges have been revoked. These tests matter. But each examines a condition we thought to try, not every circumstance in which the program might reveal the document.
Nor is the negative space limited to access control. Imagine a search service that accepts a regular expression. An ordinary expression returns the expected matches. A carefully chosen one may drive the matching engine into pathological backtracking, tying up a worker for far longer than the request should be allowed to consume—a regular expression denial-of-service (ReDoS) attack. The feature did what its author intended for ordinary searches. The service also allowed an unintended use of its resources.
Regular-expression handling can also lead to remote code execution (RCE), not just denial of service. Disclosures between December 2025 and October 2026 include two GitLab flaws involving crafted expressions in CI/CD configuration,23 a heap buffer overflow involving NGINX’s regex capture variables that could crash workers or permit code execution under additional conditions,4 and RCE in pgAdmin’s PLAIN-format restore path, which attempted to screen input with a regular expression.5 The failures differ: parsing and memory handling can be unsafe, and using a regular expression as a security filter doesn’t establish that later processing is safe.
A different system might produce the correct report while exposing a secret in its logs, writing an unexpected file, or contacting a network address it was never meant to reach. These are different failures, but they invite the same question: what else happens while the requested work succeeds?
We won’t find the answer by inventing a longer list of malicious-looking inputs. First we need to decide which behaviors and effects are permitted. Only then can we judge whether the implementation stays within those boundaries.
Permission, not exclusion
Saltzer and Schroeder put one useful design rule plainly: “Base access decisions on permission rather than exclusion.”1 They called this fail-safe defaults. Instead of collecting reasons to reject particular callers, establish the conditions under which access is allowed.
There are systems that take this principle further than an access check in application code.
Apache Accumulo is a distributed key-value database, originally developed at the NSA, with an unusual security feature: individual stored values can carry their own visibility requirements. It’s not just the data that is hidden either; the very existing of the field is not disclosed unless the user has permission to see it. Two users can query the same table, even the same row, and receive different keys and values because the database evaluates those requirements as it processes each scan.6
A value might carry the visibility expression PROJECT_X&LEGAL. Reading that value requires both authorizations. Another value in the same row might require only PROJECT_X. When a scanner supplies authorizations that don’t satisfy a value’s expression, Accumulo excludes that value from the results.
This moves an important security decision out of individual application endpoints. A developer retrieving records doesn’t have to remember to filter every returned value according to its visibility label. The database enforces that constraint as part of the read operation.
But that doesn’t eliminate the need to decide who is authorized. Accumulo permits applications to connect through a service account with broad authorizations, then create scanners using the narrower authorizations appropriate to each application user. Its documentation explicitly describes this arrangement.6 If the application mistakenly scans using all the service account’s authorizations, Accumulo can return information that the application user should never receive. The database has enforced the rule it was given. The application supplied too much authority.
The labels themselves must also be correct. A value written without the necessary visibility restriction may be readable by users who have permission to access the table. Enforcing an incomplete policy doesn’t make that enforcement correct.
This illustrates both the promise and the difficulty of building security into a system. We can make authorization part of the mechanism that retrieves data, rather than leave every caller responsible for remembering it. But someone must still define the permitted behavior, assign the right labels, and establish which authorizations a request may exercise.
The simpler contract is not necessarily the shorter implementation. It is the one with fewer independently interpreted security decisions.
And the principle extends beyond databases. A parser shouldn’t need the application’s database credentials. A reporting worker shouldn’t have unrestricted network access. A user permitted to read a record shouldn’t acquire permission to modify it merely because both operations use the same service.
What testing can tell us
The security-testing community has ways to make these rules concrete. OWASP ASVS 5.0.0 requirement 8.2.2 calls for verification that access to specific data items is restricted to consumers with explicit permission.7 The OWASP Web Security Testing Guide describes trying an object identifier from one account while authenticated as another.8
For our document service, the method is straightforward. Give two accounts different documents, confirm that each can retrieve its own, then try crossing the boundary. If the request succeeds, we’ve demonstrated a vulnerability. If it fails, we’ve learned that this particular path, for these accounts and this state, enforced the rule.
With Accumulo, we can make the experiment more interesting. Give two users different authorizations and store values requiring each set. Verify that a scan using one user’s authorizations doesn’t return the other’s protected values. Then exercise the application that uses the database, especially any shared service account, and determine whether the application limits each scan to the authorizations its actual user possesses.
Testing the database’s visibility enforcement is not the same thing as testing the application’s authorization decisions. Both matter. The requirement states what should hold. The test challenges the implementation. Neither should be confused with a guarantee that every way of reaching the same data has been examined. A useful assessment makes its scope visible: which routes, roles, configurations, and failure conditions were actually tested, and what remained outside the examination.
We cannot exhaustively test every combination of input, state, timing, and failure in a real system. Generative tests broaden the cases we try by producing inputs from rules rather than a hand-written list. Property-based tests go further: they state a property and search many generated cases for one that violates it. A failing case is useful evidence; a large collection of passing cases is not a proof.
Formal methods can establish stronger claims. Model checking can explore every reachable state of a finite model and find a sequence that violates an invariant. Specification languages such as TLA+ let us state properties of concurrent state transitions before—or alongside—an implementation. Proof assistants and program logics can verify that an algorithm meets a formal specification. Each claim is conditional: on the model, the stated property, the assumptions, and any argument that the implementation matches the verified artifact.
Testing and verification are valuable precisely because they give us evidence. They become misleading when we turn that evidence into a broader claim than it supports.
Generally speaking though, finding no violation is not the same as proving that none is possible.
Doing nothing else
The surprising part of unintended behavior is how ordinary it can look. Reading a document, executing a search, generating a report: these are legitimate operations. The failure may be in who can invoke them, what resources they can consume, or what other effects occur along the way.
That is why the phrase does nothing it’s not specified to do is demanding. It is not a request for a list of features followed by tests showing they work. It asks us to identify the powers a system exposes and the conditions under which those powers may be exercised. It asks us to notice capabilities the acceptance criteria rarely expose.
Accumulo provides a useful example of what an explicit boundary can accomplish. The database doesn’t need to understand why a person belongs to a project or why a particular document is sensitive. It needs to evaluate a visibility expression against a set of authorizations. That is a smaller problem, one with a rule the database can enforce consistently.
Simplicity isn’t a line count. It is fewer unexplained decisions, fewer unexpected interactions, and fewer opportunities for software to do something nobody intended.
De Raadt’s second half remains the demanding one. Doing what’s intended demonstrates a feature works. Knowing why it won’t do what’s unintended demonstrates that a feature is more secure.
-
Jerome H. Saltzer and Michael D. Schroeder, The Protection of Information in Computer Systems, §I.A, Proceedings of the IEEE, September 1975. The paper discusses the negative requirement to prevent unauthorized actions and the principle of fail-safe defaults. ↩︎ ↩︎
-
NIST NVD, CVE-2026-97229, published October 13, 2026. Insufficient validation of a crafted regular expression in GitLab CE/EE CI/CD configuration could, under certain conditions, allow an unauthenticated user to execute arbitrary code on the GitLab server. ↩︎
-
NIST NVD, CVE-2026-89078, published September 24, 2026. A double-free defect while parsing a crafted regular expression in GitLab CE/EE CI/CD configuration could allow an authenticated user to execute arbitrary code under certain conditions. ↩︎
-
NIST NVD, CVE-2026-93479, published October 14, 2026. A heap buffer overflow in NGINX Plus and NGINX Open Source involves regex captures in
mapprocessing and particular variable-evaluation conditions. Crafted HTTP requests can cause worker restarts and denial of service; code execution additionally requires ASLR to be disabled or bypassed. This is a data-plane issue, not control-plane exposure. ↩︎ -
NIST NVD, CVE-2025-13780, published December 11, 2025. pgAdmin 4 through version 9.10 could allow command injection and RCE when running in server mode and restoring PLAIN-format dumps. The affected 9.10 restore implementation used a regular expression in
has_meta_commandsto screen for psql meta-commands. The NVD record describes restore-path RCE, not a regex-engine defect. pgAdmin is a PostgreSQL administration tool, not the PostgreSQL database engine. ↩︎ -
Apache Accumulo, Authorizations, Security Overview, and Accumulo Clients, version 2.x documentation. Accumulo evaluates column-visibility expressions against the authorizations supplied for a scan, which must be a subset of the Accumulo account’s authorizations. The documentation describes the use of broadly authorized service accounts with narrower scanner authorizations for individual application users. When no authorizations are explicitly supplied, a scanner uses the account’s authorizations. An empty cell visibility with an empty table-default visibility allows access to users with table-read permission; see Server Properties. ↩︎ ↩︎
-
OWASP, Application Security Verification Standard, version 5.0.0, V8.2.2, “General Authorization Design.” This Level 1 requirement addresses explicit permission for specific data items. ↩︎
-
OWASP, Web Security Testing Guide, version 4.2, WSTG-ATHZ-04: Testing for Insecure Direct Object References, “How to Test.” ↩︎