Parse a bounded invoice, not an arbitrary object graph
Python 3.13+ with Expat 2.6.0+. Standard library only. No downloads or server.

Reference solution: not included here, so the exercise does not hand you the answer.
Attempt the repair first. Then download bounded-data-parsing-solution.zip from the project card
on https://softwaresecurity.com/labs/#project-bounded-data-parsing and extract it into this folder;
it adds reference.py without changing your files, and the reference commands below
need it.

Goal
Accept a small JSON/XML invoice vocabulary while rejecting ambiguous structure,
coercion, DTDs and excessive input. The project performs real JSON and XML parsing;
it does not execute deserialized Python objects. No pickle/eval loader is included.

Prerequisites and time
Read Python dictionaries, lists and exceptions. Allow 45–60 minutes. Run:
   python3 --version
   python3 -c "from xml.parsers import expat; print(expat.EXPAT_VERSION)"
A Python installation can link a different Expat version; check the library,
not only the Python version. The reference rejects Expat older than 2.6.0.

Input contract
Both formats describe exactly currency (USD or EUR) and 1..100 items. Each item
has exactly cents: a non-negative integer <=10,000,000. Total <=100,000,000.
Maximum encoded document size is 8,192 bytes. JSON nesting is limited to four
containers, duplicate members and non-finite constants are rejected. Booleans,
floats, strings and negative numbers are not integer cents. XML contains only an
invoice root with currency attribute and item children with cents attributes.
XML cents use 1..8 ASCII decimal digits. XML 1.0/UTF-8, whitespace and comments
are accepted; non-whitespace text, extra attributes/elements, processing
instructions, DTDs and entity declarations are outside this vocabulary.

Valid JSON:
   {"currency":"USD","items":[{"cents":150},{"cents":225}]}
Valid XML:
   <invoice currency="USD"><item cents="150"/><item cents="225"/></invoice>
Expected normalized result:
   {"currency": "USD", "items": [150, 225], "total": 375}

1. Observe a semantic failure
   python3 -m unittest -v test_parsing.py
   Expected starter: 5 pass / 19 fail. Read exercise.py: it parses syntactically
   valid inputs, then converts cents with int(). That turns true into 1 and 1.5
   into 1. It silently ignores extra fields and lets a repeated currency replace
   the earlier one. These are contract violations without arbitrary execution.
   The tiny XML entity case expands only the literal USD; it is intentionally not
   a resource exhaustion payload or a file-reading/network experiment.

2. Draw the boundaries
   encoded bytes -> size/encoding -> syntax/parser capabilities -> exact schema
                 -> numeric/business bounds -> normalized invoice
   A schema check after parsing does not disable the parser's capabilities. A
   byte limit alone does not define valid fields. Rejecting an unknown item field
   also does not establish that the caller is authorized to submit this invoice.
   Keep those questions separate and assign tests to each boundary.

3. Repair exercise.py
   Validate byte size before decoding/parsing. For JSON, use object_pairs_hook
   so duplicate members are visible and parse_constant to reject NaN/Infinity.
   Bound structural depth before constructing the tree. JSON strings and escaped
   quotes must not be counted as nesting; reference.py demonstrates this scanner.
   Require exact dictionaries and keys, exact integer type (not isinstance alone,
   since bool subclasses int), item count, per-item range and aggregate total.
   For XML, either adopt a maintained hardened parser configured for this
   vocabulary or use the reference's narrowly scoped Expat event handlers.
   The reference rejects DOCTYPE at the parser event, disables parameter entity
   parsing, and rejects external-entity callbacks. It constructs only the allowed
   invoice structure while processing events, rather than building an arbitrary
   XML tree and hoping disallowed nodes are never used. A substring search for
   '<!DOCTYPE' is not an XML parser or a complete security boundary.

4. Run the checks and compare
   python3 -m unittest -v test_parsing.py
   EXERCISE_MODULE=reference python3 -m unittest -v test_parsing.py
   PowerShell: $env:EXERCISE_MODULE='reference'; python3 -m unittest -v test_parsing.py
   Clear that variable before testing your repair.
   Expected reference: 24 tests pass. Keep both ordinary formats, zero cents and
   the 100-item boundary working. Blanket rejection cannot meet this contract.

Worked reasoning
Duplicate currency keys are ambiguous because another component may choose a
different occurrence; a duplicate-aware parser rejects the document before a
single meaning is assumed. Boolean true converts to 1 in Python, so exact type
checking precedes arithmetic. DTD/entity syntax is rejected independently of
whether this particular entity is harmless. Eleven maximum-size line amounts
exceed the aggregate cap despite individually valid items. Finally, an unknown
approved=true field cannot become an implicit server-side approval decision.

Completion rubric
All 24 tests pass. Explain the order of checks and add one positive boundary and
one rejection of your own. Keep a table of bytes, nesting, counts, numeric bounds
and allowed parser capabilities. Identify the authorization and persistence
transaction that would follow parsing in a real application; this project does
neither and does not claim that a well-formed invoice is an authorized invoice.

Troubleshooting and limits
Unsupported Expat: update the interpreter/library rather than deleting the check.
Do not enable entity resolution to accommodate an unexpected document; revise
and review the vocabulary if a legitimate requirement truly needs it. Large
enterprise XML, schemas, signatures, XPath, namespaces, compressed requests and
streaming network uploads are deliberately excluded. The byte bound applies to
decoded request payload bytes; an HTTP service must also bound transport and
compression expansion before calling this function. Processing time and process
memory still need service-level budgets. This narrow example is not a general
replacement for maintained XML hardening libraries or a complete schema engine.
The tests create no files, connections or durable state; no cleanup is needed.

Primary references
https://docs.python.org/3/library/json.html
https://docs.python.org/3/library/xml.html
https://docs.python.org/3/library/pyexpat.html
https://docs.python.org/3/library/pickle.html

Static analysis (optional)
semgrep/xml-json-parsing.yaml holds Semgrep rules for the defect in this
project, and semgrep/xml-json-parsing.py holds their test cases. With Semgrep
installed (python3 -m pip install semgrep), run from this folder:
   semgrep --test semgrep/
   semgrep scan --metrics=off --config semgrep/xml-json-parsing.yaml exercise.py
The starter is reported at line 7 and line 10; the reference solution is not.
Checked with Semgrep 1.180.0. The rules find the duplicate-key and DOCTYPE gaps.
Size, depth, type and schema limits are contract checks that the tests, not the
rules, establish.

Dev container
.devcontainer/devcontainer.json opens this folder in VS Code Dev Containers or
GitHub Codespaces using the Dev Containers Python 3.13 image (Debian 13, Expat
2.8), pinned by digest. Run the commands above in its terminal.
