chore(plugins): align rate-limiter config with gateway convention#4582
chore(plugins): align rate-limiter config with gateway convention#4582gandhipratik203 wants to merge 3 commits intomainfrom
Conversation
|
Me review your PR! Changes GOOD idea but have few problems that MUST fix before merge. 🔴 CRITICAL Issues (Must Fix Now!)1. Bare Exception Swallow All Errors 🚨
2. No URL Validation 🚨
3. Breaking Change: redis_fallback Removal Not Justified
🟡 Should Fix (Recommended)
Read full report for:
✅ What Need Before MergeMUST FIX (Blocking):
SHOULD FIX: 🎯 VerdictEstimated effort: 4-6 hours to fix all critical issues |
msureshkumar88
left a comment
There was a problem hiding this comment.
As mentioned in comments
Closes #4581. Sources the rate-limiter plugin's redis_url from the gateway's REDIS_URL env var via the plugin loader's existing Jinja substitution, instead of hardcoding it as a literal string in plugins/config.yaml. Why --- The gateway already sources its own Redis URL from REDIS_URL via pydantic Settings (mcpgateway/config.py). Operators set REDIS_URL once and the gateway picks it up. The rate-limiter plugin previously ignored that env var and forced a second, separate edit if operators wanted to point the limiter at a different Redis (different host, different DB number, different deployment topology, etc.). One env var mapping to two configuration surfaces is mildly surprising and creates drift over time. Backwards-compatible: docker-compose.yml exports REDIS_URL=redis://redis:6379/0 by default, so the resolved value matches the prior literal in local dev. Operators who currently set REDIS_URL in their deployment pick up the new convention transparently. Test ---- Adds tests/integration/test_rate_limiter_redis_url_from_yaml.py — a small smoke test that loads plugins/config.yaml, asserts the redis_url resolves to a non-empty string with no leaked Jinja placeholder, and PINGs a live Redis at the resolved URL. Skips cleanly when Redis isn't reachable, so the suite stays green on runners without Redis. Out of scope * URL log sanitization or other observability changes. * Plugin-loader changes — Jinja substitution already supported. * Other plugin yaml entries — this PR is scoped to the rate-limiter block only. Signed-off-by: Pratik Gandhi <gandhipratik203@gmail.com>
…ng to survive yaml round-trip
The previous form ``"{{ env.REDIS_URL }}"`` rendered to an empty string
in any environment that did not set the ``REDIS_URL`` env var, including
the existing ``test_manager_initializes_packaged_plugins_from_shipped_configs``
unit test on CI. The empty URL then hit the redis crate as a parse
failure (``InvalidClientConfig: Redis URL did not parse``).
Fix uses Jinja's ``default()`` filter to fall back to the previous
literal default when the env var is unset. Operators in production
who set ``REDIS_URL`` get the override; CI / fresh-laptop environments
get the same value as before this PR.
Quoting note: YAML outer-single + Jinja inner-double, not the other
way around. ``yaml.safe_dump`` (used by the existing test to filter
plugins and write a tmp config) doubles the inner ``'`` when it
round-trips a double-quoted YAML scalar, producing
``default(''redis://...'')`` which Jinja parses as empty-string + bare
identifier ``redis`` (the source of the CI failure). Inverting the
quotes preserves the Jinja expression byte-for-byte across
``yaml.safe_load`` -> ``yaml.safe_dump`` and is the standard
recommendation for embedding Jinja in YAML scalars.
Signed-off-by: Pratik Gandhi <gandhipratik203@gmail.com>
a1292e1 to
f34d7a1
Compare
|
Thanks for the review. Scope-narrowing this PR to just the That moots 1 and 3 of your review (both lived in the code that's been moved out). 2 (SSRF on 4–6: not applicable in the narrowed scope. A one-shot Redis |
msureshkumar88
left a comment
There was a problem hiding this comment.
Good direction — the Jinja | default(...) pattern matches what the rest of config.yaml already does for IBM_WATSON_*, and the outer-single/inner-double quoting to survive yaml.safe_dump round-trips is a nice touch. A couple of things worth addressing before this lands:
Blocking
Relative path in the integration test
cfg = ConfigLoader.load_config("plugins/config.yaml") # test line 56This resolves from cwd, so the test silently breaks whenever pytest is invoked from any directory other than the repo root (e.g. cd tests && pytest). Suggest anchoring to the file's own location:
import pathlib
_REPO_ROOT = pathlib.Path(__file__).resolve().parents[3]
cfg = ConfigLoader.load_config(str(_REPO_ROOT / "plugins" / "config.yaml"))Security Scan CI failure
25/28 checks pass but the Security Scan job is failing. Worth checking whether Bandit is flagging something in the new test file (e.g. the bare except Exception in _redis_reachable, or the socket usage) and either suppressing with a # nosec + justification comment or adjusting the code. Happy to take a look at the job log if you can share it.
Suggestions
No test for the default fallback path
The test always injects REDIS_URL via monkeypatch.setenv, so the | default("redis://redis:6379/0") branch is never exercised. A small unit test (no live Redis needed) would pin that:
def test_rate_limiter_redis_url_uses_default_when_env_unset(monkeypatch, tmp_path):
monkeypatch.delenv("REDIS_URL", raising=False)
cfg = ConfigLoader.load_config("plugins/config.yaml")
rl = next(p for p in cfg.plugins if p.name == "RateLimiterPlugin")
assert rl.config.get("redis_url") == "redis://redis:6379/0"(Same relative-path caveat applies here.)
Minor
The comment in config.yaml mentions WASM sandboxes — a bit surprising in a Redis URL context, since WASM plugins typically can't open TCP sockets. Trimming to just "separate container" would avoid confusion:
# Sourced from the gateway's REDIS_URL env; when the plugin runs in a
# separate container, ensure that host is reachable from within it.One pre-existing issue worth tracking separately (not blocking this PR): SandboxedEnvironment in config.py:80 is constructed with autoescape=True, which HTML-encodes &, <, >, ". That's correct for HTML templates but wrong for YAML — if REDIS_URL ever includes a percent-encoded password or similar, the rendered value would be silently corrupted. Probably worth a follow-up issue since it affects all Jinja-templated values in the config, not just this one.
Address review feedback on #4582: - ``test_rate_limiter_redis_url_from_yaml.py``: anchor ``plugins/config.yaml`` to the test file's location via ``pathlib.Path(__file__).resolve().parents[2]`` so the test works whether pytest is invoked from the repo root or from any subdirectory (``cd tests && pytest`` was silently broken). - Add ``test_rate_limiter_redis_url_uses_default_when_env_unset``: the existing test always set ``REDIS_URL`` via ``monkeypatch.setenv``, so the ``| default("redis://redis:6379/0")`` branch was untested. The new test deletes the env var and asserts the default resolves — runs without a Redis service. - ``plugins/config.yaml``: trim the ``redis_url`` comment to drop the WASM-sandbox reference (WASM plugins can't open TCP sockets, so ``REDIS_URL`` doesn't apply to them). Keep the separate-container caveat which is the realistic case. Signed-off-by: Pratik Gandhi <gandhipratik203@gmail.com>
Mirrors the same fix applied to PR #4582 so that when this branch is eventually rebased onto main as part of #4603, the path-anchoring fix is preserved in the renamed test file. - ``test_plugins_config_yaml_validation.py``: anchor ``plugins/config.yaml`` to the test file's location via ``pathlib.Path(__file__).resolve().parents[2]`` so the tests work whether pytest is invoked from the repo root or a subdirectory. Applied to both the redis-url-resolves test and the schema-validation test (the latter also hardcoded the relative path). - Add ``test_rate_limiter_redis_url_uses_default_when_env_unset``: the existing redis-url test always set ``REDIS_URL`` via ``monkeypatch.setenv``, so the ``| default("redis://redis:6379/0")`` branch was untested. The new test deletes the env var and asserts the default resolves; runs without a Redis service. - ``plugins/config.yaml``: trim the ``redis_url`` comment to drop the WASM-sandbox reference. Keep the separate-container caveat. Signed-off-by: Pratik Gandhi <gandhipratik203@gmail.com>
|
Thanks — this was a great review. Addressed all four actionable items in Relative path — anchored via Security Scan failure — the job log says Default fallback test — added WASM comment — trimmed to the separate-container case as suggested. You're right that WASM plugins can't open TCP sockets, so autoescape pre-existing bug — filed as #4605. Sharp catch; agree it's broader than this PR (affects every Jinja-templated value, not just |
Summary
Align the rate-limiter plugin's
redis_urlinplugins/config.yamlwith the gateway's existing convention of sourcing Redis URLs from theREDIS_URLenv via the plugin loader's Jinja substitution. Operators setREDIS_URLonce and the rate-limiter picks it up — no more hand-editing the yaml or templating it in deployment tooling.Companion to #4581.
Changes
plugins/config.yaml:redis_url: "redis://redis:6379/0"→redis_url: '{{ env.REDIS_URL | default("redis://redis:6379/0") }}'plus a short comment about the env-source. Outer single / inner double quotes surviveyaml.safe_dumpround-trips cleanly.tests/integration/test_rate_limiter_redis_url_from_yaml.py: new test — env-var substitution flows through the plugin loader cleanly and the resolved URL reaches a live Redis (PING → PONG). Skips when Redis isn't reachable.Test plan
pytest tests/integration/test_rate_limiter_redis_url_from_yaml.pygreen locally with Redis runningThe schema-regression test and
redis_fallbackcleanup originally bundled here are now tracked as #4603.