Relationships
↔ sibling #2817#1674 Feature: user-configurable redaction rules (.swamp/redaction.yaml)
Opened by webframp · 8/16/2026
Summary
Add support for per-repo custom redaction rules that run additively after the built-in 17-pattern redactor. Rules are declared in .swamp/redaction.yaml and follow the same guard-expression model used by workflow steps: a single optional field from a fixed enum that controls whether a match applies in context.
Motivation
Enterprise environments have organization-specific sensitive data (internal project codenames, employee badge IDs, internal hostnames under custom domains, proprietary identifiers) that the built-in redactor cannot anticipate. Users currently choose between --no-redact (losing all protection) or manually sanitizing before submission. A structured, testable layer of custom rules fills this gap without modifying the core redactor.
Proposed design
Rule format
# .swamp/redaction.yaml
version: 1
rules:
- name: project-codenames
category: project-name
pattern: '\b(Falcon|Condor|Osprey)\b'
guard: not-inside-code-fence
description: "Internal project codenames"
- name: badge-ids
category: badge-id
pattern: '\bEMP-\d{7}\b'
description: "Employee badge numbers"
- name: corp-hosts
category: hostname
pattern: '[\w.-]+\.megacorp\.internal\b'
guard: not-preceded-by
guard-value: "sha256:"
description: "Internal hostnames under megacorp.internal"A rule is: name + category + pattern + optional guard + optional description.
Guards (modeled after workflow step guards)
In workflows, a guard is a single expression that determines whether a step runs. Here, a guard determines whether a pattern match is kept or discarded. Fixed enum, three values to start:
| Guard | Behavior |
|---|---|
not-inside-code-fence |
Skip matches between triple-backtick delimiters |
preceded-by |
Match only when preceded by guard-value literal |
not-preceded-by |
Skip matches preceded by guard-value literal |
No guard means the pattern matches unconditionally (same as a workflow step with no guard always executes). The set is intentionally minimal — the maintaining team can expand it if demand justifies the maintenance surface.
Execution model
- Built-in redactor runs first (all 17 patterns, unchanged behavior).
- Custom rules run in declaration order against the already-redacted text.
- Custom rules share the existing
PlaceholderMap, socategory: hostnamemerges into the built-in[HOST-N]series rather than creating parallel numbering.
Running custom rules second prevents them from interfering with built-in pattern matching (e.g., a broad custom pattern consuming a credit card before the Luhn-validated built-in catches it properly).
Safety: defense in depth against both malicious and mistaken patterns
Static (rule load time):
- Reject patterns with nested quantifiers (
(a+)+,(a*)*) — canonical ReDoS structures. - Enforce max pattern length (e.g., 256 chars).
- Validate regex syntax at parse time.
Runtime (match time):
- Per-pattern deadline (e.g., 50ms). Timeout causes skip + warning, not failure.
- Total custom-rule budget (e.g., 500ms cumulative). Excess skips remaining rules with summary warning.
These two layers mean a malicious pattern cannot DoS the client and a mistaken broad pattern degrades gracefully with visible feedback.
Over-breadth protection via swamp redaction test
New command for dry-run validation:
$ swamp redaction test --file sample-issue.md
Rule "project-codenames": 3 matches (lines 4, 12, 27)
Rule "badge-ids": 1 match (line 9)
Rule "corp-hosts": 0 matches
Total custom redactions: 4If a rule exceeds a configurable match threshold (e.g., 50 matches), emit a warning about pattern breadth. The test command never submits content — pure local dry-run against user-supplied input.
CLI flag: --redact-opts
--redact-opts=all # default: built-in + custom
--redact-opts=builtin # only the 17 built-in patterns
--redact-opts=custom # only custom rules (useful for testing)
--redact-opts=none # skip all redaction--no-redact becomes an alias for --redact-opts=none for backward compatibility.
Scope boundaries (v1)
- Per-repo only (no user-level or inherited rules in v1).
- Client-side only (no server enforcement).
- No arbitrary code execution in rules (no CEL, no lambdas, no validators beyond the guard enum).
- No conditional rules based on issue metadata (labels, assignees, etc.).
Risks and considerations
- Maintenance surface of guards: each new guard value is a code path to implement and test. Starting with three keeps this bounded.
- Pattern portability: regex dialects vary. Specifying the engine (Rust regex via Deno, or explicitly PCRE-subset) avoids cross-platform surprises.
- Interaction with
redactIssueContentreturn type: custom rules need to produceRedactionLineChangeentries and contribute toRedactionSummarycategories so the existing transparency diff ("line N: before -> after") works unchanged. - Future extensibility: per-user rules, rule inheritance across repos, and richer guards are natural follow-ons but deliberately excluded from v1 to keep the initial implementation tight.
Open
No activity in this phase yet.
stack72 commented 8/16/2026, 9:06:51 PM
@webframp I am giving you a heads up here - this isn't something we will take on soon - this is going to be something we would need to think about and how we would support it - our current redaction rules are something we control so that we can still try to triage issues and giving users their own ability to create their own rules may compromise that ability
I will leave this open for a little bit so I can think about it - but this isn't going to be something we take on for quite a while
Sign in to post a ripple.