Skip to content
Backend Infrastructure7 min

When “=0” means “off”: config drift and the unenforced legal limit

A configurable rolling-period limit falls back to a permissive default when the value is zero or malformed. The control still appears to run — it just stops enforcing anything.

Failure modeA compliance limit reads its window from configuration with a fallback that treats an invalid or zero value as “no filter.” A single stray environment variable disables a legally mandated control while the code path still executes and reports success.

Commercial risk

In a regulated sale — ammunition, pharmaceuticals, alcohol, credit — an unenforced statutory limit is not a bug ticket. It is criminal liability, licence revocation, and personal exposure for the manager who signed off, and it leaves no trace that the check ever failed.

The sales are all recorded. The compliance decisions are not. So when the regulator audits, the system can show every transaction but cannot prove a single limit check ran correctly — the worst possible position: demonstrable activity, undemonstrable control.

Regulated software encodes law as logic, and law has hard numbers: a rolling 365-day window, a per-period quantity cap, a cooling-off interval. The instinct to make those numbers configurable is reasonable — different jurisdictions, different products, a need to tune without redeploying. The failure mode is in how the fallback is written when the configured value is missing or nonsensical.

We have seen a statutory quantity limit silently switched off by a single `.env` line left over from testing. The mechanism is worth dissecting because it is a pattern, not a one-off, and it appears anywhere configuration governs a control that must not be permissive by default.

1. The fallback that fails open

The limit is a rolling window — how many units a customer may buy within N days. The window length is read from config with a default, then the query only applies a date filter when the window is greater than zero. Read those two decisions together and the trap is visible: set the window to `0` and the date filter is skipped entirely, so the query counts only today's purchases instead of the whole legal period. The cap is now trivially evaded by buying the maximum every day.

The value reaches zero more easily than you'd think. Someone sets `ROLLING_PERIOD_DAYS=0` to disable the window during a test and forgets to revert. Or the variable is set to an empty string, or `"none"`, or `null` — all of which `(int)` casts to `0`. There is no schema validation on the config, so any of these sails through and the control fails open, in the most permissive direction, silently.

protected function rollingPeriodDays(): int
{
    $days = (int) config('ammunition.rolling_period_days', 365);
    return $days >= 1 ? $days : 365; // <-- looks safe, but see the caller
}

public function purchasesInPeriod(Customer $c, Caliber $cal): int
{
    $q = AmmunitionSale::where('customer_id', $c->id)
        ->where('caliber_id', $cal->id);

    // The trap: the date filter is CONDITIONAL on the window being > 0.
    if ($this->rollingPeriodDays() > 0) {
        $q->whereDate('sale_date', '>=', now()->subDays($this->rollingPeriodDays()));
    }
    return (int) $q->sum('quantity');
}

2. Fail closed, and validate config at boot

A control that enforces the law must fail closed: if it cannot determine the window, it must refuse the sale, not permit it. That is the inverse of how most defaults are written, and it is the entire fix. An invalid configuration should be a fatal, boot-time error — the application should not start with a compliance limit it cannot enforce, any more than it should start with no database credentials.

Validate the config where it is loaded, not where it is used. A configuration schema that asserts `rolling_period_days` is an integer of at least 1 turns a silent runtime bypass into a loud deployment failure — which is exactly the trade you want, because a deploy that won't start is a problem you notice, and a limit that quietly stops enforcing is a problem your regulator notices for you.

// Boot-time validation — the app refuses to start misconfigured.
public function boot(): void
{
    $days = config('ammunition.rolling_period_days');
    if (!is_int($days) || $days < 1) {
        throw new \RuntimeException(
            'ammunition.rolling_period_days must be an integer >= 1; '
            . 'refusing to start with an unenforceable legal limit.'
        );
    }
}

// Enforcement always applies the window — no conditional skip.
$q->whereDate('sale_date', '>=', now()->subDays($days));

3. Log the decision, not just the transaction

The second, quieter failure is that the system records the sale but not the compliance decision that permitted it. When the regulator asks "prove this limit was enforced," the answer must be a durable record: for this customer, at this time, the window was N days, the prior total was X, the cap was Y, the check passed. Without that, you can show what was sold but never that it was allowed.

Write an immutable compliance-decision log alongside the sale, in the same transaction, so the two cannot diverge. It is cheap to write and it is the only artefact that converts "we believe the control worked" into "here is the evidence it worked on every transaction" — which is the difference between an audit you pass and one you cannot answer.

A legal control implemented as a configurable limit inherits the safety of its fallback. If the fallback fails open — if a zero or a typo turns enforcement off while the code path still runs green — then the control is decorative, and the first party to discover that will be a regulator, not your test suite.

Fail closed on invalid configuration, validate the config at boot so a bad value stops the deploy, and log every compliance decision as durably as the transaction it governs. In regulated software the control is not the feature — the provable enforcement of the control is the feature.

Need this level of structural integrity engineered for your system?

We audit and re-architect systems where a single one of these failure modes costs real money. Fixed scope, fixed price, defined delivery date.

Request a Fixed-Scope Architecture Blueprint