#7373·hive

[Bug]: Social rate-limiter's configuration.json override bypasses the documented hard ceiling

Author: Prabal864Created Aug 16, 2026Updated Sep 9, 2026

Description

SocialRateLimiter in core/framework/rate_limiter.py exists specifically to stop LLM-driven browser automation (LinkedIn invites, X DMs/posts, Instagram DMs, etc.) from getting real user accounts banned. The module docstring is explicit about the guarantee:

Limits are conservative defaults with hard ceilings. Users can raise defaults via env vars ..., but never past the ceiling.

That guarantee doesn't hold for the configuration.json override path — a value written there is honored uncapped, and the enforcement function (SocialRateLimiter.check()) actually allows actions past the documented hard ceiling as a result.

Location

python
# core/framework/rate_limiter.py

def _resolve_limit(platform: str, action: str, window: str) -> int | None:
    """Read the effective limit for *window* (``hourly``, ``daily``, or ``weekly``).

    Priority: env var > configuration.json > default.  All clamped to ceiling.
    """
    ...
    env_val = os.environ.get(env_name)
    if env_val is not None:
        try:
            return _clamp(int(env_val), ceiling)   # <-- env var: clamped
        except ValueError:
            pass

    cfg = _get_config_overrides()
    cfg_key = f"{platform.lower()}.{action.lower()}.{window}"
    if cfg_key in cfg:
        try:
            return max(1, int(cfg[cfg_key]))       # <-- configuration.json: NOT clamped
        except (ValueError, TypeError):
            pass

    return default

The env-var branch calls _clamp(value, ceiling). The configuration.json branch, right below it, only enforces a floor of 1 and never calls _clamp — despite the function's own docstring saying "All clamped to ceiling."

A second, related gap: PUT /api/config/rate-limits (core/framework/server/routes_config.py::handle_update_rate_limits) is the documented way to write these overrides. Its docstring also says "Values are clamped to the hard ceiling", but the implementation only appends an advisory warning when a value exceeds the ceiling — it still persists the uncapped value to configuration.json:

python
if ceiling is not None and val > ceiling:
    warnings.append(f"... exceeds recommended max of {ceiling} ...")
cleaned[key] = val   # persisted uncapped regardless

So there's no enforcement point in this whole path — not on write, not on read.

Steps to Reproduce

Verified directly against the real module (unmodified core/framework/rate_limiter.py), not a mock:

python
import os, time
os.environ["HIVE_HOME"] = "/tmp/hivehome"
os.makedirs("/tmp/hivehome", exist_ok=True)
with open("/tmp/hivehome/configuration.json", "w") as f:
    f.write('{"rate_limits": {"linkedin.invite.daily": 999999}}')

from framework.rate_limiter import _resolve_limit, SocialRateLimiter, _LIMITS

ceiling = _LIMITS[("linkedin", "invite")]["daily_max"]   # 125
print(_resolve_limit("linkedin", "invite", "daily"))     # 999999 -- not clamped

limiter = SocialRateLimiter(db_path="/tmp/hivehome/social_rate_limits.db")
# seed 150 LinkedIn invites already sent in the last 24h (above the ceiling of 125)
con = limiter._connect()
now = time.time()
for i in range(150):
    con.execute(
        "INSERT INTO actions (platform, account_id, action_type, target_id, performed_at, session_id) VALUES (?,?,?,?,?,?)",
        ("linkedin", "acct1", "invite", None, now - 7200 - i, "seed"),
    )
con.commit(); con.close()

print(limiter.check("linkedin", "acct1", "invite"))

Actual output:

999999
{'allowed': True, 'hourly_count': 0, 'hourly_limit': 10,
 'daily_count': 150, 'daily_limit': 999999, 'weekly_count': 150, 'weekly_limit': 200}

allowed: True with 150 invites already sent today — 25 over the documented hard ceiling of 125 — and the 151st+ would be allowed too, since the effective limit is now 999999.

Impact

This is the guardrail whose entire job is preventing account-ban-triggering over-automation on social platforms. It can be silently defeated through the framework's own documented config surface (configuration.json / PUT /api/config/rate-limits), with both relevant docstrings ("never past the ceiling", "clamped to the hard ceiling") contradicted by the actual behavior. No privilege escalation or unusual access needed — just a normal config write that the API itself claims to sanitize.

Expected Behavior

_resolve_limit()'s configuration.json branch should call _clamp(value, ceiling) exactly like the env-var branch does, so the hard ceiling in _LIMITS is actually enforced regardless of where an override came from. handle_update_rate_limits() should clamp cleaned[key] to ceiling before persisting (keeping the warning as an informational note, not a silent no-op), so its own docstring becomes true.

Environment

  • OS: Windows 11 (repro is platform-independent — pure logic bug, no OS-specific behavior involved)
  • Python: 3.14.4

Additional Context

Checked core/tests/test_social_rate_limiter.py: it covers test_env_var_clamped_to_ceiling but has no equivalent test for the configuration.json path, consistent with this being an overlooked asymmetry between the two override branches rather than intentional behavior.

Happy to open a PR with the one-line fix in _resolve_limit(), the corresponding fix in handle_update_rate_limits(), and regression tests covering both paths, if this is a direction the maintainers want.