PII Checker User Guide
PII Checker detects personal data and credentials in Agent inputs and outputs. It returns a structured verdict, produces safe evidence and optional redacted text, and records sanitized Security Events for audit and Observability correlation.
Scan text
Provide exactly one input source: inline text, standard input, or a UTF-8 file.
# Inline text
agent-sec-cli scan-pii --text "contact alice@example.com"
# Standard input
printf '%s' 'token=secret-value-1234567890' \
| agent-sec-cli scan-pii --stdin --redact-output
# UTF-8 file
agent-sec-cli scan-pii --input ./agent-output.txt --format text
Useful options:
| Option | Purpose |
|---|---|
--format json|text | Select structured JSON or human-readable output; default is json |
--redact-output | Include redacted_text; the input file is never modified |
--include-low-confidence | Include findings below the default confidence threshold |
--raw-evidence | Include raw evidence in local CLI output only |
--max-bytes N | Scan at most N UTF-8 bytes and mark the result as truncated |
--source SOURCE | Label the audit context, such as user_input or tool_output |
Supported source labels are user_input, tool_input, tool_output, model_output,
observability, manual, and unknown.
Built-in detection
The built-in detector combines regex matching, format validation, and context-based confidence adjustment.
| Category | Types | Default severity |
|---|---|---|
| Personal data | email, phone_cn, credit_card, cn_id | warn |
| Credentials | private_key, bearer_token, api_key, jwt | deny |
| Alibaba Cloud credentials | aliyun_access_key_id, aliyun_access_key_secret | deny |
| Secret fields | generic_secret_field | deny |
Credit card, Chinese ID, and JWT candidates are validated before becoming findings. Surrounding
security keywords can increase confidence, while fixture markers such as example, dummy,
test, and sample can lower it. Findings below the default 0.5 threshold are omitted unless
--include-low-confidence is set.
Verdicts and redaction
The scanner aggregates findings into one verdict:
| Verdict | Meaning |
|---|---|
pass | No finding remains after confidence filtering |
warn | Findings exist, but none has deny severity |
deny | At least one finding has deny severity |
Each finding includes its type, category, severity, confidence, span, detector metadata, and
redacted evidence. --redact-output also returns a redacted copy of the scanned text. Overlapping
findings are preserved, while their overlapping spans are merged and replaced once. If different
spans overlap, the complete merged range is fully redacted so a shorter match cannot leave a
sensitive suffix visible.
--raw-evidence is intended only for local troubleshooting. Raw evidence is never written to
Security Events. Host integrations consume the same verdict and finding schema; whether a host
only observes a finding or blocks an operation depends on that host's configured PII policy.
Host hook policy
Set PII_CHECKER_HOOK_ENABLED=false to skip host PII hooks entirely. When enabled,
PII_CHECKER_MODE accepts observe, warn, ask, or block and defaults to
observe. ask or block falls back to warn when the host or hook event cannot enforce
that action; post-execution hooks never claim to undo an external side effect. The environment
policy overrides Hermes/OpenClaw capability configuration. debug maps to observe, and
deny maps to block. Qwen Code additionally accepts the legacy PII_CHECKER_ENABLED switch when
PII_CHECKER_HOOK_ENABLED is absent.
The host Agent reads these variables when it loads the plugin. Restart the Agent process that hosts the hook after changing them; the hook and agent-sec-core are not separate policy services.
Scanner verdict deny describes finding severity. Hook policy block controls whether the
current adapter attempts enforcement.
Custom regex rules
PII Checker optionally loads custom business-specific types from one fixed user-level file:
~/.config/agent-sec/pii-checker/rules.yaml
The YAML top level is a list. Each rule contains a unique custom type, one regex, and an optional severity.
- type: dogfood_order_no
regex: '(?i)(?<=order_no[=:])DFT-[A-Z0-9]{8}'
severity: warn
- type: dogfood_customer_token
regex: 'DFT-[A-Z0-9]{16}'
severity: deny
| Field | Required | Description |
|---|---|---|
type | Yes | Lowercase snake_case custom type, unique within the file |
regex | Yes | One regex expression; use inline flags such as (?i) |
severity | No | warn or deny; defaults to deny |
The complete regex match is the finding and redaction span. Capture groups and named capture groups
do not change that span. If a regex matches both a field name and its value, both are redacted. Use
lookaround when the full match must cover only the value. Multiple formats for one type must be
combined with regex alternation (\|); the same type cannot appear in multiple rules.
Custom findings use category custom, confidence 1.0, detector custom_rule, and engine regex.
They are fully redacted with a stable type marker such as [DOGFOOD_ORDER_NO_REDACTED] and flow
through the same verdict, policy, Security Event, and Observability paths as built-in findings.
No CLI option, environment variable, XDG override, system-level file, or multi-file merge is supported for the custom rules path.
Custom rule validation and runtime limits
The complete custom ruleset is accepted or rejected as one unit.
| Limit or rule | Value |
|---|---|
| Maximum file size | 256 KiB |
| Maximum number of rules | 100 |
| Maximum regex length | 2,048 characters |
| Maximum regex group nesting depth | 64 |
| Type format | ^[a-z][a-z0-9_]{0,63}$ |
| Allowed severity | warn or deny |
| Per-rule matching timeout | 20 ms |
| Total custom matching budget per scan | 200 ms |
| Maximum custom findings per scan | 100 |
Unknown YAML fields, duplicate types, built-in type names, invalid regexes, and regexes that match an empty string make the complete custom ruleset invalid. Other zero-length matches encountered at runtime are ignored.
Rules with deny severity run before warn rules. File order is preserved within each severity.
The 100-finding limit caps emitted custom findings but does not stop evaluation of later rules;
truncated becomes true only when an additional valid match is omitted. Per-rule or total time
limits may still stop the remaining custom rules and are reported separately.
When the file content changes, the next scan validates and compiles the new content automatically.
If the new version is invalid, the previous valid version is not reused. Built-in detection remains
active and scan-pii still completes successfully.
Custom rule status
Every default scan includes sanitized custom rule state in summary.custom_rules:
{
"custom_rules": {
"status": "loaded",
"rule_count": 2,
"runtime_error_count": 0,
"budget_exhausted": false,
"truncated": false
}
}
status is absent when the file does not exist, loaded when validation succeeds (including an
empty list), and invalid when reading, YAML parsing, schema validation, or regex compilation fails.
An invalid status includes a sanitized error_code; loaded or invalid content may include its
SHA-256 digest. Runtime counters do not contain input text or regex content. A direct scan-pii
invocation also prints a sanitized invalid-configuration warning to stderr while still exiting
successfully.
The current error_code values are:
| Error code | Meaning |
|---|---|
read_error | The rules file could not be read |
file_too_large | The rules file exceeds 256 KiB |
invalid_utf8 | The rules file is not valid UTF-8 |
invalid_yaml | The YAML content cannot be parsed safely |
top_level_not_list | The YAML top level is not a list |
too_many_rules | The file contains more than 100 rules |
invalid_rule_schema | A rule has missing, unknown, incorrectly typed, or unsupported fields |
invalid_rule_type | A rule type does not match the required naming format |
duplicate_rule_type | The same custom type appears more than once |
reserved_rule_type | A custom type conflicts with a built-in PII type |
invalid_regex | A regex cannot be compiled, exceeds 64 nested groups, or its load-time validation times out |
regex_matches_empty_text | A regex can produce a zero-length match on an empty string |
load_error | An unexpected loader error was handled in fail-open mode |
Security Events and Observability
Every scan follows the existing pii_scan Security Event path. Events contain the source, verdict,
summary, finding type, severity, category, span, and redacted evidence. They do not contain the
custom rules path, regex expressions, or raw sensitive matches.
Host hooks remain fail-open when custom rules are invalid and do not add a separate host warning.
The sanitized summary.custom_rules state in the Security Event is the structured audit source for
hook invocations.
Observability uses the existing trace context and input hash to correlate telemetry with the Security Event instead of storing another copy of finding details.