POST /api/v1/guardrail-credentials and its PATCH accept arbitrary constructor arguments for the chosen guardrail, validated only against the catalog's parameter names, types and flags. Several of those arguments are URLs that the gateway will eventually dial: lakera_guard.endpoint, watsonx_guardian.url, the Azure endpoints, and whatever a guardrail added upstream brings with it.
The sibling store checks its own. api/routes/search_tools.py:202 runs validate_url(str(api_base)) from services/tool_settings_service.py before storing a backend's base URL, which is what keeps a stored tool from pointing the gateway at a link-local or loopback address. The guardrail store has no equivalent, so a stored endpoint is whatever the operator typed.
Why this is not urgent, and why it should not be left
Nothing on the request path reads guardrail_credentials yet, so today the value is written and never dialed. The runner that reads these rows is what turns it into a reachable SSRF sink, and the check is much easier to add before that lands than after.
Why this is filed rather than fixed in #1211
#1209 scoped that PR to the table and the operator API, deliberately without a runner or a test endpoint. Adding URL validation there would mean deciding which catalog arguments count as URLs, which is a catalog question (upstream types them as plain strings, not as a URL kind) rather than a storage one. Surfaced during review of #1211: #1211
Scope
- Decide how a URL-valued guardrail argument is recognized: a
kind on GuardrailParameterSpec in services/guardrail_catalog.py is the honest answer, since the store's whole design is that every rule is read off the catalog rather than written into the store.
- Run
validate_url over those arguments in services/guardrail_credential_service.py on create and on update, refusing with the existing 400 family in exceptions/guardrail_credentials.py.
- Cover it beside the other refusals in
tests/integration/test_guardrail_credentials_api.py.
Related
POST /api/v1/guardrail-credentialsand its PATCH accept arbitrary constructor arguments for the chosen guardrail, validated only against the catalog's parameter names, types and flags. Several of those arguments are URLs that the gateway will eventually dial:lakera_guard.endpoint,watsonx_guardian.url, the Azure endpoints, and whatever a guardrail added upstream brings with it.The sibling store checks its own.
api/routes/search_tools.py:202runsvalidate_url(str(api_base))fromservices/tool_settings_service.pybefore storing a backend's base URL, which is what keeps a stored tool from pointing the gateway at a link-local or loopback address. The guardrail store has no equivalent, so a storedendpointis whatever the operator typed.Why this is not urgent, and why it should not be left
Nothing on the request path reads
guardrail_credentialsyet, so today the value is written and never dialed. The runner that reads these rows is what turns it into a reachable SSRF sink, and the check is much easier to add before that lands than after.Why this is filed rather than fixed in #1211
#1209 scoped that PR to the table and the operator API, deliberately without a runner or a test endpoint. Adding URL validation there would mean deciding which catalog arguments count as URLs, which is a catalog question (upstream types them as plain strings, not as a URL kind) rather than a storage one. Surfaced during review of #1211: #1211
Scope
kindonGuardrailParameterSpecinservices/guardrail_catalog.pyis the honest answer, since the store's whole design is that every rule is read off the catalog rather than written into the store.validate_urlover those arguments inservices/guardrail_credential_service.pyon create and on update, refusing with the existing 400 family inexceptions/guardrail_credentials.py.tests/integration/test_guardrail_credentials_api.py.Related
redact_secret_like_valuesmasks only top-level keys, whichvalidate_kwargsinherits.