# deny on an inherited field

> A subtype may NARROW a field it inherits — `deny read Notes when !IsReviewer;`, `deny update Code except via Recode;` — and never widen one: an `allow` that names an inherited field is refused. The deny holds on the subtype's rows on every path, including a read written over the base type. A field follows the rules of the type that declares it, so a kit extending another kit decides its own fields and narrows the ones it inherits.

<!-- id: security-inherited-field-deny · area: security · stability: stable · html: https://osysharp.com/reference/security/inherited-field-deny/ -->

## Summary        {#summary}
A subtype carries every field of its base, and its own `security { }` may take some of them further away: a `deny`
naming an inherited field is a rule like any other field deny, decided on the subtype's rows. An `allow` naming an
inherited field is refused — permissions only narrow on the way down. What the base type grants is the floor of what
anyone gets; a subtype can say less, never more.

## Signature      {#signature}
```osy syntax
entity <Sub> : <Base> {
  security {
    deny read <InheritedField> [when <who>] [where <which rows>];
    deny update <InheritedField> [when <who>] [where <which rows>] [except via F];
    // allow … <InheritedField> …;   ✗ refused — only a deny may name an inherited field
  }
}
```

## Description    {#description}

### What does a deny on an inherited field do?   {#what}
Exactly what a deny on one of the subtype's own fields does, on the subtype's rows: `deny read` masks the column for
the callers and rows it names (the row still comes back, the value reads as its type's default — null, or `0` for a number; [Security.IsMasked — was this value withheld from me?](https://osysharp.com/reference/security/is-masked/)), and `deny update` refuses the write.
The base type's own rows are untouched — the rule is the subtype's.

It is decided on the server, on every path a row can be read or written by: a function, a client's unit of work, a
REST or MCP call, an import. **A read written over the base type is masked by each row's own type**, so
`Document.Where(…).Select(d => d.Notes)` returns null for a `Memo` row whose `Notes` the `Memo` type withholds, just
as `Memo.Where(…)` does.

```osy title="a memo's notes are for reviewers, and its title is fixed once filed" test app=inherited-field-deny
[Role] enum Duty { Reviewer, Clerk }
[RoleGrant] entity DutyGrant { User Grantee; [Required] Duty Level; }

[Principal] entity User {
  [Required, MaxLength(200)] string Email;
  security { allow read when IsAuthenticated; }
}

policy IsReviewer => DutyGrant.Any(g => g.Grantee == user && g.Level == Duty.Reviewer);

entity Document {
  [Required, MaxLength(200)] string Title;
  [MaxLength(2000)] string Notes = "";
  security { allow read, create, update when IsAuthenticated; }
}

entity Memo : Document {
  bool Filed = false;
  security {
    allow read, create, update when IsAuthenticated;
    deny read Notes when !IsReviewer;     // inherited from Document — narrowed here
    deny update Title where Filed;        // inherited too — fixed once the memo is filed
  }
}
```

### Why is an `allow` on an inherited field refused?   {#no-allow}
Because the subtype would be handing out a column of its base to callers the base never granted it to. The rule is
the one an organisation's policy sets: the base type is the floor, and everything below it can only narrow. So the
compiler refuses `allow update Title when IsClerk;` on `Memo` and says what to write instead — a `deny` that takes
`Title` from the callers who should not change it, while the subtype's entity-level rules (`allow update when …`)
already cover every field it inherits.

If a field genuinely needs different rules on the subtype, it is a different field: declare one of the subtype's own
and grant that.

### Does this work on a kit's type?   {#kit-base}
Yes, and that is where it matters most. An app's subtype of a kit's entity may deny a field the kit declares:

```osy title="an app narrows a kit's field on its own kind of account" syntax
entity Member : User {                       // User comes from the UserAccounts kit
  security {
    allow read when IsAuthenticated || IsAuthenticator;
    allow update when IsAuthenticated where Id == user.Id;
    deny update DisplayName when IsSuspended;   // the kit's field, narrowed for this app's members
  }
}
```

The kit's own rules still cap everything — its grants are a ceiling no subtype widens — so the deny can only ever take
something away from what the kit allowed.

### What happens when a kit extends another kit?   {#kit-on-kit}
**A field follows the rules of the type that declares it.** When kit B's `Bay` derives from kit A's `Shelf`:

- **kit A's fields stay kit A's.** Its rules (its own-row rule, say) decide them on every `Bay` row.
- **kit B's own fields follow kit B's rules** once kit B's block names them in an `allow update` rule (a `deny` naming one only narrows, and leaves it under kit A's rules) — exactly as an app's
  own fields on its subtype do ([[entity-inheritance#own-fields]]). A field kit B adds and does not name keeps kit A's
  rules. This holds on an app's subtype of `Bay` too.
- **kit B may deny a field it inherits from kit A**, and its `except via` names kit B's own functions — a kit vouches
  only for its own code.
- **kit B imports only what kit B's own lines name.** Everything `Bay` carries because `Shelf` declared it — kit A's
  rules, with the app's answers to kit A's policies, and its defaults, computed members and invariants — is read in
  kit A's file, with kit A's `using`s. Kit B writes `use Acme.Shelves;` and nothing for what those lines mention.

```osy title="kit B: its own field for the desk, and an inherited field only its function may change" syntax
namespace Acme.Bays;
using Acme.Shelves;

policy TagsBays;
policy RecodesBays;

public entity Bay : Shelf {              // Shelf: kit A's, with Label, Code and an own-row update rule
  [MaxLength(80)] string Tag = "";
  security {
    allow read, create when IsAuthenticated;
    allow update when IsAuthenticated where OwnerId == user.Id;
    allow update Tag when TagsBays;      // Bay's own field — Bay decides it
    deny update Code except via Recode;  // Shelf's field — Bay may only narrow it
  }
}

[Server, Authorize(RecodesBays)]
public void Recode(Bay b, string code) { b.Code = code; }
```

## See also       {#see-also}
- [entity Sub : Base](https://osysharp.com/reference/entity/inheritance/) — grants are never inherited, denies are; who decides a subtype's own fields
- [deny … except via](https://osysharp.com/reference/security/except-via/) — naming the only functions that write past a deny
- [[Authority]](https://osysharp.com/reference/security/contribution-authority/) — a kit's rules as the floor an app stands on
- [security { }](https://osysharp.com/reference/security/entity-security/) — the `security { }` block
