Rows of the caller only
For every table your groups reach, it checks that an active pre-hook limits every API to the rows of the person behind the request, merges the filter with $and, stamps the owner on writes and refuses aggregate and distinct.
API Maker
The framework for AI era
Security
Find where a person could read data that is not theirs, and fix it in one click.
API Maker reads your groups, API users, auth providers, settings and the code of your pre-hooks, and shows where a person could read or change rows that are not theirs. A leak comes with the request that proves it, and many findings with an action that fixes them in one click. The report lives in your Git repository, where your AI assistant can read it too.
Step through it, slow it down, or open the full canvas.
It reads the whole account. Groups, API users, auth providers, settings and the code of every pre-hook, read and never run. Each open finding takes points off 100: 20 for a critical one, 10 high, 4 medium, 1 low.
Groups, API users, auth providers, settings and the code of every pre-hook, read and never run. Each open finding takes points off 100: 20 for a critical one, 10 high, 4 medium, 1 low.
The group lets the shop app read orders and alice signs in with her own token, but no pre-hook limits the rows: she gets the orders of every customer. The finding comes with the request that proves it.
A row scoping pre-hook: the owner column goes into every filter with $and, the owner is stamped on every write, aggregate is refused. You can edit the code first. The account is scanned again right after.
The same request returns the two orders of alice. Asking for the orders of customer 23 matches nothing: her filter is joined to the owner with $and. Aggregate, which no filter can scope, gets 403.
Revoke a sensitive system API, block inline SQL, or skip a finding with a reason, like reference data everyone may read. A skipped finding leaves the score and its reason is kept in git.
With a local client connected, a change in the account is scanned within minutes and the report is committed to git. Your AI assistant reads it there, fixes the finding, and the next scan confirms it.
For every table your groups reach, it checks that an active pre-hook limits every API to the rows of the person behind the request, merges the filter with $and, stamps the owner on writes and refuses aggregate and distinct.
Broad flags like all collections or all system APIs, sensitive system APIs such as EXECUTE_PLAIN_QUERY and GET_SECRET, bulk update and delete grants, and groups nobody uses.
Passwords inside tokens or stored in clear, providers without a groups column, tokens that live longer than 30 days, and APIs that need no token at all.
Add a row scoping pre-hook, add a column to a token, require a person token, freeze a group to an explicit allow-list, revoke grants or block inline SQL. You see the code before it is saved.
Some findings are fine, like reference data everyone may read. Skip them with a reason: they leave the score, and the reason is kept in git for your team.
While a local client is connected, changes are scanned on their own and the report is committed to git: README.md, report.md, report.yaml and actions.yaml.
Customers sign in with their own token and the app calls the generated APIs of orders. The report sees that no pre-hook limits orders to the rows of the caller, shows the request any customer could send to read every order, and installs the pre-hook in one click. The next scan confirms it.
# every row of "orders", with the token of any person allowed on it :curl "$BASE/api/schema/$ADMIN/shop/main/orders?limit=1000" \ -H "x-am-authorization: $APP_TOKEN" \ -H "x-am-user-authorization: $PERSON_TOKEN"A finding about rows shows how it is exploited. Here any customer gets every order, because no pre-hook of orders reads the person behind the request.
const OWNER_COLUMN = 'customer_id';const IDENTITY_FIELD = 'id';async function main(g: T.IAMGlobal) { // Custom APIs, schedulers and events run under their own rules : only real callers are scoped. if (!g.req.isApiRequestFromUser) return; const person = g.req.auth?.authAMDB; const me = person ? person[IDENTITY_FIELD] : undefined; // ... 401 without a person token const scope = { [OWNER_COLUMN]: me }; // MERGE, never replace : $and keeps what the caller asked for and adds the owner on top const merge = (existing: any) => (existing && typeof existing === 'object' && Object.keys(existing).length ? { $and: [existing, scope] } : { ...scope }); const apiId = g.req.reqInfo?.apiInfo?.id; if (QUERY_FIND_APIS.has(apiId)) { g.req.query.find = merge(g.req.query.find); stamp(g.req.body); // update-by-id and replace-by-id : the owner of a row can not be handed over return; } if (BODY_FIND_APIS.has(apiId)) { g.req.body = g.req.body || {}; g.req.body.find = merge(g.req.body.find); if (g.req.body.updateData) stamp(g.req.body.updateData); // update-many return; } // ... save and master-save : the owner is stamped on every row // Fail closed : aggregate and distinct can not be scoped generically, so they are refused. g.res.statusCode = T.EStatusCode.FORBIDDEN; throw new Error(`API ${apiId} is not available for row scoped users.`);}module.exports = main;Generated for the owner column and the field of the token you pick, shortened here. Edit it before saving if you want: the report reads the code again on every scan.
# API Security Report> Generated by API Maker. Read only : work on the findings below, then rescan from the admin panel. See README.md.- Generated at : 2026-09-29 10:00 UTC- Score : **52 / 100** (grade D, Data at risk)- Findings : 5 open, 0 skipped, 0 fixed...## Findings### Row gate : does every collection limit persons to their own rows ?#### [CRITICAL] "orders" is not scoped to the rows of the callerNext to it: README.md for people and AI assistants, report.yaml with the same content, and actions.yaml with the skipped findings and their reasons.
No. It reads the settings of the account and parses the code of your pre-hooks as TypeScript, without running it. Nothing changes until you click an action.
Every open finding takes points off 100: 20 for a critical one, 10 for high, 4 for medium and 1 for low. Skipped and fixed findings take nothing. 90 and more is grade A, then B from 75, C from 60, D from 40, and F below.
It leaves the score and keeps its reason. The reason is written to actions.yaml in git, so your team, or an AI assistant working on the repository, knows the decision. You can open it again at any time.
While a local client is connected, the next scan comes 100 times the duration of the last one later, between 1 and 30 minutes. When nothing changed in the account, nothing is scanned, saved or committed.
Yes. It reads report.md in src/API Security Report/, fixes the hooks, groups or providers in their own folders and commits. The next scan shows the result.