---
title: 'Masking Sensitive Identifiers'
description: 'Reversible masking of pod, cluster, and account identifiers before external LLM calls'
---

## Overview

OpenSRE can mask sensitive infrastructure identifiers (pod names, cluster names, hostnames, account IDs, service names, IP addresses, emails) **before** sending text to external LLMs, and restore the originals in any user-facing output (Slack report, problem MD, ingest). This lets teams use external models while keeping raw identifiers private to the investigation runtime.

Masking is **off by default**. Enable it per investigation via environment variables — no code changes required.

## How it works

1. When masking is enabled, the investigation step replaces sensitive identifiers in collected evidence with stable placeholders like `<POD_0>`, `<NAMESPACE_0>`, `<CLUSTER_1>`. The placeholder→original map is stored in investigation state.
2. The diagnosis model receives masked evidence, so raw identifiers never hit the external LLM.
3. After the model returns its root-cause analysis, OpenSRE restores real identifiers in downstream state and display output.
4. Report delivery (for example Slack) runs a final unmask pass before sending, as defence in depth.

The same identifier always maps to the same placeholder within a single investigation, so the LLM's reasoning about `<POD_0>` remains coherent.

## Environment variables

| Variable | Default | Description |
| --- | --- | --- |
| `OPENSRE_MASK_ENABLED` | `false` | Master switch. Set to `true` / `1` / `yes` / `on` to activate masking. |
| `OPENSRE_MASK_KINDS` | `pod,namespace,cluster,hostname,account_id,ip_address,email,service_name` | Comma-separated list of identifier kinds to mask. Unknown kinds are ignored with a warning. Empty value uses all defaults. |
| `OPENSRE_MASK_EXTRA_REGEX` | *(empty)* | Optional JSON object mapping a label → regex for custom identifiers. Example: `'{"jira_key": "\\\\b[A-Z]+-\\\\d+\\\\b"}'`. Group 1 of the regex, if present, defines the span to mask. |

Policies are read fresh from the environment at the start of each investigation — changes take effect on the next run without a restart.

## Built-in identifier kinds

| Kind | Example input | Example placeholder |
| --- | --- | --- |
| `pod` | `etl-worker-7d9f8b-xkp2q` | `<POD_0>` |
| `namespace` | `kube_namespace:tracer-test` | `kube_namespace:<NAMESPACE_0>` |
| `cluster` | `eks_cluster:prod-us-east-1` | `eks_cluster:<CLUSTER_0>` |
| `service_name` | `service:checkout-api` | `service:<SERVICE_NAME_0>` |
| `hostname` | `kind-control-plane`, `ip-10-0-1-23.ec2.internal` | `<HOSTNAME_0>` |
| `account_id` | `123456789012` | `<ACCOUNT_ID_0>` |
| `ip_address` | `192.168.1.50` | `<IP_ADDRESS_0>` |
| `email` | `alice@example.com` | `<EMAIL_0>` |

## Round-trip guarantee

For the built-in detectors and extra regex patterns, `mask → unmask` round-trips the original payload byte-for-byte. See `tests/masking/test_integration_with_k8s_fixture.py` for a worked example against a realistic Datadog k8s alert.

## Relationship to guardrails

The masking layer is complementary to the one-way `GuardrailEngine`. Guardrails handle hard-block rules (credit cards, API keys) and replace matches with `[REDACTED]` irreversibly. Masking handles infrastructure identifiers reversibly so they can be restored for user-facing output.

Both can be active together: guardrails apply first at the LLM client layer, then masking at the node layer.

## Example

```bash
export OPENSRE_MASK_ENABLED=true
export OPENSRE_MASK_KINDS=pod,namespace,cluster,hostname
opensre investigate -i tests/e2e/kubernetes/fixtures/datadog_k8s_alert.json
```

During the investigation the LLM sees masked evidence; the final Slack report shows the original pod, namespace, and cluster names.
