smith/src/structured.rs inserts transformed keys into one map.
smith-core/src/agent.rs uses this transformation for masking and authorized local unmasking.
Distinct input keys can map to one output key; later insertion replaces earlier value.
let data = smith::ToolArguments::from_json(
r#"{"secret-value":1,"{{secret:token}}":2}"#,
)?;
let masked = data.transform_strings(
&|text| text.replace("secret-value", "{{secret:token}}"),
64,
)?;
// Serialized result: {"{{secret:token}}":2}; first value disappeared.
Impact
Argument members disappear during secret substitution.
Recorded and authorized effect inputs can lose information.
No proof of filesystem escape or secret disclosure.
Resolution candidate
Reject transformed-key collisions explicitly before any effect.
Diagnostic must not expose original keys or secret values.
Pin nested masking and unmasking collisions with regression tests.
Existing behavior changes; resolve separately from representation-only migration.
---
id: SMH-ISSUE-JSONKEYS
type: issue
title: "Secret substitution overwrites colliding object keys"
specs: [SMH-SPEC-SPEC0001]
status: open
---
# Secret substitution overwrites colliding object keys
## Boundary
Secret substitution across nested object keys.
Shared string transformation inherits existing last-key-wins behavior.
## Evidence
`smith/src/structured.rs` inserts transformed keys into one map.
`smith-core/src/agent.rs` uses this transformation for masking and authorized local unmasking.
Distinct input keys can map to one output key; later insertion replaces earlier value.
```rust
let data = smith::ToolArguments::from_json(
r#"{"secret-value":1,"{{secret:token}}":2}"#,
)?;
let masked = data.transform_strings(
&|text| text.replace("secret-value", "{{secret:token}}"),
64,
)?;
// Serialized result: {"{{secret:token}}":2}; first value disappeared.
```
## Impact
Argument members disappear during secret substitution.
Recorded and authorized effect inputs can lose information.
No proof of filesystem escape or secret disclosure.
## Resolution candidate
Reject transformed-key collisions explicitly before any effect.
Diagnostic must not expose original keys or secret values.
Pin nested masking and unmasking collisions with regression tests.
Existing behavior changes; resolve separately from representation-only migration.