Attribute-value masking proposal

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

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 •••••••• shown (read-only, no ✕).

Admin holds none of the values

Exactly 1 masked chip •••••••• shown (read-only). Operator and attribute are locked. If the operator is multi-value ("in"), the + button is available to add held values alongside. If the operator is single-value ("is", "is not", "contains", "starts with", "ends with"), the row is effectively fully read-only — admin can only delete the entire row.

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 (not pointer) — no interactive affordance

  • Tooltip: "One or more restricted values" (does not reveal count)

  • Aria-label: "Hidden values that you do not have permission to view" with role="img" and aria-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 Spacecraft and 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:

image-20260316-141926.png

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

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 access_mode

Rationale

Attribute source

Default access_mode

Rationale

Created within Mattermost (System Console)

shared_only

Secure-by-default for the masking feature to work out of the box

Created/managed by plugins

source_only

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 using source_only attributes 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

  1. "Advanced Configuration" in System Console UI — The entire section with the two settings (operator restrictions, CEL toggle) and their descriptions

  2. "Handling Pre-Existing Rules After Operator Restriction" — No longer needed; operators are unrestricted

  3. "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)

  4. "Warning Modal: Switching to All standard operators" — Eliminated

  5. "Warning Modal: Restricting Operators or CEL Editor (Unified)" — Eliminated

  6. "Mid-Edit Configuration Change" validation — Eliminated (no configuration to change mid-edit)

8.2 Sections Modified

  1. 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.

  2. 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.

  3. Scoping Rules & Constraints > Value autocomplete — Retained and simplified. No operator restriction validation needed.

  4. Scoping Rules & Constraints > Server-side validation — Operator restriction validation removed. Self-inclusion check and value-hold validation retained.

  5. Validation, Errors and Guardrails > Operator Restriction Validation — Removed.

  6. View System Policy — Updated from full suppression to masked rule display.

  7. Assumptions and Open Questions > Attribute Value Confidentiality — Updated to reference masking instead of restrictions.

  8. Decision Log — Entries 1–6, 8 superseded. New entries added.

8.3 System Console Settings Removed

Setting

Disposition

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

#

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 (access_mode: shared_only), eliminates 3 degraded states, 2 warning modals, and complex operator parsing. Security is enforced server-side — non-held values never reach the client.

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 •••••••• tokens in the expression).

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 access_mode: shared_only for Mattermost-created attributes

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

#

Assumption

Status

A1

Attribute values in defense/regulated environments are controlled information

Retained — this is the foundational assumption driving masking

A2

The PropertyAccessService (shared_only mode) correctly computes caller-target value intersection

New — depends on PR #34812/#34818 implementation correctness

A3

The API can indicate has_masked_values: true without revealing count or content

New — requires API contract extension

A4

Attributes used in ABAC policies default to shared_only access mode

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

#

Question

Owner

Impact

Q1

Does the current PropertyAccessService API response support a has_masked_values flag, or does this require an API extension?

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 access_mode per-attribute setting, should we add a UI in the Custom Profile Attributes section of System Console, or is this API/config-only for now?

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".