in reviewhumanPR #304Privacy
[Fellowship Challenge] privacy: redact.values.v1, a secret copied under another field name stays out of the evidence
Submission for the Hexaware x Project NANDA Fellowship Technical Challenge
Author
@malharinamdar
github profile →- Status
- In review
- Opened on
- Oct 1
- Branch
- challenge/malharinamdar-value-redaction
Description
The pitch.
Submission for the Hexaware x Project NANDA Fellowship Technical Challenge
**Track:** Lab · **Layer:** privacy · **Fault:** an agent pastes its private key into a message under a different field name · **Control:** the same scenario on `redact.v1`
## The gap
`redact.v1` hides declared private fields by name. If an agent repeats the value under another name, for example inside a message note, the value is exported as is. The standing `privacy` stage is name-based too, so the report still says the field "never left the run".
I reproduced this on `main` with a buyer that adds `note: "my key is <api_key>"` to its quote requests. The key ended up in `events.jsonl` and `intents.jsonl`, yet the bundle passed `nandatown verify` and the existing privacy stage. The privacy check confirmed name-based redaction, but it did not detect a secret copied under a different field name. This is also a common way LLM agents leak: they repeat private context in their own words.
## What this adds
- **`redact.values.v1`** (`layers/privacy.py`). It does everything `redact.v1` does. It also reads the values of the declared fields from the scenario's agent configs and replaces every verbatim copy of them inside exported strings. It only handles text values of at least 8 characters. Numbers and short strings stay name-only, because blanking every `10` or `1995` in a trace would destroy unrelated evidence. `redact.v1` is unchanged.
- **`chatty_buyer`** (`sim/agents.py`). A normal buyer that pastes its `api_key` into the note of each quote request. Before sending, it records `secret_shared` with the field name but not the value, so the attempt is still provable after the value is redacted. To support this, `Buyer` gained a one-line `quote_request_body()` hook; existing scenarios produce the same stages.
- **Two scenarios.** `secret_leak` (new rule) and `secret_leak_name_only` (`redact.v1`). They are identical apart from the `privacy:` line.
- **`secret_leak` validator** with three stages:
| stage | passes when |
|---|---|
| `leak_attempted` | a `secret_shared` marker from the chatty agent exists, so the fault actually fired |
| `secret_withheld` | no string in any exported event matches the test-key format `nt-canary-[0-9a-f]{16}`; if no leak was attempted, the stage is `not_enough_evidence`, not a pass |
| `trade_completed` | the trade still settles, so the scrubbing did not break anything |
The validator only knows the format of the test key, never the key itself, so it replays from public records and `verify` works offline.
The fault is deliberately introduced through the `chatty_buyer` agent, which copies its private API key into an outgoing message under a different field name. This is a behavioural fault rather than a network fault, so the report's `Faults: none` header is expected: that header only lists network faults declared in the scenario's `faults:` block. The bundled `capability_spoofing` scenario uses the same pattern. The `leak_attempted` stage records evidence that the behaviour actually occurred, without exposing the key.
## Result
`nandatown compare secret_leak --swap privacy=redact.v1`
| stage | redact.values.v1 | redact.v1 |
|---|---|---|
| leak_attempted | passed | passed |
| secret_withheld | passed | **failed** (2 events) |
| trade_completed | passed | passed |
| ledger_conserved | passed | passed |
| privacy (existing) | passed | passed |
Only `secret_withheld` changes, which is the predicted failure. The last row is the original problem: under `redact.v1` the name-based stage still passes while the key is in the bundle.
With the new rule, the key appears in none of the bundle's files, including `report.md`, `receipt.json` and the `visualize` output.
Both bundles pass `nandatown verify`. That confirms their integrity, and that the recorded result replays from the public records. The `redact.v1` control still fails `secret_withheld`, as predicted: a verified bundle can carry a failing verdict.
These are the bundles f
…Try it
Open PR on GitHubView diffCheckout locally
git fetch origin pull/304/head:pr-304
git checkout pr-304