Skip to main content

PII Detection & Redaction

AxonFlow detects Personally Identifiable Information (PII) in the requests and responses it governs, and redacts it where a policy says so. Each detector is a pattern plus a validator: the pattern finds candidates, and the validator (a checksum, a format rule, or a required label next to the value) decides which candidates count. An accepted match is a fact. What happens to it - allow, redact, block, warn or log - is decided by the policy decision engine from the stored policy action and your organization's settings, not by the detector.

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 pair - POST /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.

What decides the runtime outcome of a PII match

For pii-* policies the action stored on the matched policy decides, unless the organization has recorded a pii detection-posture override, which replaces it for every pii-* policy. Since v11 no environment variable or profile sets it: PII_ACTION and AXONFLOW_PROFILE are ignored with a boot warning. A row that stores block denies unless a weaker pii override is recorded. See System Policies: what the Action column means at runtime for the per-plane matrix, the resolution order, and the planes that behave differently.

Enterprise Privacy Governance​

AxonFlow Enterprise does not use a separate hidden PII engine behind the scenes. It extends the same policy and detection framework used across the product and makes it easier to operate across many teams through:

  • organization-wide governance
  • broader policy capacity
  • approval actions for higher-risk events
  • longer audit-retention profiles
  • evidence export
  • protected portal workflows

That makes enterprise PII governance useful not only for masking sensitive values, but for operating real privacy and compliance workflows over time.

A good enterprise privacy model​

Use system policies for the broad baseline:

  • email and phone detection
  • card and bank-account patterns
  • SSNs, IBANs, PAN, Aadhaar, NRIC, FIN, and other regional identifiers

Use organization policies for company-wide privacy requirements:

  • internal employee identifiers
  • customer-account formats
  • business-specific restricted fields
  • regional privacy baselines that should apply across teams

On Enterprise, use group-scoped policies in your organization's typed policy document for local context:

  • one product line
  • one region
  • one regulated workflow
  • one especially sensitive internal tool

Do not treat PII governance as only redaction. In practice, privacy teams often combine redact for lower-risk masking, block for clear violations, require_approval where context matters, and evidence export for review.

Supported PII Types​

Each shipped type is a system policy row (sys_pii_*). The Validation column is what the validator requires before a candidate counts. The shipped action for each row, per plane, is on Shipped Policy Posture.

Global PII Types​

TypePolicyExampleValidation
Credit Cardsys_pii_credit_card4532-0151-1283-036613-19 digits that pass the Luhn check
Emailsys_pii_email[email protected][email protected] format check
Phonesys_pii_phone(555) 123-45677-15 digits, not all the same digit; nearby words raise or lower confidence, and a low-confidence match is rejected
IP Addresssys_pii_ip_address203.0.113.200 is rejected (documentation range); 81.2.69.160 countsIPv4 octets; loopback, link-local, multicast, carrier-grade NAT, 0.x and the documentation ranges are rejected, and so is a value directly after a version label
Passportsys_pii_passportpassport AB12345671-2 letters and 6-9 digits, with a passport label directly before the value
Date of Birthsys_pii_dobDOB: 01/15/1990a US or ISO date, with a birth-date label directly before it

A booking-reference row (sys_pii_booking_ref) also ships, but it is inert by design: its matches fall to the credit-card validator, which rejects anything shorter than 13 digits, so it detects nothing. A booking reference is not treated as PII to redact.

United States PII Types​

TypePolicyExampleValidation
SSNsys_pii_ssn123-45-6789three groups optionally separated by - or a space, so a bare nine-digit run matches too; area 000, 666 and 900-999, group 00 and serial 0000 are rejected; a match with words such as "order" nearby and no SSN, employee, applicant, patient or member word nearby is rejected
Bank Accountsys_pii_bank_account021000021-12345678917-26 digits whose first nine pass the ABA routing checksum

European Union PII Types​

TypePolicyExampleValidation
IBANsys_pii_ibanDE8937040044053201300015-34 characters, country letters and check digits first, MOD 97 remainder of 1

India PII Types​

TypePolicyExampleValidation
India PANsys_pii_panABCPE1234Fthe AAAAA9999A format with a valid entity-type letter in the fourth position (format only; PAN has no check digit)
India Aadhaarsys_pii_aadhaaraadhaar 2345 6789 012312 digits, first digit 2-9, not all the same digit, and an Aadhaar or UID label next to the value

The shipped Aadhaar row does not run a Verhoeff checksum: about one in ten random 12-digit numbers passes that check, so it cannot tell an Aadhaar number from a barcode. The label is what qualifies the value. The Enterprise build of the India validator (below) does compute Verhoeff, but it uses the result to set its confidence, not to reject: a value that fails the checksum still counts, at lower confidence.

Singapore PII Types (MAS FEAT)​

Available in Community

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

TypePolicyExampleValidation
NRICsys_pii_singapore_nricNRIC S1234567Dprefix S, T, F, G or M, 7 digits and a letter, with an NRIC, national-registration or identity-card label directly before it
FINsys_pii_singapore_finFIN: F1234567Nprefix F or G, 7 digits and a letter, with a FIN (FIN:, FIN no.), foreign-identification or identity-card label directly before it
UENsys_pii_singapore_uenUEN 200312345A, T08LL1234A8-9 digits and a letter A-Z with a UEN, entity-number, business-registration or ACRA label directly before it, or the structured T/S form, which needs no label
SG Phonesys_pii_singapore_phone+65 9123 4567+65, then 8 digits starting 6, 8 or 9; the Phone validator's context scoring and low-confidence rejection apply
SG Postalsys_pii_singapore_postalSingapore 2388776 digits in the 01-82 sector range, with a Singapore or postal-code label directly before it

The trailing check letter of an NRIC or FIN is not verified. The label is what qualifies the value.

Indonesia and code-backed regional detectors​

TypeWhereBehaviour
KTP / NIKsys_pii_indonesia_ktpkeyword-anchored (KTP, nomor KTP, kartu tanda penduduk, ...) followed by a run of 16-24 digits and separators; request phase only; shipped action block; no checksum
Indonesian NIK, NPWP, phone and bank accountsthe Indonesia validator (indonesia_pii_protection)a code detector with no stored policy row
India Aadhaar, PAN, UPI, IFSC, bank account, GSTIN, voter ID, driving licence, passport, ration card, phone and PIN codethe India validator (rbi_pii_protection)a code detector with no stored policy row; pattern-based in Community, with the Verhoeff and context checks added in Enterprise

The India validator runs on the Decision API and the gateway pre-check, and blocks only under an organization pii=block override. The Indonesia validator blocks under pii=block on the Decision API and the gateway pre-check, and masks under pii=redact on the MCP check-input and check-output passes. It also masks NIK and NPWP on the orchestrator's response plane and on the Enterprise cowork ingest pass regardless of any override. A response or audit row a validator acted on names it in legacy_validators.

How It Works​

One scan on every plane​

Detection runs in one place, the shared policy engine. Every plane that governs PII runs the same patterns through the same validators. The scan walks the occurrences in order and keeps the ones the validator accepts: on the request path it answers with the first occurrence the validator accepts, and on the response path it keeps every accepted occurrence. There is no separate "enhanced" detector on some planes and a lighter one on others.

A validator's accept-or-reject is final. Some validators also compute a confidence from nearby words. SSN and phone reject a match whose confidence falls too low, but there is no global confidence threshold to tune.

Context and labels​

Each validator sees a window of text on either side of the candidate; the default context window is 50 characters. That context is how the SSN validator tells these two apart:

Input: "Order number: 123-45-6789"
→ "order" nearby, no SSN label → rejected (not an SSN)

Input: "Customer SSN: 123-45-6789"
→ SSN label nearby → accepted

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 dotted quad, a 6‑digit code. To avoid masking benign values (order ids, barcodes, asset tags, build numbers, route codes), these detectors require a label next to the value before they count it. A bare, unlabelled value of the same shape is not governed.

DetectorGoverned when…Not governed (benign)
Aadhaaran aadhaar / UID label is next to the value (aadhaar 2345 6789 0123)barcode 234567890123
Singapore NRIC / FINan NRIC / FIN: / identity‑card label directly precedes the valueasset tag S1234567Z
Singapore UEN (numeric form) / Postala UEN / business‑registration label, or a Singapore / postal code label, directly precedes the valueorder 12345678X, route 408600
Passport / Date of Birtha passport / birth‑date label directly precedes the valueorder X1234567, invoice 03/04/2025
IP Addressnot directly preceded by a version label (version, ver, semver, firmware, revision){"ver":"10.20.30.40"}, firmware 1.2.3.4
Safe direction

The gate is deliberately conservative: an unlabelled value that merely looks like one of these types is left untouched rather than masked. The detectors backed by a checksum - credit card (Luhn), IBAN (MOD 97) and bank account (ABA routing) - validate the value itself and do not depend on a label.

Configuration​

Choosing the PII Action​

Since v11 no environment variable sets the PII action. The action stored on each matched pii-* policy decides, and the organization's pii detection-posture override is the only thing that replaces it.

Shipped rowsStored action (request / response)
sys_pii_ssn, sys_pii_credit_card, sys_pii_singapore_nricwarn / redact
sys_pii_passport, sys_pii_dob, sys_pii_emaillog / redact
sys_pii_indonesia_ktpblock / none (request phase only)

To change the outcome, either record a pii override (Enterprise customer portal Settings → Governance → Detection Posture, or the API below; a Community deployment can seed rows directly), or change that one policy for your organization: in v11 every edition can disable, re-enable or re-action a shipped policy.

Actions:

  • block - Reject request with HTTP 403
  • warn - Log warning, allow request through unchanged
  • 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

On the stored policy rows, warn and log detect for audit but do not modify the content. The code-backed detectors with no stored policy row (Indonesian NIK / NPWP via indonesia_pii_protection, and India PII via rbi_pii_protection) block only under an organization pii=block override, and mask or emit a redact obligation only under pii=redact - each on its own planes: the India validator on the Decision API and the gateway pre-check, and the Indonesia validator on those two plus the MCP check-input and check-output passes. One exception masks with no override: the Indonesia validator masks NIK and NPWP on the orchestrator's response plane and on the Enterprise cowork ingest pass whatever the stored action or override. Elsewhere, with no override, they detect and record, and the stored static policy rows decide. If you need these values masked on the other planes, record pii=redact (or pii=block).

# Out of the box: the stored pii-* policy actions decide
docker compose up -d

# Enterprise: mask PII for your organization (portal session with sso:configure)
curl -b "axonflow_session=$SESSION" -X PUT -H 'Content-Type: application/json' \
-d '{"action":"redact"}' http://localhost:8082/api/v1/detection-posture/pii

# Block requests containing PII for your organization
curl -b "axonflow_session=$SESSION" -X PUT -H 'Content-Type: application/json' \
-d '{"action":"block"}' http://localhost:8082/api/v1/detection-posture/pii

# Remove the override: the stored policy actions decide again
curl -b "axonflow_session=$SESSION" -X DELETE http://localhost:8082/api/v1/detection-posture/pii
Removed in v11

PII_ACTION, MCP_PII_ACTION, GATEWAY_PII_ACTION, PII_BLOCK_CRITICAL and AXONFLOW_PROFILE no longer set an action. A deployment that still sets one keeps running with the stored policy actions, logs one boot WARN per variable, and increments axonflow_ignored_posture_env_total{name="<NAME>"}. (Earlier, v7.0.0 had moved the PII default from redact to warn through governance profiles, which v11 also removed.) The full list is in Environment Variables Reference.

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/v9"

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 removedSuits 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

Since v11 there is no per-execution-path PII action: the stored policy action and the organization's pii override apply on every plane that honors the override. No setting stops PII evaluation on one surface: a narrowing value refuses boot in v11. What each shipped PII policy does on each plane is listed in Shipped Policy Posture.

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 - masked rows for tabular responses, or a masked string for message-style responses).

The fail-closed signal - redaction_evaluated. The allow response carries a boolean redaction_evaluated - on check-input since platform v8.6.0, and on check-output since v9.7.0. 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:

FieldMeaning for the PEP
redaction_evaluated: trueThe redactor ran. Read the redaction fields (redacted / redacted_statement on the request leg, redacted_data on the response leg) and forward the engine-masked content.
redaction_evaluated: false (or absent)The redactor did not run (no detection config enabled). An unmasked result here would be indistinguishable from "looked, found nothing," so a PEP fulfilling a redact_pii obligation MUST fail closed - do not forward the content as if it were clean.

/decide is pre-call and only emits request-phase obligations, so the response leg is driven by the PEP's own check-output call - which, independent of redaction_evaluated, always fails closed on any unsuccessful engine round-trip (non-2xx, allowed: false, timeout, or an unexpected redacted_data shape). Engines older than v9.7.0 never emit redaction_evaluated on check-output. 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. When the resolved action is block (a stored block, or a pii=block override), 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.

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. Decide the outcome with policy, not code. Change a shipped row's action, record an organization pii override, or author a control in your organization's typed policy document. Do not add detection or masking to your integration.
  2. Include the label in your test data. Aadhaar, NRIC, FIN, passport, date of birth and numeric UENs count only with a label next to the value. A test that sends the bare value proves nothing.
  3. Read the plane before you rely on masking. A plane masks only where it can carry a redaction out: the MCP check-input and check-output passes, the orchestrator's response plane and the Enterprise cowork ingest pass mask content themselves, the Decision API hands a redact_pii obligation to your enforcement point, and the gateway pre-check answers requires_redaction: true. Shipped Policy Posture lists each row's action on each plane.
  4. Treat redaction_evaluated: false as a failure. It means the redactor did not run, not that it found nothing. See Decision Mode: two-touch redaction.
  5. Keep the audit trail. warn and log record a detection without changing content; use them while you tune, then move to redact or block.

See Also​

Operational Readiness Checklist​

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