Static read-through audit of crates/dpp-integrator — an unusually well-hardened bulk-upload surface (zip-bomb/dimension-bomb guards, row cap, body-size limit ahead of auth+parsing). Two verified findings.
1. GTIN accepted with zero format/checksum validation for three sectors
Location: src/domain/validate/steel.rs:29, aluminium.rs:29, tyre.rs:30
Issue: battery.rs:67-77 runs Gtin::parse(&g), enforcing 14 digits and the GS1 mod-10 check digit. Steel/aluminium/tyre instead use require_str and pass the raw string straight through (their struct fields are plain String, not Gtin). dpp-vault's Rust-level check-digit validation is explicitly scoped to SectorData::Battery only, so nothing downstream catches this either.
Failure scenario: Uploading a steel/aluminium/tyre CSV row with gtin=123 or gtin=not-a-code passes row validation and is forwarded to dpp-vault verbatim — a bad checksum on a nominally 14-digit code is never caught anywhere in the pipeline.
2. Vault response missing id is silently recorded as success
Location: src/domain/batch_runner.rs:79-88
Issue: body.get("id").and_then(|v| v.as_str()).unwrap_or("") — if the vault ever returns 2xx without a string id (contract drift, partial response), the row lands in created with passport_id: "" instead of surfacing an error.
Failure scenario: success_count overstates real success; the operator gets an unusable empty ID with no error to act on.
Static read-through audit of
crates/dpp-integrator— an unusually well-hardened bulk-upload surface (zip-bomb/dimension-bomb guards, row cap, body-size limit ahead of auth+parsing). Two verified findings.1. GTIN accepted with zero format/checksum validation for three sectors
Location:
src/domain/validate/steel.rs:29,aluminium.rs:29,tyre.rs:30Issue:
battery.rs:67-77runsGtin::parse(&g), enforcing 14 digits and the GS1 mod-10 check digit. Steel/aluminium/tyre instead userequire_strand pass the raw string straight through (their struct fields are plainString, notGtin).dpp-vault's Rust-level check-digit validation is explicitly scoped toSectorData::Batteryonly, so nothing downstream catches this either.Failure scenario: Uploading a steel/aluminium/tyre CSV row with
gtin=123orgtin=not-a-codepasses row validation and is forwarded todpp-vaultverbatim — a bad checksum on a nominally 14-digit code is never caught anywhere in the pipeline.2. Vault response missing
idis silently recorded as successLocation:
src/domain/batch_runner.rs:79-88Issue:
body.get("id").and_then(|v| v.as_str()).unwrap_or("")— if the vault ever returns 2xx without a stringid(contract drift, partial response), the row lands increatedwithpassport_id: ""instead of surfacing an error.Failure scenario:
success_countoverstates real success; the operator gets an unusable empty ID with no error to act on.