Let's talk
security

The generated app client attached the wrong credential

An API client generated from a live schema guessed which credential each route needed from its path prefix, and got one pair backwards. It failed safe.

· · updated

An open laptop on a small office desk in soft window light; beside it the children's learning app's module editor with a module's text and its source material.

A parent presses a button labelled Remove this tablet. The app sends the child’s device token instead of the parent’s, the server refuses with a 401, and the parent, who had every right to use that screen, sees a button that does nothing. It would have failed that way every time.

The bigger risk was the one next to it. The same mistake, made the other way round, would have handed a child’s tablet a credential that reaches everything in the household, generated automatically into every screen at once. Nothing harmed a customer here: the error failed in the safe direction. But a supplier who builds authorisation on generated code needs to be able to say which parts of it were copied from the server and which were guessed.

What was actually going on

A mobile app of 47 screens was being rebuilt against a server with 96 operations. The whole API layer was generated from the server’s own live schema, so that whether a button points at the right endpoint became a compile error rather than a review question. Screens that reference an operation that does not exist do not build.

The generator went one step further and worked out which credential each route needed. Two families of routes exist. One is called by a paired tablet acting for itself, presenting a device token scoped to exactly one child. The other is called by a parent managing those tablets, presenting an ordinary user token.

The generator told them apart by testing whether the path started with the singular word for device. But the plural path starts with the singular one: /devices/{id} begins with /device. A prefix test cannot tell them apart and does not fail loudly. It quietly produces a correct-looking function carrying the wrong token.

The server knew the answer. Every route declares its authentication dependency explicitly in its own definition. That fact was not in the schema the generator read, so it found a correlated signal in the data it did have, and the correlation was about 95 percent right.

What we changed

The correct fix is to emit the security requirement into the schema for each operation, so the generator reads a declaration instead of inferring one. Where that is not possible, the generator should refuse to build when it cannot determine the credential rather than pick the likelier of two.

What was done is smaller: a regression test now pins the specific pair of paths. It does not make the derivation correct. It makes this particular wrong answer impossible, and it documents in a test file what took two people to work out: the singular is a tablet acting for itself, the plural is a parent managing tablets, and they look identical to a string comparison.

What it did not fix

The inference is still an inference. Any other route pair where one path begins with another is an untested assumption until somebody writes the test for it.

The severity of a naming bug was decided by the alphabet. Had the parent-facing route been the shorter string, the same prefix test would have attached a parent token to a child’s tablet. That is not a basis on which to run an authorisation model.

The same shape appears wherever authorisation is decided from a path: prefix rules in a gateway, middleware applied to everything under an administrative prefix, a firewall rule matching a URL stem, a logging redaction rule that skips paths beginning with a public prefix. The rule is written when there are three routes and stays written when there are ninety-six. Two habits help: match the whole path segment and not the substring, and test the near miss, the pair hardest to tell apart.

The generated client itself is worth keeping. What the schema stated, the generator got right every time. What the schema implied, it guessed.

What to ask your own team or supplier

  • In our generated code, which facts are copied from the server and which are inferred from names?
  • Is the credential each route needs declared in the schema, or derived from its path?
  • If a rule cannot be determined, does the build fail, or does it pick the likelier answer?
  • Do the tests cover the two routes that are hardest to tell apart, not only the easy ones?
  • Where does any gateway or middleware rule decide access from a URL prefix?

Where this ends up

Drawing the line between what is transcribed and what is inferred is part of how the bespoke systems described under custom software development get built, because a generated client is only as trustworthy as the boundary somebody can name inside it.

Working on something like this?

We build this kind of software, and we staff the teams that do.

Get in touch