From c4c697f1ec8ec20f228679d3db92f17800e5a7dc Mon Sep 17 00:00:00 2001 From: Dawson Toth Date: Tue, 18 Aug 2026 12:07:20 -0400 Subject: [PATCH 1/4] docs(roles): state the limits of the operations allowlist MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit harper#2176 moved gate one ahead of every other privilege check on the role, so the two limits this page originally described no longer hold: the structure_user carve-out and the SQL path both consulted the allowlist as of that change. Documents the 5.3.0 semantics instead — structure_user narrows which databases listed DDL reaches rather than widening which operations are reachable, and sql must be listed for a role to run SQL at all. The one limit that survives is that super_user/cluster_user roles cannot carry an allowlist: add_role and alter_role reject either flag set to true alongside other keys, so a 'restricted super user' is a validation error rather than a narrowed administrator. Drops the earlier claim about what authorization does with such a role, which described a state validation will not store and which #2176 inverted. Also notes that a non-array value can wedge the user-cache load (harper#2194), and adds the 5.3 release note, since the tightening revokes access from roles that relied on either bypass. Co-Authored-By: Claude Fable 5 Co-Authored-By: Claude Opus 5 --- reference/users-and-roles/overview.md | 8 ++++++++ release-notes/v5-lincoln/5.3.md | 6 ++++++ 2 files changed, 14 insertions(+) diff --git a/reference/users-and-roles/overview.md b/reference/users-and-roles/overview.md index bdfdd0a7..c5cc8592 100644 --- a/reference/users-and-roles/overview.md +++ b/reference/users-and-roles/overview.md @@ -79,6 +79,14 @@ The `operations` field in a permission object restricts which Operations API cal Operations normally restricted to `super_user` can be selectively granted by including them in the list. If `operations` is not set, the role can call any non-`super_user` operation, subject to table CRUD permissions. +Gate one runs ahead of every other privilege check on the role. Before v5.3.0 the `structure_user` carve-out and the SQL path were both reached without consulting the list, so a role could call operations its list omitted; both are now gated. Three consequences are worth stating directly, because the field scopes an ordinary role and is not a way to narrow an administrator: + +- **`super_user` and `cluster_user` roles cannot carry an allowlist at all.** `add_role` and `alter_role` reject any permission that sets either flag to `true` alongside other keys, so the combination is a validation error rather than a restricted administrator. Grant the operations you want to delegate to an ordinary role instead. +- **`structure_user` narrows which databases DDL reaches; it does not widen which operations are reachable.** `create_table`, `create_attribute`, `drop_table`, and `drop_attribute` must appear in the list like anything else, and `structure_user` then restricts them to its named databases, or to every database when it is `true`. `create_database` and `drop_database` additionally require `structure_user: true`. A role whose list omits an operation cannot perform it whatever `structure_user` is set to. +- **`sql` must be listed for a role to run SQL.** Listing it grants the SQL interface, not unrestricted DML through it: statements are still authorized against table CRUD permissions, which is what separates `read_only` (no writes) from `standard_user`. Both groups include `sql`, so a role built from either keeps it. + +The value must be an array of strings, and a non-array value is rejected on write. A role that already holds one — for example a pre-5.0 role that granted a database named `operations`, before the key became reserved — can prevent the instance from loading its user cache; see [HarperFast/harper#2194](https://github.com/HarperFast/harper/issues/2194). + **Permission Groups** Groups expand to a predefined set of operations and can be mixed with individual operation names: diff --git a/release-notes/v5-lincoln/5.3.md b/release-notes/v5-lincoln/5.3.md index 1dc7e574..d0fd4c30 100644 --- a/release-notes/v5-lincoln/5.3.md +++ b/release-notes/v5-lincoln/5.3.md @@ -32,6 +32,12 @@ See [Configuration Options](/reference/v5/configuration/options#node). Harper now defers rejection of an unrecognized Basic or Bearer credential on the application HTTP port until a handler claims the request. Harper-owned routes still return the normal unauthorized response. Unowned routes can fall through to application middleware with the original `Authorization` header unchanged. This lets catch-all applications and reverse proxies apply their own authentication schemes without URL exemptions or header rewriting, and the unauthorized response such an application returns — including its own `WWW-Authenticate` challenge — is passed back to the client unchanged. Internal authentication failures continue to fail closed. See [Authentication and route ownership](/reference/v5/security/overview#authentication-and-route-ownership). +### Operation Allowlist Enforcement + +A role's `permission.operations` allowlist is now consulted before every other privilege check on the role, so an operation the list omits is denied whatever else the role carries. Two paths previously ran ahead of it: the `structure_user` carve-out, which made `create_table`, `create_attribute`, `drop_table`, and `drop_attribute` reachable regardless of the list, and the SQL path, which never consulted the list at all — so a role with an allowlist omitting `sql` could still run SQL against whatever its table permissions reached. + +**A role that relied on either path will lose access on upgrade.** Audit roles that combine `operations` with `structure_user`, and roles whose `operations` list omits `sql`: add the DDL operations and `sql` to the list where the access was intended. Roles with no `operations` field are unaffected, and so are roles that list everything they call. `structure_user` keeps its database-scoping role — it now narrows which databases listed DDL reaches rather than widening which operations are reachable. See [Operation Permissions](/reference/v5/users-and-roles/overview#operation-permissions). + ### OIDC Trusted Publishing A CI runner can now authenticate to Harper with no stored credential. It presents an identity token minted by its own provider, and if that token verifies against a trust policy configured on the instance, Harper returns a one-hour operation token for the user the policy names — the same exchange npm, PyPI, and AWS STS `AssumeRoleWithWebIdentity` use. This replaces a 30-day refresh-token secret with a rule you configure once and revoke with `drop_oidc_trust`. From ac0376c6036509f6788a97cc862c8dcb9bdb5b32 Mon Sep 17 00:00:00 2001 From: Dawson Toth Date: Thu, 17 Sep 2026 11:23:42 -0400 Subject: [PATCH 2/4] Move the changed-in badge onto its own line Matches the shape reference/database/compaction.md uses for the same kind of note: the badge on its own line, then a sentence naming what changed and an "earlier v5 releases" clause for the prior behavior. The badge was sitting mid-sentence immediately before a literal "Before v5.3.0", which rendered as "Changed in: v5.3.0 Before v5.3.0 ...". Co-Authored-By: Claude Opus 5 --- reference/users-and-roles/overview.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/reference/users-and-roles/overview.md b/reference/users-and-roles/overview.md index c5cc8592..4f77029b 100644 --- a/reference/users-and-roles/overview.md +++ b/reference/users-and-roles/overview.md @@ -79,7 +79,11 @@ The `operations` field in a permission object restricts which Operations API cal Operations normally restricted to `super_user` can be selectively granted by including them in the list. If `operations` is not set, the role can call any non-`super_user` operation, subject to table CRUD permissions. -Gate one runs ahead of every other privilege check on the role. Before v5.3.0 the `structure_user` carve-out and the SQL path were both reached without consulting the list, so a role could call operations its list omitted; both are now gated. Three consequences are worth stating directly, because the field scopes an ordinary role and is not a way to narrow an administrator: + + +Gate one runs ahead of every other privilege check on the role. Earlier v5 releases reached the `structure_user` carve-out and the SQL path without consulting the list, so a role could call operations its list omitted; both are now gated. + +Three consequences are worth stating directly, because the field scopes an ordinary role and is not a way to narrow an administrator: - **`super_user` and `cluster_user` roles cannot carry an allowlist at all.** `add_role` and `alter_role` reject any permission that sets either flag to `true` alongside other keys, so the combination is a validation error rather than a restricted administrator. Grant the operations you want to delegate to an ordinary role instead. - **`structure_user` narrows which databases DDL reaches; it does not widen which operations are reachable.** `create_table`, `create_attribute`, `drop_table`, and `drop_attribute` must appear in the list like anything else, and `structure_user` then restricts them to its named databases, or to every database when it is `true`. `create_database` and `drop_database` additionally require `structure_user: true`. A role whose list omits an operation cannot perform it whatever `structure_user` is set to. From 4a0675e895f77dc1c4cae6d14b4b12ec444a726e Mon Sep 17 00:00:00 2001 From: Dawson Toth Date: Thu, 17 Sep 2026 11:27:11 -0400 Subject: [PATCH 3/4] Show the allowlist limits in a role instead of describing them MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The three bullets explained gate ordering and which path bypassed what — the mechanism, not the task. A developer arriving here is writing a role and wants to know what to put in the list. Replaces them with one role that exercises all three limits (sql listed, DDL listed, structure_user scoping the DDL to one database) and two sentences of annotation. The super_user limit becomes the reason to build a role up rather than a bullet about validation. Same for the release note: what broke and the two cases to audit, rather than a narrative of the ordering change. Verified while rewriting that listing `sql` alone admits any statement variant on a direct SQL call — the allowlist checks `sql`, and the statement is then authorized against table permissions (harper DESIGN.md:2536), so the example can show SELECT/INSERT reaching only what the table grants. Co-Authored-By: Claude Opus 5 --- reference/users-and-roles/overview.md | 34 ++++++++++++++++++++++----- release-notes/v5-lincoln/5.3.md | 9 +++++-- 2 files changed, 35 insertions(+), 8 deletions(-) diff --git a/reference/users-and-roles/overview.md b/reference/users-and-roles/overview.md index 4f77029b..f1d5c1f4 100644 --- a/reference/users-and-roles/overview.md +++ b/reference/users-and-roles/overview.md @@ -81,15 +81,37 @@ Operations normally restricted to `super_user` can be selectively granted by inc -Gate one runs ahead of every other privilege check on the role. Earlier v5 releases reached the `structure_user` carve-out and the SQL path without consulting the list, so a role could call operations its list omitted; both are now gated. +Gate one admits nothing the list omits, whatever else the role carries. Earlier v5 releases let table DDL and SQL around it; both now go through it. -Three consequences are worth stating directly, because the field scopes an ordinary role and is not a way to narrow an administrator: +So grant by listing, and build a role up rather than trying to narrow `super_user` — `add_role` and `alter_role` reject `super_user` or `cluster_user` set to `true` alongside any other key. A role that maintains one database's tables and queries them with SQL: -- **`super_user` and `cluster_user` roles cannot carry an allowlist at all.** `add_role` and `alter_role` reject any permission that sets either flag to `true` alongside other keys, so the combination is a validation error rather than a restricted administrator. Grant the operations you want to delegate to an ordinary role instead. -- **`structure_user` narrows which databases DDL reaches; it does not widen which operations are reachable.** `create_table`, `create_attribute`, `drop_table`, and `drop_attribute` must appear in the list like anything else, and `structure_user` then restricts them to its named databases, or to every database when it is `true`. `create_database` and `drop_database` additionally require `structure_user: true`. A role whose list omits an operation cannot perform it whatever `structure_user` is set to. -- **`sql` must be listed for a role to run SQL.** Listing it grants the SQL interface, not unrestricted DML through it: statements are still authorized against table CRUD permissions, which is what separates `read_only` (no writes) from `standard_user`. Both groups include `sql`, so a role built from either keeps it. +```json +{ + "operation": "add_role", + "role": "orders_maintainer", + "permission": { + "operations": ["sql", "create_table", "drop_table"], + "structure_user": ["orders_db"], + "orders_db": { + "tables": { + "orders": { + "read": true, + "insert": true, + "update": false, + "delete": false, + "attribute_permissions": [] + } + } + } + } +} +``` + +`sql` has to be listed or the role cannot run SQL at all. Listing it grants the interface, not the data: the statement is still checked against the table permissions above, so this role can `SELECT` and `INSERT` on `orders` and nothing else. That check is what separates the `read_only` and `standard_user` groups below, which both include `sql`. + +`create_table` and `drop_table` have to be listed too, and `structure_user` then limits them to `orders_db`. `create_database` and `drop_database` additionally require `structure_user: true`. -The value must be an array of strings, and a non-array value is rejected on write. A role that already holds one — for example a pre-5.0 role that granted a database named `operations`, before the key became reserved — can prevent the instance from loading its user cache; see [HarperFast/harper#2194](https://github.com/HarperFast/harper/issues/2194). +The value must be an array of strings; a non-array value is rejected on write. A role that already holds one — a pre-5.0 role that granted a database named `operations`, before the key became reserved — can stop the instance loading its user cache ([harper#2194](https://github.com/HarperFast/harper/issues/2194)). **Permission Groups** diff --git a/release-notes/v5-lincoln/5.3.md b/release-notes/v5-lincoln/5.3.md index d0fd4c30..627ad222 100644 --- a/release-notes/v5-lincoln/5.3.md +++ b/release-notes/v5-lincoln/5.3.md @@ -34,9 +34,14 @@ Harper now defers rejection of an unrecognized Basic or Bearer credential on the ### Operation Allowlist Enforcement -A role's `permission.operations` allowlist is now consulted before every other privilege check on the role, so an operation the list omits is denied whatever else the role carries. Two paths previously ran ahead of it: the `structure_user` carve-out, which made `create_table`, `create_attribute`, `drop_table`, and `drop_attribute` reachable regardless of the list, and the SQL path, which never consulted the list at all — so a role with an allowlist omitting `sql` could still run SQL against whatever its table permissions reached. +A role's `permission.operations` allowlist is now checked before every other privilege check on the role, so an operation the list omits is denied whatever else the role carries. Table DDL and SQL previously went around it. -**A role that relied on either path will lose access on upgrade.** Audit roles that combine `operations` with `structure_user`, and roles whose `operations` list omits `sql`: add the DDL operations and `sql` to the list where the access was intended. Roles with no `operations` field are unaffected, and so are roles that list everything they call. `structure_user` keeps its database-scoping role — it now narrows which databases listed DDL reaches rather than widening which operations are reachable. See [Operation Permissions](/reference/v5/users-and-roles/overview#operation-permissions). +**A role that relied on either will lose access on upgrade.** Two cases to audit, both fixed by adding the operation to the list: + +- `operations` combined with `structure_user` — `create_table`, `create_attribute`, `drop_table`, and `drop_attribute` now have to be listed. `structure_user` still scopes them to its databases. +- `operations` omitting `sql` — the role can no longer run SQL. + +Roles with no `operations` field are unaffected, as are roles that already list everything they call. See [Operation Permissions](/reference/v5/users-and-roles/overview#operation-permissions). ### OIDC Trusted Publishing From f5e6ad77e3474a09fab2659680624142be72b73c Mon Sep 17 00:00:00 2001 From: Dawson Toth Date: Mon, 21 Sep 2026 08:01:23 -0400 Subject: [PATCH 4/4] Drop the gate-one label from the sentence stating the rule MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Review nit from @ldt1996. The sentence read "Gate one admits nothing the list omits", which makes the reader carry the numbering from the two-gate list above into the rule itself. States the rule directly instead, and says where the check happens rather than which numbered gate it is — also matching the wording the 5.3 release note already uses. Co-Authored-By: Claude Opus 5 --- reference/users-and-roles/overview.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/reference/users-and-roles/overview.md b/reference/users-and-roles/overview.md index f1d5c1f4..bf73a0b2 100644 --- a/reference/users-and-roles/overview.md +++ b/reference/users-and-roles/overview.md @@ -81,7 +81,7 @@ Operations normally restricted to `super_user` can be selectively granted by inc -Gate one admits nothing the list omits, whatever else the role carries. Earlier v5 releases let table DDL and SQL around it; both now go through it. +An operation the list omits is denied whatever else the role carries — the list is checked ahead of every other permission on the role. Earlier v5 releases let table DDL and SQL around it; both now go through it. So grant by listing, and build a role up rather than trying to narrow `super_user` — `add_role` and `alter_role` reject `super_user` or `cluster_user` set to `true` alongside any other key. A role that maintains one database's tables and queries them with SQL: