Suppose you sign in to a notes app and see only your own records. Did the server send only yours, or did it send everyone’s and let the screen filter them? This article did not test that screen. It compares responses and stored state across three before/after pairs in a local teaching example with two synthetic users, four notes, and a deliberately omitted owner check. It does not assess real-app security or platform failure rates. Reading, updating, and deleting were checked separately.

This is a conceptual illustration, not a test screenshot or evidence of isolation.
In this article
What the sources say: identity and permission
Authentication checks who is making a request. Authorization checks whether that person may perform a particular action on a particular record. The OWASP API security guidance calls for authorization checks in every function that acts on a supplied record ID. The OWASP prevention guidance also explains that hard-to-guess IDs cannot replace authorization. Both documents were rechecked on October 7, 2026. This experiment did not compare how hard IDs were to guess.
Recorded observations: an example with the owner check deliberately omitted
The before version tested on October 7, 2026 distinguished synthetic identities but deliberately omitted the owner condition. The after version used the server’s mapping of synthetic tokens to A or B to apply an “only that person’s records” condition to list, read, update, and delete operations. This experiment did not discover a naturally occurring error made by an AI app-building tool.
The records were A1 and A2, owned by A, and B1 and B2, owned by B, plus a nonexistent ID, Z0. Tokens were test identity labels attached to requests. Signed-out visitor N sent no token; an invalid token was tested separately. There was no real login test. The intended rule allowed A and B to list, read, update, and delete only their own records, and denied every request from N. Direct operations targeted only A1, B1, and Z0; A2 and B2 were checked only as list members. Invalid-token tests covered only the collection and A1.
The same 36 cases ran in three pairs: before → after, after → before, and before → after, for 216 requests in total. All four records were restored before every request. A frozen driver and validator checked raw responses and separately collected full before/after stored states against the predefined expectations. This was not an experiment asking an AI to execute and grade the reader template.
Before the fix, B’s read of A1 returned its body, 합성 A 메모 (Korean for “synthetic A memo”). B’s update changed A1’s body to Synthetic replacement, and B’s delete removed A1. These were three independent requests, each starting from the original state. After the fix, all three were denied without returning record data, and the complete stored state remained unchanged. The test notes mixed Korean and English; the gloss here was not part of the test.
Corresponding results were identical in all three pairs. Every count below is per version per run (36 cases). “Match” means that the allow/deny decision, returned data, and full post-request stored state matched the predefined owners-only rule. A → B1 means A requests B1.
| Cases and expected behavior | Before the fix | After the fix |
|---|---|---|
A/B lists: only each user’s own 2 records |
2/2 violations: both returned 4 records |
2/2 match |
A → B1 and B → A1 direct read, update, delete: deny |
6/6 violations: all allowed |
6/6 match |
A → A1 and B → B1 read, update, delete: allow |
6/6 match |
6/6 match |
A/B → nonexistent Z0 read, update, delete: deny |
6/6 match |
6/6 match |
N’s list and read, update, delete requests for A1, B1, Z0: deny |
10/10 match |
10/10 match |
Attempts to change A1’s owner to B and B1’s owner to A: deny |
2/2 match |
2/2 match |
Invalid token: list and A1 read, update, delete: deny |
4/4 match |
4/4 match |
In every run, the before version violated the policy in 8 of 36 cases; the 28 cases expected to remain correct matched 28/28. The after version matched 36/36. The before version also matched its deliberately programmed predictions 36/36, but its eight policy violations still count as violations. Only the two lists and six cross-owner direct operations changed between versions.
Before and after the fix, A’s read of A1 and B’s read of B1 returned the correct data. Their updates changed only the target record’s body, and their deletes removed only the target record. Before the fix, each list response sent all four records. Afterward, each list returned the user’s own two records exactly once and left stored data unchanged. What a screen displayed was not measured.
Requests from N and requests with the invalid token were denied with 401 (identity not recognized); owner-change attempts were denied with 400 (invalid request). Those results, and A/B’s denials for nonexistent Z0, already held before the fix. Passing these controls alone can coexist with cross-user access. In each of the three after runs, all 6/6 direct requests for another user’s record returned the same 404 (“not found”) response body as the corresponding requests for Z0. A 404 is this example’s chosen response, not a requirement for every app. Matching response bodies do not establish equal response times or identical internal processing paths. Errors and unexecuted cases were 0/36 in each of the six runs, or 0/216 overall.
Reproduction details: macOS 27.0 arm64, Python 3.9.6’s built-in sqlite3 module, and SQLite 3.54.0; communication stayed on this computer (127.0.0.1). Code, data, and expected results were frozen before measurement, and file fingerprints (hashes) were checked for changes. Measurement took approximately 0.945 seconds and analysis took 73.4 seconds; additional external cost was USD 0. These figures do not represent total production time. There was 1 mechanical summary correction before the freeze and 0 corrections during measurement. A separately kept experiment record retains the raw evidence, stored states, and method.
Author’s view: ask for an access table and evidence together
The following untested reader template adapts checks from this local example. Passing it does not guarantee that an app is secure. Lock the intended table before execution, then have the reader or a separate frozen validator compare it with actual raw responses and full state. An AI’s summary cannot substitute for that procedure.
- Fill in the access table
Test only in an isolated test environment for your own app.
Use a disposable copy whose entire store contains only synthetic data.
If a denial check fails, an update or deletion really happens.
Record type: ____ / Ownership and sharing rules: ____
Lock each cell’s intended allow/deny decision and data scope before execution:
List requests: A ____ / B ____ / signed-out visitor ____
This example’s list rule: include own records, exclude other users’ records.
Requester → target Direct read Update Delete
A → own records ____ ____ ____
A → B’s records ____ ____ ____
B → own records ____ ____ ____
B → A’s records ____ ____ ____
Signed-out visitor → each target ____ ____ ____
Nonexistent ID ____ / Invalid token ____ / Attempted owner change ____
For other designs, add cases and expectations (not tested here):
Shared access ____ / Admin or service privileges ____ / Whole-list denial ____
- Send the AI this request
Run each cell only in the isolated synthetic-data environment above.
Use the same request method and privileges as an ordinary user.
Save files containing the test identity, actual request, and raw response.
If unexpected real data appears, stop and redact it. Do not send its contents to an AI.
Collect stored state through a separate local test tool, not a new HTTP inspection feature.
Do not use its privileges for user requests or leave it in the deployed app.
Restore the synthetic data before each request and save the full before/after state files.
Do not change the intended table to fit results or fill unexecuted results with predictions.
Compare separately: missing own records, other users’ records appearing,
all records unchanged after denial, and only the intended record changed
by a legitimate update or delete.
Do not count errors or unexecuted cases as successful denials; mark them separately.
After a fix, rerun the same table.
A reply such as “I applied it” does not establish the result on its own. Open the saved requests, raw responses, and full-state files and compare them with the intended table. Your app-building tool’s and database’s documentation provides context for authorization and user requests; it does not replace execution evidence. Another design may correctly reject an entire list rather than return a subset, so define its expectations separately.
A row is one stored record. This experiment tested an application owner check on a SQLite file. It did not test database-native row-level security (RLS), which restricts row access inside the database for roles subject to it. This table did not assess administrator or service privileges, or direct access to the database file.
Untested areas include real login, authentication strength, passwords, sessions, stolen tokens, sharing and administrator roles, concurrent requests and race conditions, screens and deployment, direct database-file access, exports, backups, storage, information inferred from timing differences, and every possible bypass. Creating records was not tested; it was used only to prepare the initial data. Cloud systems were also untested. There was no comparison between platforms, authentication systems, native RLS and application checks, or alternative defenses. The three repetitions show only that the result repeated in this local setup. They do not show how often AI app-building tools or platforms make this mistake, that AI caused the flaw, or that any production app is secure.
AI assisted with research and drafting. The author reviewed the final manuscript and directed the revisions.
답글 남기기