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 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.
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
| Type | Policy | Example | Validation |
|---|---|---|---|
| Credit Card | sys_pii_credit_card | 4532-0151-1283-0366 | 13-19 digits that pass the Luhn check |
sys_pii_email | [email protected] | [email protected] format check | |
| Phone | sys_pii_phone | (555) 123-4567 | 7-15 digits, not all the same digit; nearby words raise or lower confidence, and a low-confidence match is rejected |
| IP Address | sys_pii_ip_address | 203.0.113.200 is rejected (documentation range); 81.2.69.160 counts | IPv4 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 |
| Passport | sys_pii_passport | passport AB1234567 | 1-2 letters and 6-9 digits, with a passport label directly before the value |
| Date of Birth | sys_pii_dob | DOB: 01/15/1990 | a 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
| Type | Policy | Example | Validation |
|---|---|---|---|
| SSN | sys_pii_ssn | 123-45-6789 | three 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 Account | sys_pii_bank_account | 021000021-123456789 | 17-26 digits whose first nine pass the ABA routing checksum |
European Union PII Types
| Type | Policy | Example | Validation |
|---|---|---|---|
| IBAN | sys_pii_iban | DE89370400440532013000 | 15-34 characters, country letters and check digits first, MOD 97 remainder of 1 |
India PII Types
| Type | Policy | Example | Validation |
|---|---|---|---|
| India PAN | sys_pii_pan | ABCPE1234F | the AAAAA9999A format with a valid entity-type letter in the fourth position (format only; PAN has no check digit) |
| India Aadhaar | sys_pii_aadhaar | aadhaar 2345 6789 0123 | 12 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)
Singapore PII detection is available in AxonFlow Community Edition. See MAS FEAT Compliance.
| Type | Policy | Example | Validation |
|---|---|---|---|
| NRIC | sys_pii_singapore_nric | NRIC S1234567D | prefix S, T, F, G or M, 7 digits and a letter, with an NRIC, national-registration or identity-card label directly before it |
| FIN | sys_pii_singapore_fin | FIN: F1234567N | prefix F or G, 7 digits and a letter, with a FIN (FIN:, FIN no.), foreign-identification or identity-card label directly before it |
| UEN | sys_pii_singapore_uen | UEN 200312345A, T08LL1234A | 8-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 Phone | sys_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 Postal | sys_pii_singapore_postal | Singapore 238877 | 6 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
| Type | Where | Behaviour |
|---|---|---|
| KTP / NIK | sys_pii_indonesia_ktp | keyword-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 accounts | the 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 code | the 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.
| Detector | Governed when… | Not governed (benign) |
|---|---|---|
| Aadhaar | an aadhaar / UID label is next to the value (aadhaar 2345 6789 0123) | barcode 234567890123 |
| Singapore NRIC / FIN | an NRIC / FIN: / identity‑card label directly precedes the value | asset tag S1234567Z |
| Singapore UEN (numeric form) / Postal | a UEN / business‑registration label, or a Singapore / postal code label, directly precedes the value | order 12345678X, route 408600 |
| Passport / Date of Birth | a passport / birth‑date label directly precedes the value | order X1234567, invoice 03/04/2025 |
| IP Address | not directly preceded by a version label (version, ver, semver, firmware, revision) | {"ver":"10.20.30.40"}, firmware 1.2.3.4 |
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 rows | Stored action (request / response) |
|---|---|
sys_pii_ssn, sys_pii_credit_card, sys_pii_singapore_nric | warn / redact |
sys_pii_passport, sys_pii_dob, sys_pii_email | log / redact |
sys_pii_indonesia_ktp | block / 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 403warn- Log warning, allow request through unchangedredact- Mask detected PII with category-tagged placeholders (e.g.,[REDACTED:ssn]), allow requestlog- Log for audit only, allow unmodified
redact or blockOn 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
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
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:
| Action | Runtime behavior | Typical use |
|---|---|---|
redact | Request continues with detected values masked or removed | Suits production systems that need continuity |
block | Request is rejected during pre-check or request processing | Strict regulated workflows |
warn | Request continues and detection is recorded | Rollout and tuning |
log | Request continues and detection is written to audit | Observation 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-inputreturns an engine-redacted request (redacted_statement).POST /api/v1/mcp/check-outputreturns 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:
| Field | Meaning for the PEP |
|---|---|
redaction_evaluated: true | The 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/decidenever returns content. When the resolved action isblock(a storedblock, or apii=blockoverride), request-phase PII is denied rather than redacted. Redacted content always comes back from the engine'scheck-input/check-outputendpoints, never from client-side code.
Compliance
AxonFlow's PII detection helps with compliance requirements:
| Regulation | Supported PII Types |
|---|---|
| PCI-DSS | Credit card numbers, bank accounts |
| HIPAA | SSN, DOB |
| GDPR | Email, phone, IP address |
| CCPA | SSN, email, phone |
Best Practices
- Decide the outcome with policy, not code. Change a shipped row's action, record an organization
piioverride, or author a control in your organization's typed policy document. Do not add detection or masking to your integration. - 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.
- 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_piiobligation to your enforcement point, and the gateway pre-check answersrequires_redaction: true. Shipped Policy Posture lists each row's action on each plane. - Treat
redaction_evaluated: falseas a failure. It means the redactor did not run, not that it found nothing. See Decision Mode: two-touch redaction. - Keep the audit trail.
warnandlogrecord a detection without changing content; use them while you tune, then move toredactorblock.
See Also
- Media Governance -- text that the local OCR analyzer extracts from an image is scanned by the same PII detectors, and the result is a media signal the decision engine reads. The cloud image analyzers return no extracted text, so nothing is scanned for PII there
- Security Best Practices
- Policy Configuration
- Gateway Mode
Operational Readiness Checklist
Before relying on this page in a production rollout, pair it with the core operations docs:
- Deployment Mode Matrix for self-hosted, Evaluation, Enterprise, SaaS, and In-VPC fit
- Failure Modes And Recovery for degraded-provider, connector, approval, and runtime behavior
- Capacity Planning for sizing and growth signals
- Community vs Evaluation vs Enterprise for limits, support surfaces, and upgrade triggers
