fix: validate OTC order creation payloads#5559
Conversation
LubuSeb
left a comment
There was a problem hiding this comment.
Reviewed head 4f590c0359755ecd7d6382118c85d36a14267a66.
This is a focused fix for the order-create validation gap from #5525. The route now distinguishes an absent/malformed JSON body from non-object JSON, rejects arrays/scalars before the first data.get(...), and parses ttl_seconds through a helper that rejects strings, floats, bools, and explicit nulls before order creation. Missing ttl_seconds still defaults to ORDER_TTL_DEFAULT, and accepted integer TTLs keep the existing min/max clamp.
Validation performed on the PR checkout:
git diff --check origin/main...HEAD -- otc-bridge/otc_bridge.py tests/test_otc_bridge_query_validation.py
python -B -m py_compile otc-bridge/otc_bridge.py tests/test_otc_bridge_query_validation.py
.\.venv\Scripts\python.exe -B -m pytest -q tests/test_otc_bridge_query_validation.py --tb=short --noconftest
# 15 passed
I also checked the changed route manually against the diff: the new object guard runs before side, pair, wallet, and amount validation, and the new tests cover the two reported 500 paths plus a valid integer TTL success path. No blocker found.
strongkeep-debug
left a comment
There was a problem hiding this comment.
Approved. I re-checked current head 4f590c0 specifically against the #5525 OTC order creation regressions.
What I verified:
- Non-object JSON for
POST /api/ordersnow returns400 {"error":"JSON object required"}before the route tries to read order fields. ttl_secondsnow rejects non-integer JSON values, including strings, floats, and booleans, instead of coercing them throughint(...).- Integer TTLs still work: values below 3600 clamp to 1 hour,
7200yields 2 hours, and omittedttl_secondskeeps the configured default. - The change is scoped to
create_order()plus focused regressions intests/test_otc_bridge_query_validation.py.
Local validation:
git diff --check origin/main...HEAD -- otc-bridge/otc_bridge.py tests/test_otc_bridge_query_validation.py
# passed
python -B -m py_compile otc-bridge/otc_bridge.py tests/test_otc_bridge_query_validation.py
# passed
python -m pytest -q tests/test_otc_bridge_query_validation.py
# 15 passed
I also ran a direct Flask client probe for the affected endpoint covering non-object JSON, string/float/bool TTL rejection, integer TTL clamping, and default TTL preservation.
TJCurnutte
left a comment
There was a problem hiding this comment.
Approved. I focused this on the POST /api/orders validation path from #5525 / the recent OTC TTL regressions.
Validation performed on head 4f590c0359755ecd7d6382118c85d36a14267a66:
git diff --check origin/main...HEAD -- otc-bridge/otc_bridge.py tests/test_otc_bridge_query_validation.py
python3 -B -m py_compile otc-bridge/otc_bridge.py tests/test_otc_bridge_query_validation.py
uv run --no-project --with pytest --with flask --with flask-cors --with requests \
python -B -m pytest -q -o addopts='' --noconftest tests/test_otc_bridge_query_validation.pyThe focused pytest run passed with 15 passed in 1.45s.
I also ran a Flask test-client probe against a temp DB after init_db(). That confirmed:
- JSON array body now returns
400with{"error":"JSON object required"}. ttl_secondsvalues of'abc',1.5,true, andnullall return400with{"error":"ttl_seconds must be an integer"}.- missing
ttl_secondsstill creates an order with the default168.0hour expiry. - integer
7200still creates an order with2.0hours. - integer clamping remains intact:
0clamps to1.0hour and an oversized integer clamps to720.0hours.
The important bit is that order_ttl_seconds() now rejects bools explicitly before Python's bool-is-an-int edge case, rejects non-integer JSON numbers/strings/null before order creation, and the route rejects non-object JSON before .get() calls. That closes the coercion/500 path without weakening the existing min/max TTL behavior.
2balmprune
left a comment
There was a problem hiding this comment.
Reviewed head 4f590c0359755ecd7d6382118c85d36a14267a66 against the #5525 OTC order-creation bug report.
This fixes the two reported 500 paths: POST /api/orders now rejects non-object JSON before any .get(...) calls, and ttl_seconds is validated as an actual JSON integer before order creation. I also checked the Python bool edge case: booleans are rejected explicitly rather than being accepted as integers. Missing ttl_seconds still falls back to the configured default, and valid integer TTLs keep the existing min/max clamp behavior.
Validation performed:
git diff --check origin/main...HEAD -- otc-bridge/otc_bridge.py tests/test_otc_bridge_query_validation.pypython -B -m py_compile otc-bridge/otc_bridge.py tests/test_otc_bridge_query_validation.pypython -B -m pytest -q tests/test_otc_bridge_query_validation.py-> 15 passed- Direct Flask client probe for JSON array body, string
ttl_seconds, and integerttl_seconds=7200
No blocker found.
jaxint
left a comment
There was a problem hiding this comment.
LGTM! Great work on this PR. 🚀
iamdinhthuan
left a comment
There was a problem hiding this comment.
Reviewed current head 4f590c0359755ecd7d6382118c85d36a14267a66 for the #5525 OTC order creation validation fix. Approving.
The patch now rejects non-object JSON before the /api/orders handler reaches any .get(...) calls, while still returning JSON body required for a missing/malformed body. The new order_ttl_seconds() helper also closes the Python coercion edge cases: strings, floats, booleans, and explicit null values are rejected before order creation, while missing ttl_seconds keeps the configured 7-day default and valid integers still use the existing min/max clamp.
Validation run on this head:
git diff --check origin/main...HEAD -- otc-bridge/otc_bridge.py tests/test_otc_bridge_query_validation.py
python3 -B -m py_compile otc-bridge/otc_bridge.py tests/test_otc_bridge_query_validation.py
python3 tools/bcos_spdx_check.py --base-ref origin/main
# BCOS SPDX check: OK
python3 -B -m pytest -q tests/test_otc_bridge_query_validation.py --noconftest
# 15 passed
Direct Flask probe with a temp DB:
array_json -> 400 {'error': 'JSON object required'}
ttl_string -> 400 {'error': 'ttl_seconds must be an integer'}
ttl_float -> 400 {'error': 'ttl_seconds must be an integer'}
ttl_bool -> 400 {'error': 'ttl_seconds must be an integer'}
ttl_null -> 400 {'error': 'ttl_seconds must be an integer'}
ttl_missing -> 201, expires_in_hours=168.0
ttl_zero_clamped -> 201, expires_in_hours=1.0
ttl_7200 -> 201, expires_in_hours=2.0
No private keys, tokens, wallet secrets, production services, live wallet actions, withdrawals, or fund movement were used.
|
Closing as duplicate of #5526 (canonical first-poster on this issue per Codex forensic audit 2026-05-18). Both PRs target the same root cause: OTC order ttl_seconds + JSON-object validation. Bounty credit goes to #5526. Thanks for the contribution — for next time, please check open PRs against the same issue before filing. |
Summary
POST /api/orderswith a stable JSON 400ttl_secondsas an actual JSON integer before order creationttl_secondsdefaulting to the configured OTC order TTL and preserve min/max clampingFixes #5525.
Validation
python -B -m pytest -q tests/test_otc_bridge_query_validation.pypython -B -m py_compile otc-bridge/otc_bridge.py tests/test_otc_bridge_query_validation.pygit diff --check -- otc-bridge/otc_bridge.py tests/test_otc_bridge_query_validation.pyBounty
RTC wallet:
RTC02811ff5e2bb4bb4b95eee44c5429cd9525496e7