Osy#the first language built for agents
Agents firstAgentic appsWorkflowsDurable Execution — built inSecurityTestingThe editorThe UI modelOne program

Reference / Security

secure by default (deny-all)

entity Ledger { string Entry; } // no security { } block → denied to every user request

Deny-all is the posture, and it is the only one: an entity that declares no `security { }` block is denied to every user request, and you grant access explicitly where you want it. There is no setting, no opt-out and no app that runs open — the one thing that genuinely has to run before anyone is signed in is declared as an auth-bootstrap. System, designer, and bootstrap contexts are never affected.

stable1 example compiled by CIsecurityauthorizationposture

Summary#

An entity with no security { } block is denied to every user request — access is granted only where you write it. This is the secure-by-default posture: you cannot forget to lock down a table, because an unmentioned table is already locked. It is not a setting you turn on and there is no way to turn it off, so there is nothing to write and nothing to remember: every app is deny-all, every entity starts closed, and the only question left is which access you grant back.

// Deny-all is the default — no security block → denied to every user request.
entity Ledger { string Entry; }

// Grant access back explicitly — anyone may read the catalog.
entity Product {
  string Name;
  security { allow read when IsAuthenticated || IsAnonymous; }
}

Signature#

entity Ledger { string Entry; }                              // no block  → denied to every user request
security { allow read when IsAuthenticated; }                // a block   → a LIST OF GRANTS, and the only way in

There is nothing app-level to write. The posture is a property of the language, not a configuration value, so it cannot be relaxed for an app, a file or an entity — the only lever is which grants you write in a security { } block. The one thing that genuinely has to run before anyone is signed in — login, signup, a password reset — is declared as an auth-bootstrap and runs under an identity you defined, on a leash of the grants you gave that role.

Description#

The posture changes only what a user request may touch. It does not change how the platform runs itself:

  • User requests (a logged-in or anonymous caller reading/writing app data) are subject to the posture. An entity with no security { } block is denied; an entity with a block is governed by exactly what that block grants.
  • System, designer, and bootstrap work (schema evolution, seeding, the metadata designer) is never affected — it does not run through a user's security context, so the posture never denies the platform's own operations.
  • Platform (osy.*) entities are exempt — their access is gated by other layers, so the posture never denies a built-in read.

Grant access back with ordinary security { } rules — allow read where Owner == user;, a role check, or one of the built-in principal predicates (IsAuthenticated / IsAnonymous) and open reads (allow read when IsAuthenticated, when IsAnonymous, or when IsAuthenticated || IsAnonymous for a genuinely public read).

  • Every allow must say WHO — a rule with no when and no where is a compile error. Deny-all decides what happens when you say nothing; this decides what happens when you open a block and then forget to qualify a line inside it. An unqualified allow grants that verb to every caller, anonymous included, and the runtime consults nothing — so it is not a weaker rule, it is the absence of one wearing the syntax of one, and it is worse than no block at all because deny-all would have caught the omission. It applies to all four verbs and to every app, whether or not it declares a [Principal].

    The remedy is never to remove the grant — it is to name who holds it:

    you meanyou write
    the owner of the rowallow update where Owner == user;
    anyone signed inallow read when IsAuthenticated;
    signed-out visitors (signup, a public form)allow create when IsAnonymous;
    genuinely everyoneallow read when IsAuthenticated \|\| IsAnonymous;

    That last row is supported and is the right answer for a store front — "everyone" is a legitimate decision, and spelling it out is the whole point. What is refused is leaving it unsaid. when true is a compile error for the same reason: it says yes without saying who, so a reader cannot tell a public surface from an unfinished one. The diagnostic names the form that does mean everyone.

  • An entity-level deny must say WHEN as well. "Denies more, never less" is the reasoning of an allow-by-default system; under deny-all there is nothing broader to narrow. A bare deny update; on an entity is either dead text — the verb was already denied — or, when a sibling allow names the same verb, a silent kill switch: an unconditional deny is checked FIRST, so it cancels the grant written above it. Both readings are worse than deleting the line, which is the same argument that refuses default deny;.

  • A FIELD-scoped deny needs no condition — except on a CREDENTIAL. deny read PasswordHash when !IsAuthenticator; subtracts one column from whatever the entity grants, because property rules REPLACE the entity's rather than layering on them — it is the only way to say "nobody, ever", and no when states it as cleanly.

  • A partial entity is exempt too, since narrowing a declaration made elsewhere is the whole reason it exists.

  • There is no way out, and that is the point. Deny-all holds for every app, always — so "we will turn security on before we ship" is not a state this platform can be in, and an app that appears to work is an app whose grants you actually wrote. The only sanctioned path past a locked table is the one that must exist: login and signup have to reach a user row before a principal exists, so you declare them in app.AuthBootstrap and the engine runs them as an ephemeral principal bearing a role you chose — which may touch exactly what your security { } blocks grant that role, and nothing else. See auth bootstrap (login, before anyone is signed in), and page authorization (policies) for page-level authorization, the request-time complement to entity-level deny-all.

Examples#

A public catalog an anonymous visitor may browse, alongside a private table only an owner sees — under the posture, the untouched Ledger is denied to all:

[Principal] entity User { string Name; }

entity Product {                                   // anonymous visitors may browse the catalog
  string Name;
  security { allow read when IsAnonymous || IsAuthenticated; }
}

entity Order {                                     // a buyer sees only their own orders
  User Buyer;
  string Item;
  security { allow read where Buyer == user; }
}

entity Ledger { string Entry; }                    // no security block → denied to everyone (the default)

See also#

Related

security { }

The rules that decide who may read and write an entity's rows. A where clause filters by the row (the owner sees their…

principal predicates (IsAuthenticated / IsAnonymous) and open reads

Two built-in `when` predicates say who a request is: `IsAuthenticated` is a signed-in user, `IsAnonymous` is an…

auth bootstrap (login, before anyone is signed in)

Under deny-all, login faces a paradox: it must read a user row *before* anyone is authenticated. `app.AuthBootstrap`…

page authorization (policies)

A `policy` names a reusable authorization predicate over the current user — e.g. `policy Admins => UserRole.Any(r =>…