Skip to main content

PII Detection & Redaction

AxonFlow provides built-in detection and redaction of Personally Identifiable Information (PII) in LLM interactions. The system uses a hybrid approach combining fast regex-based pattern matching with intelligent validation to minimize false positives while maintaining sub-millisecond latency.

AxonFlow redacts. Your integration calls AxonFlow — it never reimplements detection.

AxonFlow performs PII detection and redaction server-side; your integration calls AxonFlow's endpoints and forwards what they return. Your gateway, proxy, plugin, or SDK must not carry its own PII patterns or redaction logic — a hand-rolled client-side redactor is a different, unverified path and a governance gap, not a shortcut. In Decision Mode, POST /api/v1/decide returns a verdict plus self-describing obligations: a redact_pii obligation names the engine endpoint to call to discharge it. Redaction is a symmetric pairPOST /api/v1/mcp/check-input redacts a request, POST /api/v1/mcp/check-output redacts a response — so a governed exchange is a two-touch flow (decide → fulfill via the named endpoint → forward). See Decision Mode: two-touch redaction below.

Detection vs Policy

Detection identifies what happened. Policies determine what action to take.

For example, PII detection can be enabled while policies are configured to log-only during development, and later switched to block or require_approval in production — without changing application code.

This separation allows AxonFlow to support experimentation, audits, and enforcement within the same architecture.

Supported PII Types

Global PII Types

TypeSeverityExampleValidation
Credit CardCritical4532-0151-1283-0366Luhn algorithm
EmailMedium[email protected]RFC 5322 format
PhoneMedium(555) 123-4567Format + context
IP AddressMedium192.168.1.100IPv4 validation
PassportHighAB1234567Format + context
Date of BirthHigh01/15/1990Context-dependent
Booking ReferenceLowABC1236-char alphanumeric

United States PII Types

TypeSeverityExampleValidation
SSNCritical123-45-6789Area/group/serial rules
Bank AccountCritical021000021-123456789ABA routing checksum

European Union PII Types

TypeSeverityExampleValidation
IBANCriticalDE89370400440532013000MOD 97 checksum

India PII Types

TypeSeverityExampleValidation
India PANCriticalABCDE1234F10-char format with entity type
India AadhaarCritical2345 6789 012312-digit with Verhoeff checksum

Singapore PII Types (MAS FEAT)

New in Community

Singapore PII detection is now available in AxonFlow Community Edition. See MAS FEAT Compliance.

TypeSeverityExampleValidation
NRICCriticalS1234567D9-char with prefix S/T/M
FINCriticalF1234567N9-char with prefix F/G
UENHigh200312345A8-10 digit business registration
SG PhoneMedium+65 9123 4567+65 format, 8-digit
SG PostalLow2388776-digit Singapore postal

NRIC Prefixes:

  • S - Citizen born before 2000
  • T - Citizen born 2000+
  • M - Foreigner from 2022+
  • F - Foreigner before 2000
  • G - Foreigner 2000-2021

How It Works

Two-Layer Detection

  1. Agent Layer (Static Engine): Fast regex-based detection (<1ms) that flags potential PII for downstream processing
  2. Orchestrator Layer (Enhanced Detector): Deep validation with Luhn, MOD 97, and context-aware confidence scoring

False Positive Prevention

The enhanced detector uses context analysis to reduce false positives:

Input: "Order number: 123-45-6789"
→ Context contains "order" → Low confidence (not flagged as SSN)

Input: "Customer SSN: 123-45-6789"
→ Context contains "SSN" → High confidence (flagged as SSN)

Context-gated detectors (label required)

Several PII types share a shape with ordinary business data — a 12‑digit number, a letter+7digits+letter id, a 4‑part dotted version, a 10‑digit reference. To avoid masking benign values (order ids, barcodes, asset tags, build numbers, tracking ids), these detectors require a label or indicator adjacent to the value before they fire. A bare, unlabelled value of the same shape is not governed.

DetectorGoverned when…Not governed (benign)
Aadhaar (bare 12‑digit)an aadhaar / UID label is adjacent (aadhaar 2345 6789 0123)barcode 234567890123
Singapore NRIC / FINan NRIC / FIN / identity‑card label is adjacentasset tag S1234567Z
Singapore UEN / Postala UEN / Singapore / postal code label is adjacentorder 12345678X, route 408600
Passport / Date of Birtha passport / birth‑date label is adjacentorder X1234567, invoice 03/04/2025
IP Addressnot preceded by a version label{"ver":"10.20.30.40"}, build 1.2.3.4
Indonesian phonea real mobile (≥7 digits after 8X)short id 6281234509
Safe direction

The gate is deliberately conservative: an unlabelled value that merely looks like one of these types is left untouched rather than masked. Detectors backed by a checksum (credit card / Luhn, IBAN / MOD‑97, Aadhaar / Verhoeff, NIK / province+date) validate the value itself and do not depend on a label.

Configuration

Environment Variable Configuration

Configure PII detection behavior using environment variables:

VariableValuesDefaultDescription
PII_ACTIONblock, warn, redact, logwarn (v6.2.0+)Action when PII is detected
AXONFLOW_PROFILEdev, default, strict, compliancedefault (v7.0.0+)Bundles per-category actions. See Governance Profiles.

Actions:

  • block - Reject request with HTTP 403
  • warn - Log warning, allow request through unchanged (v6.2.0+ default)
  • redact - Mask detected PII with category-tagged placeholders (e.g., [REDACTED:ssn]), allow request
  • log - Log for audit only, allow unmodified
Masking requires redact or block

warn and log detect for audit but never modify the content. Checksum-validated national IDs (Indonesian NIK / NPWP) are masked only under PII_ACTION=redact, and hard‑denied under PII_ACTION=block. If you need these values masked in responses, set redact (or block) — warn/log will record the detection but pass the value through unchanged.

# v6.2.0+ default: PII is flagged in audit but the request flows through
docker compose up -d

# Restore the v6.1.0 behavior — PII masked with [REDACTED:*]
PII_ACTION=redact docker compose up -d

# Block requests containing PII (recommended for production)
PII_ACTION=block docker compose up -d

# Or use the strict profile which sets PII, SQLi, and sensitive data to block
AXONFLOW_PROFILE=strict docker compose up -d
Default Changed in v7.0.0

PII detection now defaults to warn (flag but do not mutate the data). The previous default was redact. Silent redaction was breaking debugging flows — see Governance Profiles for the full rationale and the new profile matrix.

Gateway Mode

PII detection is automatically applied during the gateway pre-check flow:

# Pre-check detects PII in prompts
curl -X POST https://api.example.com/api/policy/pre-check \
-H "Content-Type: application/json" \
-d '{
"user_token": "user-123",
"client_id": "community",
"query": "Customer SSN: 123-45-6789",
"context": {}
}'

Response includes policy and redaction context:

{
"approved": true,
"requires_redaction": true,
"policies": ["sys_pii_ssn"],
"context_id": "ctx_abc123"
}

SDK Usage

Community Examples

See complete working examples in the AxonFlow examples repository.

Python

from axonflow import AxonFlow

async with AxonFlow(
endpoint="http://localhost:8080",
client_id="your-client-id",
client_secret="your-secret",
) as client:
result = await client.get_policy_approved_context(
user_token="user-123",
query="Process refund for SSN 123-45-6789",
)

if not result.approved:
print(f"Blocked: {result.block_reason}")
elif result.policies:
print(f"Policies triggered: {result.policies}")

TypeScript

import { AxonFlow } from '@axonflow/sdk';

const axonflow = new AxonFlow({
endpoint: 'http://localhost:8080',
clientId: 'your-client-id',
clientSecret: 'your-secret',
});

const result = await axonflow.getPolicyApprovedContext({
userToken: 'user-123',
query: 'Process refund for SSN 123-45-6789',
});

if (!result.approved) {
console.log(`Blocked: ${result.blockReason}`);
}

Go

import "github.com/getaxonflow/axonflow-sdk-go/v8"

client := axonflow.NewClient(axonflow.AxonFlowConfig{
Endpoint: "http://localhost:8080",
})

result, err := client.GetPolicyApprovedContext(
"user-123",
"Process refund for SSN 123-45-6789",
nil, nil,
)

if !result.Approved {
log.Printf("Blocked: %s", result.BlockReason)
}

Java

import com.getaxonflow.sdk.AxonFlow;
import com.getaxonflow.sdk.AxonFlowConfig;
import com.getaxonflow.sdk.types.*;

AxonFlow client = AxonFlow.create(AxonFlowConfig.builder()
.endpoint("http://localhost:8080")
.build());

PolicyApprovalResult result = client.getPolicyApprovedContext(
PolicyApprovalRequest.builder()
.query("Process refund for SSN 123-45-6789")
.userToken("user-123")
.build()
);

if (!result.isApproved()) {
System.out.println("Blocked: " + result.getBlockReason());
}

Redaction Behavior

When PII is detected, the exact behavior depends on your configured action:

ActionRuntime behaviorTypical use
redactRequest continues with detected values masked or removedDefault for production systems that need continuity
blockRequest is rejected during pre-check or request processingStrict regulated workflows
warnRequest continues and detection is recordedRollout and tuning
logRequest continues and detection is written to auditObservation and evidence collection

If you need different behavior by execution path, use the mode-specific overrides:

  • PII_ACTION for the shared default
  • MCP_PII_ACTION for connector/MCP traffic
  • GATEWAY_PII_ACTION for gateway-mode requests

Decision Mode: two-touch redaction

In Decision Mode, your infrastructure gateway asks AxonFlow for a verdict and enforces it — AxonFlow is never on the traffic path. AxonFlow does all the PII work; your gateway calls AxonFlow's endpoints and forwards what they return. The blessed path is decide → fulfill the obligation via the named endpoint → forward.

Touch 1 — decision. The gateway calls POST /api/v1/decide. This is a decision-only endpoint: it returns a verdict (allow / deny / needs_approval), reasons, a correlation id, and obligations — and it never mutates content (no redacted payload is returned, and /decide does not see the response). When a policy requires redaction, the decision carries a self-describing obligation whose fulfillment block names the engine endpoint to call:

{
"verdict": "allow",
"obligations": [
{ "type": "redact_pii", "detail": "...",
"fulfillment": { "endpoint": "/api/v1/mcp/check-input", "method": "POST",
"phase": "request", "content_types": ["text/plain"] } }
],
"decision_id": "dec_abc123"
}

A redact_pii obligation is an instruction to call the engine — not something your gateway fulfills with its own code.

Touch 2 — fulfillment. The gateway sends the content to the endpoint the obligation names and forwards the engine-redacted result. Request and response redaction are a symmetric pair:

  • POST /api/v1/mcp/check-input returns an engine-redacted request (redacted_statement).
  • POST /api/v1/mcp/check-output returns an engine-redacted response (redacted_data for rows, redacted_message for a string).

The fail-closed signal — redaction_evaluated. On the check-input (request) leg, the allow response carries a boolean redaction_evaluated. It is the difference between "the detector ran and found nothing" and "the detector never ran" — and a PEP must treat those two cases differently:

check-input fieldMeaning for the PEP
redaction_evaluated: trueThe redactor ran. Read redacted / redacted_statement and forward the engine-masked statement.
redaction_evaluated: false (or absent)The redactor did not run (no detection config enabled). redacted: false here would be indistinguishable from "looked, found nothing," so the PEP MUST fail closed — do not forward the statement as if it were clean.

There is no redaction_evaluated field on check-output: /decide is pre-call and only emits request-phase obligations, so the response leg is driven by the PEP's own check-output call and fails closed on any unsuccessful engine round-trip (non-2xx, allowed: false, timeout, or an unexpected redacted_data shape). For the full set of rules see Building a Policy Enforcement Point.

Client → Gateway ──(1) POST /api/v1/decide ─────────────────→ AxonFlow  → allow + obligation (names endpoint)
Gateway → Backend (forward request) → raw response
Gateway ──(2) POST <obligation endpoint> (content) ─────────→ AxonFlow → engine-redacted content
Gateway → Client (forward redacted content)

The reference PEP client (platform/shared/pep) carries no PII patterns — its only redaction path is that engine round-trip, and it fails closed if an obligation can't be discharged through the engine. The redaction contract is content-type-aware: an obligation advertises the content_types the engine can handle, and a request for an unsupported content type is rejected (415) rather than forwarded ungoverned (today text/plain is wired; media routes to AxonFlow's media-governance subsystem). Coverage is policy-derived — the PII categories your active policies enable — and gateway detection is connector-agnostic: AxonFlow governs whatever content you submit, with no "enabled connector" prerequisite.

POST /api/v1/decide never returns content. Under PII_ACTION=block, request-phase PII is denied rather than redacted. Redacted content always comes back from the engine's check-input / check-output endpoints, never from client-side code.

Performance

OperationLatencyNotes
Single type detection~1μsType-specific check
Full detection (no PII)~17μsAll patterns
Full detection (with PII)~25μsWith validation
Long text (10KB)~1.4msComprehensive scan

Compliance

AxonFlow's PII detection helps with compliance requirements:

RegulationSupported PII Types
PCI-DSSCredit card numbers, bank accounts
HIPAASSN, DOB
GDPREmail, phone, IP address
CCPASSN, email, phone

Best Practices

  1. Enable validation for financial data (credit cards, bank accounts)
  2. Use context to reduce false positives
  3. Tune actions by environment so staging can warn while production blocks or redacts
  4. Log PII detection events for audit trails
  5. Test with realistic data to tune confidence thresholds

See Also

Operational Readiness Checklist

Before relying on this page in a production rollout, pair it with the core operations docs: