Two opt-in features trust the client more than they should. Neither is used anywhere in the local fleet today, but both are one decorator away.
handle_binary_req unpickles the request body (_get_inputs_from_request, pickle.loads(request.body.read())). Unpickling data from a client executes whatever the client packed into it, so any route using the binary input mapper runs attacker-chosen code. The mirror image exists on the client side: send_binary_resp pickles responses and http2py unpickles them, so a spoofed or compromised server runs code on the client.
Plan: make the deserializer a keyword-only seam, handle_binary_req(func, *, loads=safe_pickle_loads), where the default is a restricted pickle.Unpickler whose find_class only admits plain builtins (containers, numbers, str/bytes, set/frozenset/complex). Plain-data payloads keep working unchanged; arbitrary objects need an explicit loads=pickle.loads (trusted peers only), documented as such. Same seam on the http2py side.
attr_names='*' on a class/instance endpoint exposes every attribute, including _private methods and dunders (__dict__ dumps instance state, __init__ re-initialises the object with caller-chosen arguments). Plan: with '*', refuse names starting with _ and names not defined on the class; explicit lists are unaffected. Also bound obj_store (it grows by one entry per create call, forever).
Not fixed right away because the test suite does not currently collect (py2http/tests/simple_run_process_test.py fails with a TypeError from http2py.mk_request_func_from_openapi_spec's changed signature), so CI cannot gate a change here yet; the package also fails to import on Python 3.13 (keyword-form TypedDict in service.py), which makes import qh crash on 3.13 when py2http is installed. Fixing collection first, then the two items above, is the order.
🤖 Generated with Claude Code
Two opt-in features trust the client more than they should. Neither is used anywhere in the local fleet today, but both are one decorator away.
handle_binary_requnpickles the request body (_get_inputs_from_request,pickle.loads(request.body.read())). Unpickling data from a client executes whatever the client packed into it, so any route using the binary input mapper runs attacker-chosen code. The mirror image exists on the client side:send_binary_resppickles responses andhttp2pyunpickles them, so a spoofed or compromised server runs code on the client.Plan: make the deserializer a keyword-only seam,
handle_binary_req(func, *, loads=safe_pickle_loads), where the default is a restrictedpickle.Unpicklerwhosefind_classonly admits plain builtins (containers, numbers, str/bytes, set/frozenset/complex). Plain-data payloads keep working unchanged; arbitrary objects need an explicitloads=pickle.loads(trusted peers only), documented as such. Same seam on thehttp2pyside.attr_names='*'on a class/instance endpoint exposes every attribute, including_privatemethods and dunders (__dict__dumps instance state,__init__re-initialises the object with caller-chosen arguments). Plan: with'*', refuse names starting with_and names not defined on the class; explicit lists are unaffected. Also boundobj_store(it grows by one entry per create call, forever).Not fixed right away because the test suite does not currently collect (
py2http/tests/simple_run_process_test.pyfails with aTypeErrorfromhttp2py.mk_request_func_from_openapi_spec's changed signature), so CI cannot gate a change here yet; the package also fails to import on Python 3.13 (keyword-formTypedDictinservice.py), which makesimport qhcrash on 3.13 when py2http is installed. Fixing collection first, then the two items above, is the order.🤖 Generated with Claude Code