PATTERN Cited by 1 source
Policy static analysis in CI¶
What it is¶
A CI-time linter that walks every authorization policy and rejects the PR if any policy matches a known-buggy pattern. Complementary to — but distinct from — runtime engine enforcement:
- Runtime enforcement inside the permissions engine catches bugs at evaluation time. Multiple-language deployments must implement it in every evaluator; build time is too late, runtime is always too early.
- Static analysis in CI catches them at PR time, with bug location + fix suggestion in the author's workflow. Costs only one implementation.
Why Figma chose build-time over runtime enforcement¶
Direct quote (sources/2026-04-21-figma-how-we-built-a-custom-permissions-dsl):
"At some point, we considered adding these types of checks to the engine itself, but opted for static analysis instead for two reasons. First, we could get away from implementing this just once, since this did not have to be cross platform and was only evaluated at build time. Second, because it was a build time check, engineers could uncover bugs faster and didn't have to wait to hit these while testing or (worse!) in prod or staging. Finally, we understood that the approach of having multiple engines is only viable because the engine is so simple. We didn't want to make changes to the engine unless we really had to."
Three explicit reasons: single implementation, fast feedback, and evaluator simplicity as an architectural invariant. Each evaluator-language surface is a maintenance cost; every check moved to CI is a check not adding evaluator complexity.
Canonical rule (Figma)¶
Null-comparison-without-guard rule. Flag any
BinaryExpressionDef of the form [field, '=', { ref: otherField }]
unless a sibling [field, '<>', null] exists under an enclosing
and. Reason: Figma's engine evaluates null = null as true,
which is almost never what the policy author intended.
Bug example:
— if both file.team_id and team.id are null, this is true,
opening access for records that shouldn't match.
Lint-compliant fix:
Figma also implements analogous guards for <>.
Why JSON-serializable DSLs make this cheap¶
A linter over a JSON-serializable
DSL is a recursive traversal over a plain data tree. No lexer,
no parser, no AST library. Figma implemented the rule above as
straightforward TypeScript iterating through ExpressionDef nodes.
This is a load-bearing property: the cost of adding a new lint rule is an hour of code, not a parser-generator ramp-up.
Placement in the development loop¶
- PR check, gating merge to mainline. Like type-check or unit-test failure.
- Fast — walks all policies, runs in seconds on a normal CI machine.
- Fixable in IDE before pushing if the check is also wired into editor integrations (not mentioned for Figma's case but a natural extension).
Relationship to concepts/automated-reasoning¶
A lightweight cousin of full automated-reasoning analysis (like AWS's decade of work on IAM / Cedar surfacing via IAM Access Analyzer / AVP policy analysis). Static analysis in CI:
- Cheaper — pattern-matching over JSON is O(policies × tree size).
- Less expressive — can't prove arbitrary equivalence, reachability, redundancy; just catches enumerable bug patterns.
- Works today with no solver — no SAT/SMT backend required.
The natural upgrade path is adding an SMT-backed rule (equivalence / overlap / always-denies queries); Cedar customers get the higher tier via AVP.
Caveats¶
- Covers known bug patterns only — novel bug classes slip through until a rule is written.
- False positives require care — over-restrictive rules annoy authors and erode trust in the linter; Figma's null-guard rule is narrow enough to almost always indicate a real bug.
- Doesn't catch runtime-only issues — things depending on actual data values won't be statically detectable.
Seen in¶
- sources/2026-04-21-figma-how-we-built-a-custom-permissions-dsl — canonical instance with explicit build-time-vs-runtime reasoning, the null-comparison rule as the concrete example, evaluator-simplicity-as-invariant as the architectural justification.