Skip to main content
Version: v5.6.1

7.1 Denied means not found; the denial event

Included from Basic tier

After this you can: explain why a denied request answers 404, and find the denial event the server records when someone is refused.

Before you start — tick each one:

  • Lesson 0.1 — Install and sign in (opschain learn 0.1)

In the GUI: a node you may not read is simply absent from the Estate tree; the Audit history screen under Governance finds the refusals when you search authz, and the dashboard's Insights tab plots them in the Security pulse panel.

Everything so far you did as a superuser. Real estates are run by people who hold exactly the access their job needs, and before you grant anyone anything (lesson 7.2) you need the two facts this lesson teaches: what a refusal looks like to the person refused, and what it looks like to you.

1. Denied means 404​

A resource you may not read answers 404, not 403. A denied node is indistinguishable from a missing one, so the API never leaks what exists — not the path, not the type, not whether anyone else may see it. The CLI renders it as the same error a typo would produce:

Error: the server answered HTTP 404: The requested resource could not be found

So when a user "can't find" a project that plainly exists, access is the first thing to check — that is what denial looks like. The GUI follows the same rule: a node you may not read is not greyed or locked, it is not there. (Entitlement is different: a screen whose pack the licence lacks shows locked and names the pack, because that is a fact about the licence, not about you — lesson 9.1.)

2. The denial event​

Invisible to the caller, visible to you: each refused request lands a warn:authz:denied event naming the refused procedure, the first denied rule path and action, and the caller's principal id and credential kind — never a username or email.

It is throttled to one event per refused procedure per five-minute window across the estate, so it is the evidence that denials are happening rather than a count of them (the metrics endpoint carries the volume). If the same refused lookup happened moments ago anywhere on the estate, a second refusal inside the window records nothing — a detail lesson 7.2's exercise relies on.

Read it with either feed:

opschain events --type-search authz --since 1h
WHEN ID TYPE NODE USER CORRELATION DETAIL
2026-10-15T04:14:02Z 019a0d7e… warn:authz:denied - - - NodeService/GetNode refused: readable on /projects/demoenterprise (principal 019a0d7e-…, session)

Note the empty USER column — the row carries the principal id in its payload, never the name.

opschain audit --type warn:authz:denied reads the same rows from the audit history with the source context joined; the GUI's Audit history search box matches the same way, and the dashboard's Security pulse panel plots the type as a line rather than a row.

Check yourself​

  • What event type does the server record for a refused request, and how many times does it record the same refused procedure inside five minutes?

What you learned​

  • A denied read answers 404, so the API never reveals what exists; the GUI simply omits the node.
  • Every refusal records warn:authz:denied with the procedure, the rule path and action, and the principal id — never a name.
  • The event is throttled to one per refused procedure per five minutes, estate-wide; it proves denials happen, the metrics count them.
  • opschain events --type-search authz, opschain audit --type warn:authz:denied, Audit history and the Security pulse all read it.

Next: lesson 7.2 — Users, groups, rules, policies (opschain learn 7.2), or back to your track (opschain learn).