mirror of
https://github.com/kennethreitz/responder.git
synced 2026-07-21 18:39:29 +00:00
c839db5eac
## Summary
Follow-up hardening from the fresh audit. The client generator
interpolated OpenAPI path-parameter names directly into single-quoted
string literals in the generated Python/JavaScript/Ruby clients:
```python
path_expr += f".replace('{{{raw_name}}}', _quote({py_name}))" # Python, before
```
A path-param name containing a `'` or `\` — reachable from an
**untrusted** OpenAPI spec (the path-param regex `{([^}:]+)...}` allows
both) — breaks out of the literal and produces a client that fails to
parse. Impact is limited to broken *generated* output (not generator
RCE), but it's a real correctness bug for third-party specs.
## Fix
Emit the `{name}` placeholder through a proper per-language string
escaper:
- **Python** → `repr(...)`
- **JavaScript/TypeScript** → the existing `_js_string(...)`
- **Ruby** → a new `_ruby_string(...)` (single-quoted literal; also
applied to the Ruby base path and query-hash keys, which had the same
`repr`-for-Ruby quirk where a value with a quote could force a
double-quoted literal and re-enable `#{}` interpolation)
**PHP was already correct** and is unchanged — `_php_string` escapes `\`
and `'`, and single-quoted PHP tolerates literal newlines (so the
audit's PHP "newline" finding was a false positive).
## Tests
New `tests/test_clientgen_pathparam_escaping.py`: feeds a hostile spec
(path-param name `a'b\c\n d`) and asserts the generated **Python
compiles** (`compile()` — would `SyntaxError` before), and that the
JS/Ruby placeholders are emitted through the escaper. The existing
multi-language native syntax checks (`test_clientgen.py`) still pass.
Full suite: **759 passed**, 1 skipped (php toolchain not installed
locally; runs in CI); ruff clean; mypy clean.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
64 lines
2.2 KiB
Python
64 lines
2.2 KiB
Python
"""Path-parameter names from an (untrusted) OpenAPI spec must be escaped when
|
|
interpolated into generated client string literals.
|
|
|
|
A name containing a quote/backslash/newline previously broke out of the
|
|
single-quoted `.replace('{name}', ...)` / `.gsub(...)` literal, producing a
|
|
client that fails to parse. We feed a hostile spec and check the output is
|
|
well-formed.
|
|
"""
|
|
|
|
from responder.ext.clientgen import _js_string, _ruby_string, generate_client
|
|
|
|
# A path-parameter name loaded with characters that break naive string
|
|
# interpolation: single quote, backslash, and a newline.
|
|
NASTY = "a'b\\c\nd"
|
|
|
|
|
|
def _spec():
|
|
return {
|
|
"openapi": "3.0.2",
|
|
"info": {"title": "T", "version": "1"},
|
|
"paths": {
|
|
"/x/{" + NASTY + "}": {
|
|
"get": {
|
|
"operationId": "get_x",
|
|
"parameters": [
|
|
{
|
|
"name": NASTY,
|
|
"in": "path",
|
|
"required": True,
|
|
"schema": {"type": "string"},
|
|
}
|
|
],
|
|
"responses": {"200": {"description": "ok"}},
|
|
}
|
|
}
|
|
},
|
|
}
|
|
|
|
|
|
def test_python_client_compiles_with_hostile_path_param():
|
|
src = generate_client(_spec(), language="python")
|
|
# Would raise SyntaxError before the fix (unescaped quote/newline in literal).
|
|
compile(src, "<generated>", "exec")
|
|
|
|
|
|
def test_javascript_placeholder_is_escaped():
|
|
src = generate_client(_spec(), language="javascript")
|
|
# The placeholder is emitted through the JS string escaper, so the quote
|
|
# can't break out of the literal in the generated `.replace(...)` call.
|
|
placeholder = _js_string("{" + NASTY + "}")
|
|
assert f".replace({placeholder}, quote(" in src
|
|
|
|
|
|
def test_ruby_placeholder_is_escaped():
|
|
src = generate_client(_spec(), language="ruby")
|
|
placeholder = _ruby_string("{" + NASTY + "}")
|
|
assert f".gsub({placeholder}, quote(" in src
|
|
|
|
|
|
def test_typescript_client_still_generates():
|
|
# TS shares the JS emitter; just ensure it doesn't blow up.
|
|
src = generate_client(_spec(), language="typescript")
|
|
assert "get_x" in src or "getX" in src
|