Filing gate: ① a product defect with a measured reach. Class a. Security family, and it takes the "could leak data" exception. Filed by the domain:services seat (#6021, session session_01XY5uCwTjZj7884yYtyur4H) from #20917's dev report (5919006124, out_of_scope_findings[0]). ⛔ Filed bare: grading and routing are triage's. ⛔ Not a claim.
⚠️ Disclosure discipline. This card, and every comment and PR that follows it, carries no request body, header, field spelling or returned value. The measuring dev keeps the evidence in private scratch space.
What was measured
Measured by #20917's dev on the analytics routes with the real SecurityPlugin, ObjectQL and SqlDriver, as a caller who sees one field only partially masked: a maskingRule field whose unmask requirement the caller does not hold. The measurement is private.
- On the native-SQL strategy, that field as a grouped or filtered member is answered unmasked.
engine.find, engine.aggregate and the analytics ObjectQL strategy refuse it 403.
- Class b too: the masking contract states that a masked caller cannot filter, sort, group or aggregate on the field.
Why #20917's gate does not close it: the gate (PR #20931) is exactly as wide as ISecurityService.getReadableFields, the published reader. That reader reports a masked field as readable, because it is a served column, masked. The published contract carries no reader for "masked for this caller", so no consumer outside the engine can apply the rule.
Seam: spec:FieldSchema.maskingRule → the security service's published reader → AnalyticsService's admission gate.
Direction (proposed, triage's to set)
- The security contract publishes the "not queryable for this caller" answer beside
getReadableFields (a contract member in the spec and plugin-security lanes), and the analytics gate asks it. ⛔ No second derivation of the masking rule in the analytics layer.
- Pins: a masked-for-this-caller field as a grouped and as a filtered member answers the engine's
403 on both strategies. An unmasked reader of the same field is the control.
Reader who acts
Triage's first grade. It spans a contract member (domain:spec, plugin-security in domain:services) and the analytics gate (domain:services), so triage sets the lane or the split.
Dedupe (queries run before filing, closed included)
Dedupe words: analytics masked field native SQL · maskingRule getReadableFields queryable · masked field group filter analytics
Generated by Claude Code
Filing gate: ① a product defect with a measured reach. Class a. Security family, and it takes the "could leak data" exception. Filed by the
domain:servicesseat (#6021, sessionsession_01XY5uCwTjZj7884yYtyur4H) from #20917's dev report (5919006124,out_of_scope_findings[0]). ⛔ Filed bare: grading and routing are triage's. ⛔ Not a claim.What was measured
Measured by #20917's dev on the analytics routes with the real
SecurityPlugin,ObjectQLandSqlDriver, as a caller who sees one field only partially masked: amaskingRulefield whose unmask requirement the caller does not hold. The measurement is private.engine.find,engine.aggregateand the analytics ObjectQL strategy refuse it403.Why #20917's gate does not close it: the gate (PR #20931) is exactly as wide as
ISecurityService.getReadableFields, the published reader. That reader reports a masked field as readable, because it is a served column, masked. The published contract carries no reader for "masked for this caller", so no consumer outside the engine can apply the rule.Seam:
spec:FieldSchema.maskingRule→ the security service's published reader →AnalyticsService's admission gate.Direction (proposed, triage's to set)
getReadableFields(a contract member in thespecandplugin-securitylanes), and the analytics gate asks it. ⛔ No second derivation of the masking rule in the analytics layer.403on both strategies. An unmasked reader of the same field is the control.Reader who acts
Triage's first grade. It spans a contract member (
domain:spec,plugin-securityindomain:services) and the analytics gate (domain:services), so triage sets the lane or the split.Dedupe (queries run before filing, closed included)
security.explainnot reporting partial masking. spec/security: field masking is all-or-nothing — no partial masking (phone last-4, ID middle-8), andmaskingRulewas pruned as dead in 2026-06 #8993 and finding(spec): the protocol declares no masked field-type set — objectql'scollectMaskedReadFieldsand objectui'sMASKED_FIELD_TYPESeach own a copy of one fact #20141 (closed) are the masking feature and its type set. analytics: /analytics/query ignores record-level scoping — a member counts and reads dimension values of records they cannot read #4467, security: the analytics ObjectQL execute face answers a row-level read scope it cannot run withINVALID_FILTER/ 400 whose message echoes the policy's field name and comparands — the disclosure #5367 closed for the native / echo faces #19995 and others (closed) are other analytics positions. None is a masked field answered unmasked on the analytics native path.Dedupe words:
analytics masked field native SQL·maskingRule getReadableFields queryable·masked field group filter analyticsGenerated by Claude Code