Skip to main content
← Back to list
01Issue
FeatureOpenSwamp ClubPublicTeam
AssigneesNone

Relationships

#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

  1. Built-in redactor runs first (all 17 patterns, unchanged behavior).
  2. Custom rules run in declaration order against the already-redacted text.
  3. Custom rules share the existing PlaceholderMap, so category: hostname merges 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: 4

If 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 redactIssueContent return type: custom rules need to produce RedactionLineChange entries and contribute to RedactionSummary categories 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.
02Bog Flow
OPENTRIAGEDIN PROGRESSSHIPPED

Open

8/16/2026, 8:59:31 PM

No activity in this phase yet.

03Sludge Pulse
Editable. Press Enter to edit.

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

stack72 commented 8/16/2026, 9:10:16 PM

Also @webframp, @keeb and I are just chatting - this feature would fit into swamp club itself and not the CLI

webframp commented 8/16/2026, 9:16:37 PM

I had the idea so I formalized it into a ticket! What you do next is your call @stack72 @keeb and I'll trust it

Sign in to post a ripple.