agentsclimarketplace

Reversible pii masking context

Skill kjuhwa/skills-hub/skills/safety/reversible-pii-masking-context

Self-correcting knowledge corpus for Claude Code — 9 stable shape clusters, bias-correction pipeline baked into contribution flow. 47 papers, 45 techniques, 1.1k skills.

Install
npx -y skills add kjuhwa/skills-hub --skill reversible-pii-masking-context

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

One thing to look at

  • 0 stars0 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

What its author says it does

Copied from the file, not written here

Replace sensitive identifiers with stable placeholders before LLM calls and reverse-map them on the way out, using a per-investigation context that survives node-to-node state transitions in a graph pipeline.

SKILL.md

3.2 KB, as published. Nobody here has run it

Reversible PII Masking Context

When to use

You need to send logs/evidence to an LLM but must keep raw identifiers (pod names, namespaces, IPs, account IDs, emails) out of the prompt. After the LLM responds, you want the original identifiers restored in the output so users still see real names. The placeholder map needs to survive across multiple LangGraph nodes.

How it works

  • A MaskingContext carries a placeholder map (<NS_0> -> "kube-system") plus a counter per identifier kind so successive calls reuse the same placeholder for the same value.
  • mask_value recurses into dicts/lists/tuples; same for unmask_value.
  • MaskingContext.from_state(state) reconstructs the context from state["masking_map"]; to_state() serializes it back. Each node masks evidence before LLM input, then unmasks the LLM's output before storing it back to state.
  • A regex-based MaskingPolicy (built from env vars per-investigation, no singleton) decides which identifier kinds are enabled.

Example

class MaskingContext:
    @classmethod
    def from_state(cls, state):
        policy = MaskingPolicy.from_env()
        existing = state.get("masking_map") or {}
        return cls(policy=policy, placeholder_map=dict(existing))

    def _ensure_placeholder(self, kind, value):
        if value in self._reverse_map:
            return self._reverse_map[value]
        index = self._counters.get(kind, 0)
        self._counters[kind] = index + 1
        placeholder = f"<{kind.upper()}_{index}>"
        self._placeholder_map[placeholder] = value
        self._reverse_map[value] = placeholder
        return placeholder

    def mask_value(self, value):
        if isinstance(value, str):  return self.mask(value)
        if isinstance(value, dict): return {k: self.mask_value(v) for k, v in value.items()}
        if isinstance(value, list): return [self.mask_value(v) for v in value]
        return value

# Usage in a node:
masking_ctx = MaskingContext.from_state(dict(state))
masked_evidence = masking_ctx.mask_value(evidence)
result["masking_map"] = masking_ctx.to_state()
# ...later, on LLM output:
display_root_cause = masking_ctx.unmask(llm_response)

Gotchas

  • Apply replacements in reverse order of position so earlier indices stay valid.
  • Resolve overlapping detector matches by keeping the longest/earliest one — overlapping spans corrupt the output.
  • Build the policy fresh per investigation (MaskingPolicy.from_env()) so toggling env vars between calls works without restarting the process.
  • Compile extra regex patterns once per context, not per mask() call.

Keep looking

Skills are one crate of 328,083. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.