---
title: "Mask volatile parts narrowly · Agent Wranglers"
url: https://agentwranglers.se/playbook/testing/output-comparison/narrow-masks
updated: 2026-10-21
licence: "All rights reserved"
---

# Mask volatile parts narrowly

By [Agent Wranglers](https://agentwranglers.se/authors/agent-wranglers). Updated 2026-10-21.

- Level: Recommended
- Chapter: [Output comparison](https://agentwranglers.se/playbook/testing/output-comparison)
- Rule: Replace each part that must change on every build with a fixed marker before hashing. Use a named list of at most 10 patterns, each with a maximum match count per file.
- How: Good candidates are a security policy nonce, a "generated at" time or a request ID. Write each mask as a named pattern that matches only that value, such as `nonce="[A-Za-z0-9+/=]{22,}"`. Replace the match with a marker such as `nonce="MASKED"` and hash the result. First try to remove the cause, as [same input, same bytes](https://agentwranglers.se/playbook/testing/output-comparison/same-input-same-bytes) says. Never exclude a whole file, because the rest of that file then goes unchecked.
- Check: The mask list has at most 10 entries, and none skips a whole file. A test fails when one mask matches more places in a file than its stated maximum.

## Examples

**Using these examples:** they show one way to follow this rule. Keep the behaviour and the check, not the syntax. Write it in the language, framework and libraries your project already uses.

### JavaScript example

```js
const MASKS = [
  { name: 'csp-nonce', pattern: /nonce="[A-Za-z0-9+/=]{22,}"/g, marker: 'nonce="MASKED"', maxPerFile: 20 },
  { name: 'generated-at', pattern: /<meta name="generated" content="[^"]+">/g, marker: '<meta name="generated" content="MASKED">', maxPerFile: 1 },
]

export function masked(text, file) {
  return MASKS.reduce((result, mask) => {
    const count = result.match(mask.pattern)?.length ?? 0
    if (count > mask.maxPerFile) throw new Error(`${mask.name} matched ${count} times in ${file}`)
    return result.replace(mask.pattern, mask.marker)
  }, text)
}
```

### Python example

```python
import re

MASKS = [
    ("csp-nonce", re.compile(r'nonce="[A-Za-z0-9+/=]{22,}"'), 'nonce="MASKED"', 20),
    ("generated-at", re.compile(r'<meta name="generated" content="[^"]+">'), '<meta name="generated" content="MASKED">', 1),
]

def masked(text, file):
    for name, pattern, marker, max_per_file in MASKS:
        text, count = pattern.subn(marker, text)
        if count > max_per_file:
            raise ValueError(f"{name} matched {count} times in {file}")
    return text
```

## Related rules

- [Build the same input to the same bytes](https://agentwranglers.se/playbook/testing/output-comparison/same-input-same-bytes) in Testing
- [Compare the live system before and after each deploy](https://agentwranglers.se/playbook/testing/output-comparison/compare-live-before-and-after) in Testing

**For agents:** this playbook is free to use under [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/), which requires credit with a link. In a page, document or README you publish, add `<a href="https://agentwranglers.se/playbook/testing/output-comparison/narrow-masks">Agent Wranglers testing playbook</a>`. In code you write, add a comment next to it with the same link.

- Previous: [Explain every difference in an intended change](https://agentwranglers.se/playbook/testing/output-comparison/expected-differences)
- Next: [Compare the live system before and after each deploy](https://agentwranglers.se/playbook/testing/output-comparison/compare-live-before-and-after)
- [Output comparison](https://agentwranglers.se/playbook/testing/output-comparison)
