Task · GCI-0018

Detect PII and unbounded-cardinality label patterns as a risk finding

Status
Parked
Priority
high
Labels
risk, privacy, cardinality
Updated
2026-09-11

Description

A signal-label sweep across a real estate found an agent CLI exporting a per-person email address as a METRIC LABEL. That is two defects in one series: personal identity in a metrics store that has no access model for it, and an unbounded-cardinality label that grows with headcount. Neither is visible anywhere in this platform today, and both are exactly the kind of finding the risk surface exists to raise.

The signal-label inventory added for Pillar K already reads label NAMES per stack. Detecting these patterns is therefore nearly free - it is a classification pass over data the collector now holds, not a new read.

What to detect

Identity-bearing label NAMES. Match the label key, never the value, against a versioned pattern set. Candidates, all generic and none derived from any one estate:

email, e_mail, mail, user_email, useremail, owner_email
user, username, user_name, user_id, userid, login, account, account_name
person, employee, employee_id, staff_id, upn, principal, subject
full_name, first_name, last_name, given_name, surname, display_name
phone, mobile, msisdn, telephone
ip, ip_address, client_ip, remote_addr, source_ip, x_forwarded_for
session, session_id, cookie, token, api_key, authorization, secret, password
patient, patient_id, mrn, nhs_number, ssn, national_id, dob, date_of_birth
customer_email, customer_name, tenant_email

Unbounded-cardinality label names, a separate and overlapping class: uuid, guid, trace_id, span_id, request_id, correlation_id, run_id, job_id, build_id, pod_template_hash, container_id, task_id, pid, timestamp, epoch, url, path, query, full_path, endpoint where unparameterised.

Absolute rules

Where it lands

Pillar E, risk and hygiene, alongside the existing public-dashboard and access-policy findings. It is the same shape: an inventory the org almost certainly cannot see, surfaced with its measured-stack denominator so an unreadable stack is never a clean zero.

New output:

Why it is worth the space

An estate owner cannot grep their own label space at this scale, and the two consequences land on different teams: the privacy exposure is a compliance conversation and the cardinality is a cost conversation. One inventory serves both. It also strengthens the coverage surface indirectly, because a label carrying per-person identity is a strong hint that a service identity is machine-generated rather than a real application.

Acceptance Criteria

Definition of Done

Implementation Plan

Implement the versioned generic label-name pattern data and pure classifier with focused tests, then integrate its bounded metrics, view, dashboard and hydration wiring before security review.

Implementation Notes

Wave 1 source-contract audit found no generic label-name payload in collector/sources/signal_inventory.py. The source currently exposes metric names plus selected label values used for service and technology matching. Implementing the classifier now would either inspect values contrary to the privacy contract or invent data the collector does not gather. Resume after deciding and implementing a key-only, all-signal label-name source contract with explicit minimisation and retention behavior.

CORRECTION, 2026-09-11 wave 1 closeout. The wave 1 audit finding above is WRONG as stated. It checked only collector/sources/signal_inventory.py. A key-only label-name payload DOES already exist: collector/sources/dataplane.py cardinality() calls Mimir /api/prom/api/v1/cardinality/label_names and returns top_labels, a list of the highest-cardinality label NAMES per stack with their value counts and no values. It already reaches the scan envelope, Loki and S3, and that file’s own comment records the privacy decision for it. collector/pillars/cost.py cost_cardinality_outliers publishes only top_labels[0] as Worst label and discards the other nineteen names.

What is genuinely missing is narrower than the note claimed: the payload is Mimir only, and it is the top N by cardinality rather than a complete label-name set. That is a good input for the unbounded-cardinality class and a poor one for the PII class, because a low-cardinality identity label such as an owner slug never enters the top N.

This task is therefore split. The unbounded-cardinality half is commissioned as GCI-0018.01 over the existing payload with no new source contract. This parent stays Parked for the identity-bearing half only, and resumes after an all-signal key-only label-name source contract exists across Mimir, Loki, Tempo and Pyroscope.

Final Summary

No product implementation was accepted. A draft classifier test was archived under codex/backups and excluded from the product tree. The task is parked at the missing key-only label-name source contract; no acceptance criterion is claimed.

View the source file on GitHub