nx_rate_limit.nx
buildroot/runtime/nx_rate_limit.nx
about
nx_rate_limit.nx -- token-bucket rate limiter for brute-force resistance.
Per docs/SECURITY_POSTURE.md principle P3: every API that
verifies a signature, unseals a capability, or processes auth
must rate-limit retries from the same source. Without this,
an attacker with unlimited tries can exhaust the search space
of any K-bit secret in 2^K / rate seconds, and current commodity
hardware lets that be very fast.
Token bucket semantics:
- Each (operation, identity) pair has a bucket of `capacity`
tokens, refilled at `refill_per_sec` tokens/second.
- Each `consume(op, id)` call removes 1 token; if bucket is
empty, the call fails (returns -EAGAIN).
- State is persistent across restarts (caller stores the
serialized bucket; we provide load/save).
Lockout escalation:
- After `lockout_threshold` consecutive failures from same
identity: lockout_duration grows exponentially (1s, 1min,
1hr, 1day, permanent).
- Permanent lockout requires explicit `nx_rl_admin_unlock`
by an authority key (multi-person if K-of-N policy is
enabled; single-key fallback for early dev).
Logging: every failure emits a signed log entry through
nx_attest (when shipped) so external witnesses can observe
abuse patterns.
Pairs with: nx_atom (per-bucket CAS), nx_attest (failure logs),
nx_pcc (proof of "bucket invariant: tokens in [0, capacity]").
dependencies 1 imports · 0 importers
imports: syscalls.nx
imported by: nobody (leaf or entry point)
call flow from main pre-order; caps 40 nodes / depth 6 declared; ↻ = already shown
structs
| 48 | struct NxRlBucket |
consts
| 40 | const NX_MAGIC_1000000: i64 = 1000000 |
| 41 | const NX_MAGIC_3600: i64 = 3600 |
| 42 | const NX_MAGIC_86400: i64 = 86400 |
| 43 | const NX_MAGIC_60000: i64 = 60000 |
| 44 | const NX_MAGIC_86400000: i64 = 86400000 |
| 58 | const NX_RL_BUCKET_BYTES: i64 = 56 |
| 61 | const NX_RL_OK: i64 = 0 |
| 62 | const NX_RL_THROTTLED: i64 = 0xFFFFFFFFFFFFFFF5 // -11 / -EAGAIN |
| 63 | const NX_RL_LOCKED_OUT: i64 = 0xFFFFFFFFFFFFFFE9 // -23 / temporary lockout |
| 64 | const NX_RL_PERMANENT_LOCK: i64 = 0xFFFFFFFFFFFFFFE8 // -24 / permanent |
| 69 | const NX_RL_DEFAULT_CAPACITY: i64 = 5 |
| 70 | const NX_RL_DEFAULT_REFILL_PER_MIN: i64 = 5 |
| 71 | const NX_RL_DEFAULT_LOCKOUT_THRESHOLD: i64 = 5 |
| 72 | const NX_RL_PERMANENT_AT: i64 = 100 |
functions
| 75 | func nx_rl_now_ms() -> i64 |
| 83 | func nx_rl_new(capacity: i64, refill_per_sec: i64) -> *NxRlBucket |
| 97 | func nx_rl_refill(b: *NxRlBucket) -> i64 |
| 112 | func nx_rl_try_consume(b: *NxRlBucket) -> i64 |
| 130 | func nx_rl_backoff_ms(count: i64) -> i64 |
| 141 | func nx_rl_record(b: *NxRlBucket, success: i64) -> i64 |
| 166 | func nx_rl_admin_unlock(b: *NxRlBucket) -> i64 |
| 177 | func main() -> i64 |