# Security.IsMasked — was this value withheld from me?

> A field a `deny read` withholds reads as its type's default — 0, false, DateTime.MinValue, the enum's zero; null for a nullable or reference type. `Security.IsMasked(p.Salary)` tells that stand-in from a real value: true when the member was withheld from the reader on that row. The same answer in a function, a page's slot and its action.

<!-- id: security-is-masked · area: security · stability: preview · html: https://osysharp.com/reference/security/is-masked/ -->

## Summary        {#summary}
A member a `deny read` withholds from the reader reads as its type's **default** — never the real value. For a
non-nullable value type that default is a real value of the type (`0`, `0m`, `false`, `DateTime.MinValue`, the enum's
zero), so the value alone cannot say whether it was withheld. Ask:

```osy syntax
var shown = Security.IsMasked(p.Salary) ? "hidden" : p.Salary.ToString("C");
```

## Signature      {#signature}
```osy syntax
bool Security.IsMasked(<row>.<Member>)   // the argument is a member read off an entity row
```

## Description    {#description}
**What a withheld member reads as.** `default(T)` of its declared type:

| declared | reads as when withheld |
|---|---|
| `int` `long` `double` `decimal` `bigdecimal` | `0` |
| `bool` | `false` |
| `TimeSpan` | `TimeSpan.Zero` |
| `DateTime` `DateOnly` `TimeOnly` | `MinValue` |
| `Guid` | `Guid.Empty` |
| an enum | its zero — `(E)0`, the first member of a positional enum |
| `string`, `T?`, a reference | `null` |

So `p.Salary + 1` is ordinary arithmetic for every reader, and nothing needs a null check the type says is impossible.
The real value never reaches the reader: the default is a constant of the TYPE, decided before the value is read.

**Everywhere the reader reads it.** In a function body, in a filter, an `OrderBy`, an aggregate and a projection of the
reader's own query (`Pay.Count(p => p.Salary == 0)` counts the rows whose salary is withheld from them, exactly as reading
each row would), in a page's slot and in its action. A `security { }` condition is the one place a member reads its real
value, because a rule judges the data as it is ([[security-entity-security#field-mask-in-queries]]).

**`Security.IsMasked(p.Salary)`** answers true when the rules withhold that member from the reader on that row — a
blanket `deny read Salary when !IsStaff`, or a rule decided per row (`deny read Commission where Owner != user`) — and
the value read is the stand-in. It is false:
- for a reader the rules do not cover;
- for a member this unit of work assigned, and every member of a row it created — that value is the reader's own;
- for a member the reader wrote and committed, which reads back what they wrote.

The argument must be a member read off a row — `Security.IsMasked(p.Salary)`, `Security.IsMasked(o.Customer.Credit)`.
A string naming the member is not accepted: the compiler checks the member exists.

**Writing it back is safe.** A reader who may write a member but not read it reads the stand-in, and writing that
stand-in (or a null) straight back changes nothing — the stored value survives. The consequence: such a reader cannot
set the member to exactly its default, since that write cannot be told from a write-back.

**In the browser.** A served row names the members withheld from its reader, and the page answers `IsMasked` from that,
in-process, with no round trip — the same answer the server gives.

**Not inside a query clause.** `Pay.Where(p => !Security.IsMasked(p.Salary))` is refused: read the rows, then ask of each.

## Examples       {#examples}
```osy title="a salary only staff may see, shown as 'hidden' to everybody else" test app=security-is-masked
[Principal] entity Member {
  [MaxLength(100)] string Name = "";
  security { allow read when IsAuthenticated; }
}

[Role] enum AppRole { Staff }
[RoleGrant] entity RoleGrant { Member Grantee; [Required] AppRole Level; }
policy IsStaff => RoleGrant.Any(g => g.Grantee == user && g.Level == AppRole.Staff);

entity Pay {
  [MaxLength(100)] string Label = "";
  decimal Salary;
  security {
    allow read when IsAuthenticated;
    deny read Salary when !IsStaff;   // everybody else reads 0
  }
}

/// What a person may be shown for one pay row.
string SalaryText(Pay p) {
  return Security.IsMasked(p.Salary) ? "hidden" : p.Salary.ToString("0.00");
}
```

## See also       {#see-also}
- [security { }](https://osysharp.com/reference/security/entity-security/) — `deny read` and the field mask that withholds the value
- [Security.MayRead — may THIS person read this row, under their own rules](https://osysharp.com/reference/security/may-read/) — the same question about a whole row, for another person
- [when is who, where is which rows](https://osysharp.com/reference/security/when-and-where/) — a mask decided for the reader, or per row
- [runas](https://osysharp.com/reference/testing/runas/) — test it as the reader the mask covers, and as one it does not
