Static read-through audit of crates/dpp-types. The crate is exclusively types + trait ports (no unsafe, no unwrap/panic, no TODO markers). Two verified findings.
1. Unrecognized scope values fail open to full Admin
Location: src/api_key.rs:62-71 (ApiKeyScope::from_scopes)
Issue: The match chain is is_empty() || contains "admin" → Admin, else contains "write" → Write, else contains "read" → Read, else — the final catch-all — Admin. This is not limited to the documented empty-array back-compat case; it catches any unrecognized value. This directly contradicts the enum's own doc comment, which frames Admin as the deliberately-opt-in risky tier and Read/Write as the least-privilege path.
Failure scenario: A scopes array like ["superuser"], a typo, a case mismatch, or any future scope string not yet in this match falls through every branch to the trailing else => Admin, silently granting full admin (key management + operator-config mutation) instead of failing safe to least privilege. Trigger: any row in the scopes TEXT[] column that isn't exactly {admin}, {write}, or {read} — manual edit, migration bug, or a future scope name added to the DB before this match is updated. (See also the related findings in dpp-dal and dpp-vault — this is a systemic gap across the scope-enforcement chain.)
2. Retention policy accepts non-positive values
Location: src/operator.rs:187-189 (UpdateOperatorConfig::apply)
Issue: retention_policy_days is copied straight from the PATCH body onto OperatorConfig with no range check, despite its own doc comment describing it as an ESPR-driven minimum-retention invariant (default 3650 = ~10 years).
Failure scenario: {"retentionPolicyDays": -1} in a PATCH request body passes through unchanged, leaving a config that violates the documented minimum-retention guarantee with no rejection at this layer.
Static read-through audit of
crates/dpp-types. The crate is exclusively types + trait ports (no unsafe, no unwrap/panic, no TODO markers). Two verified findings.1. Unrecognized scope values fail open to full Admin
Location:
src/api_key.rs:62-71(ApiKeyScope::from_scopes)Issue: The match chain is
is_empty() || contains "admin" → Admin, elsecontains "write" → Write, elsecontains "read" → Read, else — the final catch-all — Admin. This is not limited to the documented empty-array back-compat case; it catches any unrecognized value. This directly contradicts the enum's own doc comment, which framesAdminas the deliberately-opt-in risky tier andRead/Writeas the least-privilege path.Failure scenario: A scopes array like
["superuser"], a typo, a case mismatch, or any future scope string not yet in this match falls through every branch to the trailingelse => Admin, silently granting full admin (key management + operator-config mutation) instead of failing safe to least privilege. Trigger: any row in thescopes TEXT[]column that isn't exactly{admin},{write}, or{read}— manual edit, migration bug, or a future scope name added to the DB before this match is updated. (See also the related findings indpp-dalanddpp-vault— this is a systemic gap across the scope-enforcement chain.)2. Retention policy accepts non-positive values
Location:
src/operator.rs:187-189(UpdateOperatorConfig::apply)Issue:
retention_policy_daysis copied straight from the PATCH body ontoOperatorConfigwith no range check, despite its own doc comment describing it as an ESPR-driven minimum-retention invariant (default 3650 = ~10 years).Failure scenario:
{"retentionPolicyDays": -1}in a PATCH request body passes through unchanged, leaving a config that violates the documented minimum-retention guarantee with no rejection at this layer.