Attribute-value masking proposal
Status: Draft Proposal — Spec Addendum
Date: 2026-03-13
Author: Abhijit Singh (AI-assisted)
Replaces: Advanced Configuration section (Operator Restrictions + CEL Editor Toggle for System Admins) in UX Spec: Team-Scoped Channel Access Policies
BLUF
Replace the operator restrictions and CEL editor toggle (System Console settings that control what team/channel admins can do) with attribute-value masking — a simpler, more secure, and less disruptive approach to preventing attribute value leakage to team and channel admins. Instead of suppressing entire rule sections or restricting which operators/editors admins can use, the system masks individual attribute values the admin does not hold while keeping the rule structure fully visible. The CEL editor remains available to team admins — only the System Console toggle controlling its availability is removed. This leverages the access_mode: shared_only capability already shipped in PR #34812.
1. Problem Recap
Team and Channel Admins authoring Membership Policies need to see and understand the rules governing channel access. However, the attribute values used in those rules (program names, clearance levels, organizational designators) are themselves controlled information in defense and regulated environments. An admin who holds Program = Dragon Spacecraft should not discover that Program = Falcon Heavy exists through the rule-authoring UI.
Current spec approach (being replaced): Two System Console toggles — one restricting operators to exact-match only, another allowing System Admins to disable the CEL editor for team/channel admins. When restricted, entire rule sections are suppressed. This creates 3 degraded UI states, 2 warning modals, and complex server-side operator parsing — all for a binary trust decision.
Proposed approach: Mask non-held values at the UI layer, powered by server-side shared_only filtering. The server never sends non-intersecting values to the client. All operators become safe. Rule structure remains visible.
2. How Masking Works
2.1 Server-Side Foundation: access_mode: shared_only
The PropertyAccessService (PR #34812) already implements value-level filtering:
When a caller requests attribute field values, the service computes the intersection between the caller's values and the target values.
Non-intersecting values are never sent to the client — this is not UI-level hiding but server-level omission.
The API response includes a flag indicating whether masked (omitted) values exist, enabling the UI to render the masked chip.
API contract for policy rule values:
{
"rule_id": "r1",
"attribute": "program",
"operator": "in",
"values": [
{ "value": "Dragon Spacecraft", "visible": true }
],
"has_masked_values": true
}The client receives only the values the caller holds. has_masked_values: true tells the UI to render the masked chip. The actual count and content of masked values are never transmitted.
2.2 UI Rendering Rules
Scenario | What the admin sees |
|---|---|
Admin holds all values in the condition | All value chips shown in cleartext. No masked chip. |
Admin holds some values | Held values shown as normal chips (with ✕ for removal). Exactly 1 masked chip |
Admin holds none of the values | Exactly 1 masked chip |
No values in the condition (empty) | Empty state — admin adds values normally. |
2.3 Masked Chip Specification
Visual: Dark gray background chip with
••••••••(8 bullet characters, fixed)Fixed length: Always 8 dots regardless of actual value count or character length — prevents both count-based and length-based inference
Read-only: No ✕ dismiss button, no hover actions, not draggable
Cursor:
default(notpointer) — no interactive affordanceTooltip: "One or more restricted values" (does not reveal count)
Aria-label: "Hidden values that you do not have permission to view" with
role="img"andaria-readonly="true"(Section 508 / WCAG 2.1 AA compliance)Position: Always rendered as the last chip in the row, after all visible values
2.4 Reference Mockup
Simple Editor:
In this example, the viewing admin holds
Program = Dragon Spacecraftand can see that value. They do not hold the other program value(s) in the condition, so those appear as a single masked chip. For Location, the admin does not hold the value used in the "is not" condition, so it appears masked.
Advanced Editor:
Option 1:
Option 2:
2.5 Masking in the Advanced (CEL) Editor
When a team admin views a policy in the Advanced (CEL) editor, masking applies to literal values within the CEL expression:
Held values appear as normal string literals:
user.program == "Dragon Spacecraft"Non-held values are replaced with a masked token:
user.program in ["Dragon Spacecraft", "••••••••"]The same single-masked-token rule applies: regardless of how many non-held values exist in a list, only one
"••••••••"token is shown.Masked tokens in the CEL expression are read-only — the editor must prevent the admin from modifying or deleting masked tokens. The simplest implementation: if the expression contains masked tokens, the CEL editor is read-only with an informational note: "This expression contains restricted values. Switch to Simple mode to edit the values you have access to, or delete the entire rule."
The admin can always switch to Simple mode, where standard chip-based masking applies.
3. Rule Editing Behavior
3.1 Adding Values
The autocomplete dropdown only suggests values the admin holds for the selected attribute.
Manually typed values that the admin does not hold are rejected on submit by server-side validation.
Error message: "Invalid value."
3.2 Removing Values
Visible (held) values: Can be removed via the ✕ on the chip. Removal is immediate in the editor (staged until save).
Masked values: Cannot be removed. The masked chip has no ✕ and no interactive affordance.
Entire rule row deletion: The admin can delete an entire rule row via the trash icon, even if it contains masked values. This removes the entire condition (attribute + operator + all values, both visible and masked). This is safe because: (a) the admin doesn't learn the masked values, and (b) rule row deletion is a structural edit the admin should have authority over for policies they manage.
3.3 Changing the Operator
The operator dropdown is locked (read-only) when the rule row contains any masked values.
This only occurs when an admin is editing a pre-existing policy with masked values — a reasonable constraint since the admin did not author the masked portion and should not alter its matching logic.
If the row contains no masked values (admin holds all values), the operator dropdown is fully editable as normal.
3.4 Changing the Attribute
The attribute dropdown is locked (read-only) when the rule row contains any masked values, for the same reason as the operator — changing the attribute would clear masked values the admin cannot see or reconstruct.
If the row contains no masked values, the attribute dropdown is fully editable.
4. Self-Inclusion Check (Updated)
The self-inclusion check remains enforced at load-time and save-time. Masking introduces one nuance:
4.1 Load-Time Filtering
Unchanged. A policy whose rules exclude the admin (based on the full server-side evaluation, including masked values) is not visible to that admin in the Membership Policies tab.
4.2 Save-Time Validation
When the admin clicks Save, the server evaluates the complete rule expression (including masked values the admin didn't modify) against the admin's attributes.
Admin satisfies the full expression: Save proceeds normally.
Admin does not satisfy the full expression: Save is blocked with the existing self-exclusion error: "You cannot save these rules because they would remove your access to this policy."
Key behavior: The self-inclusion check evaluates the entire expression as stored server-side, not just the visible portion. An admin who edits only visible values but whose changes (e.g., removing a held value from an "in" condition) cause self-exclusion will be blocked.
5. Test Rules (Updated)
The Test Rules button behavior changes based on whether masked values are present:
Policy state | Test Rules behavior |
|---|---|
No masked values in any rule row | Test Rules works normally — shows matching team members |
Any rule row contains masked values | Test Rules button is disabled with tooltip: "Test is unavailable because this policy contains restricted attribute values." |
Rationale: Test results would reveal which users satisfy the masked conditions, creating an indirect information disclosure vector. If the admin needs to test, they can request a System Admin to run the test from the System Console where all values are visible.
6. System Policy View (Updated)
The current spec fully suppresses rule details for system policies viewed by team admins. With masking, system policies now show masked rules instead:
6.1 What Changes
Before (current spec): Lock icon + "Access rules are restricted" + "Access rules can only be viewed or edited by a System Administrator"
After (masking): The rule table is displayed with the same masking behavior as team policies. Values the admin holds appear in cleartext; non-held values appear as masked chips.
6.2 System Policy Rule Display
Rules are shown in the Simple mode rule builder (read-only for system policies).
All fields remain non-editable (attribute, operator, values — all disabled).
Masked chips appear as they would in an editable policy.
The
+button for adding values and the trash delete row button are hidden (system policy is read-only).
6.3 Informational Banner (Updated)
The existing banner remains but the message adjusts:
System policy — read only
This policy is managed by a System Administrator and applies to channels across multiple teams. Rules are shown with restricted values masked.
6.4 Test Rules on System Policies
Test Rules button is always hidden for system policies (unchanged — team admin can't edit or test system policies).
7. access_mode Defaults and Configuration
7.1 Default Settings
Attribute source | Default | Rationale |
|---|---|---|
Created within Mattermost (System Console) |
| Secure-by-default for the masking feature to work out of the box |
Created/managed by plugins |
| Plugins manage their own data; most restrictive default for unknown modes (per PR #34812) |
7.2 Per-Attribute Override
System Admins can change the access_mode for any attribute in the Custom Profile Attributes settings:
shared_only— Values visible at the intersection (enables masking behavior for ABAC policies)source_only— Values visible only to the owning plugin (attributes with this mode used in policies will show as fully masked to non-plugin callers)Public (empty/unset) — All values visible to all callers (no masking; appropriate for non-sensitive attributes like department or timezone)
7.3 Impact on Policy Authoring
If an attribute used in a policy has access_mode set to:
shared_only: Standard masking behavior (intersection-based visibility)source_only: All values appear masked to team admins (since they're not the source plugin). The rule row shows attribute name + operator + masked chip only. This is an extreme case; the System Admin should be aware that usingsource_onlyattributes in policies effectively hides all values from non-plugin callers.Public: No masking — all values visible to all admins. Appropriate for non-sensitive attributes.
8. What This Removes from the Current Spec
8.1 Sections Removed Entirely
"Advanced Configuration" in System Console UI — The entire section with the two settings (operator restrictions, CEL toggle) and their descriptions
"Handling Pre-Existing Rules After Operator Restriction" — No longer needed; operators are unrestricted
"Handling Pre-Existing CEL Rules After CEL Editor Restriction" — No longer needed; the System Console toggle controlling CEL availability is removed (CEL remains available to team admins)
"Warning Modal: Switching to All standard operators" — Eliminated
"Warning Modal: Restricting Operators or CEL Editor (Unified)" — Eliminated
"Mid-Edit Configuration Change" validation — Eliminated (no configuration to change mid-edit)
8.2 Sections Modified
Create/Edit Policy > Access Rules — Updated to describe masking behavior instead of operator restrictions. All operators available. Both Simple and Advanced (CEL) editors remain available.
Create/Edit Policy > CEL editor references — References to the System Console toggle controlling CEL availability removed. The Simple/Advanced toggle remains in the policy editor UI.
Scoping Rules & Constraints > Value autocomplete — Retained and simplified. No operator restriction validation needed.
Scoping Rules & Constraints > Server-side validation — Operator restriction validation removed. Self-inclusion check and value-hold validation retained.
Validation, Errors and Guardrails > Operator Restriction Validation — Removed.
View System Policy — Updated from full suppression to masked rule display.
Assumptions and Open Questions > Attribute Value Confidentiality — Updated to reference masking instead of restrictions.
Decision Log — Entries 1–6, 8 superseded. New entries added.
8.3 System Console Settings Removed
Setting | Disposition |
|---|---|
"Operators allowed for team and channel admins" (radio: Exact-match only / All standard operators) | Removed. All operators permitted; masking handles security. |
"Allow team and channel admins to use the advanced rule editor (CEL)" (radio: True / False) | Removed. The System Console toggle is removed; CEL editor is always available to team/channel admins. Masking handles value confidentiality regardless of editor mode. |
9. Updated Decision Log
# | Decision | Rationale | Alternatives Considered |
|---|---|---|---|
1 | Attribute-value masking instead of operator restrictions and CEL toggle | Masking is granular (value-level vs. section-level), leverages existing server infrastructure ( | Operator restrictions (replaced: blunt instrument, complex to implement, creates degraded UX states). Full rule suppression (replaced: over-restrictive, admin loses situational awareness). |
2 | Exactly 1 masked chip per rule row regardless of hidden value count | Prevents count-based inference. If an admin sees "3 masked chips," they learn the condition contains 3 non-held values, which is itself information disclosure. A single chip reveals only the binary: "there exist non-held values." | Show actual count of masked values (rejected: count inference). Show no indication of masked values (rejected: confusing — admin wouldn't understand why rules behave differently than authored). |
3 | Fixed 8-dot mask regardless of value length | Prevents length-based inference. Variable-length masks would reveal approximate character counts of attribute values. 8 dots provides consistent visual weight and zero information leakage. | Variable-length mask matching value length (rejected: length inference). Placeholder text like "[hidden]" (rejected: less visually distinct from real values). |
4 | Masked values are read-only (no removal by admin) | Removing a masked value would alter policy intent set by another admin or System Admin without understanding what's being removed. Read-only masking preserves policy integrity. | Allow removal (rejected: admin could systematically strip masked values to narrow policy scope without understanding impact). |
5 | Disable Test Rules when masked values present | Test results revealing which users match masked conditions creates an indirect oracle for deducing attribute values through membership observation. | Allow testing with a warning (rejected: the information disclosure risk is the same regardless of warning). Show only users matching visible conditions (rejected: misleading — doesn't represent actual policy evaluation). |
6 | Remove System Console CEL toggle; keep CEL available to team admins | With masking handling security, the System Console toggle controlling CEL availability is unnecessary — value confidentiality is enforced server-side regardless of editor mode. Removing the toggle eliminates the degraded states when CEL is disabled. The CEL editor itself remains available since masking applies equally to CEL expressions (masked values rendered as | Drop CEL editor entirely for team admins (rejected: CEL provides legitimate operational value for complex AND/OR logic). Keep toggle (rejected: unnecessary complexity when masking handles security). |
7 | Show masked rules for system policies instead of full suppression | Consistent masking UX across policy types. Team admins gain partial visibility into system policy intent (which attributes matter) without learning sensitive values. Reduces the "black box" feeling of the current full-suppression design. | Full suppression (replaced: over-restrictive, inconsistent with team policy masking). |
8 | Default | Secure-by-default: masking works out of the box without requiring per-attribute configuration. Admins can relax to public for non-sensitive attributes. | Default to public (rejected: insecure by default). Default to source_only (rejected: too restrictive — breaks ABAC value visibility entirely). |
9 | Entire rule row can be deleted even with masked values | Row deletion is a structural operation (removes the entire condition). The admin doesn't learn masked values through deletion. Blocking row deletion would make policies unmanageable for team admins. | Block row deletion when masked values present (rejected: over-restrictive, forces System Admin involvement for routine policy management). |
10 | Lock operator and attribute dropdowns when masked values present | Changing operators on masked rows could corrupt rule logic (e.g., "in" to "is" with hidden multi-values). Changing attributes would clear masked values the admin can't reconstruct. Locking only affects pre-existing policies — not a constraint on new policy creation. | Allow compatible operator changes only (rejected: added complexity for marginal benefit). Allow all changes with warning (rejected: risk of silent rule corruption). |
11 | Generic "Invalid value." error for all write-path rejections | Error messages must not reveal whether a value exists in the system, exists but isn't held by the admin, or is malformed. Any distinction between these states enables value enumeration. | Descriptive errors like "Value not assigned to your profile" (rejected: confirms value exists). Different HTTP status codes per rejection type (rejected: enables enumeration via status code). |
10. Updated Assumptions
# | Assumption | Status |
|---|---|---|
A1 | Attribute values in defense/regulated environments are controlled information | Retained — this is the foundational assumption driving masking |
A2 | The | New — depends on PR #34812/#34818 implementation correctness |
A3 | The API can indicate | New — requires API contract extension |
A4 | Attributes used in ABAC policies default to | New — requires default configuration change |
A5 | Masking applies consistently in both Simple and Advanced (CEL) editors | New — CEL expressions with masked values must render masked tokens; validate with implementation |
A6 | Uniform rule suppression regardless of authorship | Superseded — masking replaces suppression entirely |
A7 | CEL-to-Simple conversion by server | Superseded — CEL remains available; no conversion needed |
11. Migration & Backward Compatibility
11.1 Existing Policies with Non-Exact Operators
No migration needed. Policies authored with operators like in, contains, starts with remain valid. With masking, these operators are safe and the policies display normally with masked non-held values.
11.2 Existing Policies Authored via CEL
No migration needed. The CEL editor remains available to team admins. Existing CEL-authored policies continue to display in the Advanced editor with masking applied to non-held values (rendered as masked tokens within the CEL expression). The "Handling Pre-Existing CEL Rules After CEL Editor Restriction" section is eliminated since there is no longer a restriction to handle.
11.3 Existing System Console Settings
The two Advanced Configuration settings are removed from the System Console. On upgrade:
"Operators allowed for team and channel admins" — setting ignored; all operators permitted. No database migration needed if the setting simply becomes a no-op.
"Allow team and channel admins to use the advanced rule editor (CEL)" — setting ignored; CEL always available to team admins. The toggle becomes a no-op.
12. Open Questions
# | Question | Owner | Impact |
|---|---|---|---|
Q1 | Does the current | Backend team | Blocks UI implementation |
Q2 | Should the policy list view (before opening a policy) show any indication that a policy contains masked values? E.g., a small icon on the policy row. | Design | Low — nice-to-have for admin awareness |
Q3 | When a team admin's attributes change (e.g., reassigned from a program), previously visible values in policies they authored may now appear masked. Should we notify the admin or just let the masking appear on next view? | PM | Medium — affects admin experience on attribute rotation |
Q4 | Should the Audit Log capture when a team admin saves changes to a policy that contains masked values they didn't modify? | Security | Medium — traceability for compliance |
Q5 | For the | PM + Design | Medium — affects admin workflow for configuring masking |
13. Critical Findings from Threat Model & Edge Case Analysis
The following items surfaced from the companion Threat Model and Edge Case Analysis and must be addressed in the spec:
13.1 Operator and Attribute Locked on Masked Rows
Problem: If a row contains masked values, allowing operator or attribute changes could corrupt rule logic the admin can't see (e.g., changing "in" to "is" when there are hidden multi-values, or changing the attribute and clearing masked values).
Decision: Lock both the operator and attribute dropdowns to read-only when the rule row contains any masked values. This only affects admins editing pre-existing policies with masked values — they can still add/remove their own visible values and delete the entire row.
13.2 Self-Inclusion Failure with Opaque Masked Conditions
Problem: When a masked "is not" condition excludes the admin, the self-inclusion check blocks save but the admin has no actionable information — they can't see what excludes them.
Recommendation: Error message: "You do not satisfy one or more conditions in this policy. Contact a System Admin for assistance." Add a "Request System Admin Review" action that sends a notification to System Admins with the policy ID.
13.3 Attribute Drift During Open Editor Session
Problem: If the admin's attributes change while the policy editor is open (e.g., reassigned from a program), previously visible chips could become stale.
Recommendation: Snapshot attribute holdings at editor open. Validate on save. If holdings changed, reject with: "Your attribute assignments have changed since opening this editor. Please close and reopen to see updated permissions." Do NOT live-update chips mid-edit — this is disorienting and could cause accidental policy corruption.
13.4 Screen Reader Accessibility (Section 508)
Problem: The •••••••• dots are meaningless to screen readers.
Spec requirement: Masked chips must have aria-label="Hidden values that you do not have permission to view", role="img", aria-readonly="true".