Skip to content

A JSON body which is not an object raises an uncaught AttributeError #3391

Description

@adamtheturtle

What happens

A request body which is valid JSON but not a JSON object raises an uncaught AttributeError.

validate_json only checks that the body parses:

try:
json.loads(s=request_body.decode())
exceptJSONDecodeErrorasexc:

validate_keys then assumes the result is a dict:

request_json=json.loads(s=request_text)
given_keys=set(request_json.keys())

src/mock_vws/_services_validators/key_validators.py:167.

Both backends, both endpoints which take a body:

in-process POST /targets body=[1, 2] -> UNCAUGHT AttributeError: 'list' object has no attribute 'keys'
in-process POST /targets body="hello" -> UNCAUGHT AttributeError: 'str' object has no attribute 'keys'
in-process POST /targets body=5 -> UNCAUGHT AttributeError: 'int' object has no attribute 'keys'
in-process POST /targets body=null -> UNCAUGHT AttributeError: 'NoneType' object has no attribute 'keys'
in-process POST /targets body=true -> UNCAUGHT AttributeError: 'bool' object has no attribute 'keys'
in-process PUT /targets/{id} body=[1, 2] -> UNCAUGHT AttributeError: 'list' object has no attribute 'keys'
flask POST /targets body=[1, 2] -> UNCAUGHT AttributeError: 'list' object has no attribute 'keys'
flask POST /targets body=null -> UNCAUGHT AttributeError: 'NoneType' object has no attribute 'keys'

{} is handled correctly and returns 400. PUT to a nonexistent target returns 404, because validate_target_id_exists runs first — the failure needs an existing target.

Through requests and httpx the AttributeError propagates out of the caller's request call. Through Flask and Docker it is a 500.

Several validators downstream of validate_keys would fail the same way if reached. validate_metadata_type, validate_active_flag, validate_width and the name validators all do "key" not in json.loads(...), which raises TypeError for a JSON number or boolean body.

Why it matters

json.dumps of a list is an easy thing for a client to send by accident, and a fuzzing or property-based test suite will produce these bodies immediately. The mock's job is to return whatever Vuforia returns; instead the caller gets an exception from their HTTP client.

There is a precedent in this codebase for handling exactly this input. docs/source/differences-to-vws.rst says of the Model Target Web API:

Dataset creation request bodies which are valid JSON but not JSON objects are reported as missing every required top-level field.

So the case was considered and handled for one API and not the other.

Suggested resolution

Reject non-object bodies in validate_json, which is the validator whose job this already is, so that everything downstream can rely on having a dict.

What to return needs checking against a real database. Fail with a 400 is the natural guess given that is what {} produces for POST /targets, but the Model Target API chose "missing every required field" for the same input, so Vuforia is not necessarily consistent here and a verified fake test is worth more than a guess.

tests/mock_vws/test_invalid_json.py and test_unexpected_json.py are the natural homes for the test. Neither currently sends valid JSON of a non-object type.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions