Keep plan limits and pricing rules out of your public bundle

Plan limits, quota thresholds and feature-flag rules in client JavaScript read as a specification of your commercial model. How to stop them being readable, and what stays observable anyway.

Plan limits, quota thresholds and feature-flag rules usually end up in the client bundle, where a competitor or a customer can read them straight out of the file.

Your pricing table reads as a spec in plain JavaScript

A pricing table, a quota map and a list of feature flags read as documentation of your commercial model when they sit in plain JavaScript. Anyone can open the bundle and see where your tiers break, what triggers an upsell, and which flags gate which features.

One pass of a coding agent over that bundle turns loose values into an organised spec. That spec can be copied, argued with in a support ticket, or used to route around a limit before you ever notice.

Encoded values, rewritten structure

String literals get encoded at any positive complexity target, so the tables and thresholds are no longer readable text in the file you serve. The structure around them is rewritten as well. A reader who works through the output recovers an equivalent computation and has to reconstruct from it what your rules meant.

Rotation is what keeps a recovered copy from staying useful. A new seed on every release means whatever someone reconstructed last time describes a build you no longer serve.

Raise the target on the module that holds your rules

Raise the whole bundle a step on the preset ladder, or set the target directly when a preset's granularity does not fit:

{
  "preset": "medium",
  "complexity": 40
}

To mark just the module holding your rules, use a directive. Directives take effect on Pro builds.

/* @afterpack preset=extreme */
export function resolveQuota(plan: Plan, usage: Usage): number {
  return TIER_LIMITS[plan.tier] - usage.consumed;
}
/* @afterpack end */

strings.leaks.max turns "no readable literals survived" into a build gate instead of something you check by eye. A failure names DIAG_SURVIVING_READABLE_LITERAL and points at the literal that got through.

Fail the build when a literal survives

Build with --strings.leaks.max=0 in CI and treat a nonzero exit as a failed build. Then open the Protection Map and check that the rules module is coloured at the target you set for it.

What stays observable whatever you ship

Rotating your build makes a scraped copy of your rules go stale on the next release, and that is the part worth having. Values that only exist to be read inside the bundle are in scope here; a price your UI renders or a limit your API returns is observable regardless of what the bundle looks like, so treat anything the page displays as public and enforce every limit on the server.

Next

  • Threat model: what a static reader can and cannot recover.
  • Presets: the full ladder and each level's complexity target.
  • Protection Map: confirm the rules module got the treatment you configured.