Skip to content

fix(edge): coerce cjson.null pathPrefix to nil for whole-project rules - #1017

Open
apple-ouyang wants to merge 1 commit into
oblien:mainfrom
apple-ouyang:fix/null-pathprefix-cjson-null
Open

apple-ouyang wants to merge 1 commit into
oblien:mainfrom
apple-ouyang:fix/null-pathprefix-cjson-null

Conversation

@apple-ouyang

Copy link
Copy Markdown

Fixes #1015

What was broken

A route rule with pathPrefix = NULL — a whole-project rule, the default created by openship edge rules add (no --path) or by POST /api/projects/:id/route-rules without pathPrefix — crashed every request on the affected host (500 site-wide), confirmed in production on 0.8.0:

lua entry thread aborted: runtime error: .../rules_guard.lua:41: attempt to get length of local 'p' (a userdata value)

Chain: serializeProjectRules emits "pathPrefix":null → mgmt_api.lua stores the list verbatim in ngx.shared.rules → rules_lib.parse() decodes with lua-cjson, where JSON null becomes the cjson.null userdata sentinel, not nil → in the guard's match loop p == nil, p == "", p == "/" are all false → string.sub(uri, 1, #p) takes #p on userdata → throw inside access_by_lua → every request 500s.

The fix

  • rules_lib.lua (parse): coerce a non-string pathPrefix to nil at the decode boundary. This is the load-bearing fix — fixing only the guard's match loop would still leave (chosen.pathPrefix or "/") concatenating userdata into the rate-limit key (cjson.null is truthy), crashing the same way for a catch-all rule that carries a rate limit.
  • rules_guard.lua: defensive type(p) ~= "string" check in the match loop so an un-normalized entry (hand-written dict data, a future producer) degrades to the catch-all instead of throwing on the request path.
  • rules-lua-contract.test.ts: new describe asserting both normalizations against the Lua source — the established pattern in this file, since there is no Lua runtime in CI.

Verification

  • luac -p on both modified Lua files: clean.
  • Ran rules_lib.parse + the guard's match loop under a plain Lua harness with cjson.safe/resty.lrucache stubbed and a userdata stand-in for cjson.null:
    • Before: attempt to get length of a FILE* value (local 'p') — the same crash class as production.
    • After: userdata prefix normalizes to nil, catch-all wins, and the rate-limit key renders as rl:host:/:ip:….
  • Simulated the new contract assertions against the pre-fix sources: they fail; against the fixed sources they pass, and every pre-existing assertion in the file still holds.
  • Not run: the repo's full bun run test (no local checkout of the monorepo); the new tests follow the file's existing source-assertion pattern and were validated as described above.

A whole-project route rule stores path_prefix NULL and serializes as
"pathPrefix":null; lua-cjson decodes JSON null to the cjson.null userdata
sentinel, not nil. Every nil/""/"/" check in rules_guard's match loop was
then false and the guard took #p on userdata, throwing inside access_by_lua
-- every request on the host 500s (whole-site outage, 0.8.0):

  rules_guard.lua:41: attempt to get length of local 'p' (a userdata value)

Normalize non-string pathPrefix to nil at the decode boundary in
rules_lib.parse; fixing only the match loop would still leave
(chosen.pathPrefix or "/") concatenating userdata into the rate-limit key,
since cjson.null is truthy. A defensive type check in the guard's match loop
keeps un-normalized entries degrading to the catch-all instead of throwing.

Adds contract-test coverage asserting both normalizations against the Lua
source (the established pattern: no Lua runtime in CI).

Fixes oblien#1015
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: edge rules: whole-project rule (pathPrefix null) crashes site — cjson.null userdata vs nil

1 participant