Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions reference/users-and-roles/overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,40 @@ 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.

<VersionBadge type="changed" version="v5.3.0" />

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The first affected release is v5.2.5, not v5.3.0. The tagged core sources establish that v5.2.4 lacks both the DDL/SQL allowlist enforcement and v5.2.5 contains them. Keeping this badge, with the corresponding warning at release-notes/v5-lincoln/5.3.md:35, leaves 5.2.4 -> 5.2.5 upgrades without an alert to list sql and DDL operations before access is revoked. Please badge this v5.2.5 and relocate the release-note warning to the 5.2.5 notes.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not seeing this — v5.2.4 and v5.2.5 are byte-identical on both files that would carry the enforcement:

$ git diff --stat v5.2.4 v5.2.5 -- utility/operation_authorization.ts \
    server/serverHelpers/serverUtilities.ts
(no output)

And the enforcement is absent from the whole 5.2 line, not just 5.2.4. At v5.2.13:

  • verifyOperationsAllowlist does not exist at any 5.2.x tag (v5.2.3 through v5.2.13 all absent).
  • The SQL path calls sql.checkASTPermissions(json, parsedSqlObject) with no allowlist consultation — expandOperationsPerms appears zero times in serverUtilities.ts.
  • In verifyPerms, the structure_user carve-out is still at line 604 and the operations gate at line 628, so DDL continues to bypass the list.

The change is harper#2176, merged to main 2026-08-21 and tagged only in v5.3.0-alpha.1. I checked for a back-port by file content rather than ancestry, since a cherry-pick would defeat git tag --contains — that is the failure mode your reading would fit, but the files show no cherry-pick happened.

So a 5.2.4 to 5.2.5 upgrade revokes nothing, and a warning in the 5.2.5 notes would tell operators to change roles for a tightening their release does not contain. Keeping the badge at v5.3.0. Happy to look again if you have a specific 5.2.5 behavior in mind that I am reading past.

🤖 Addressed by Claude Code


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.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

structure_user schema operations remain an exception in MCP discovery: both visibility helpers advertise them whenever structure_user is truthy before consulting permission.operations. A role such as { structure_user: ['orders_db'], operations: ['sql'] } therefore lists create/drop MCP tools but rejects every invocation. That conflicts with this stated invariant and with reference/mcp/tools-and-resources.md:46-50, which promises that discovery shows only callable operations. Please fix the root cause in core by making both visibility helpers require the operation allowlist too, rather than documenting around the discrepancy.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Confirmed, and it reproduces exactly as you describe. Both helpers short-circuit on structure_user truthiness before reaching the list:

// components/mcp/toolRegistry.ts:413 — canRoleInvokeOperation
if (perm.structure_user && SCHEMA_STRUCTURE_OPERATIONS.has(operation)) return true;
if (Array.isArray(perm.operations) && perm.operations.includes(operation)) return true;

components/mcp/resources.ts:918 is the same two lines. SCHEMA_STRUCTURE_OPERATIONS holds all eight create/drop schema, table and attribute ops, and an array structure_user is truthy, so a role of { structure_user: ["orders_db"], operations: ["sql"] } advertises all eight and gate one denies all eight at dispatch.

One correction to the framing: this does not conflict with the invariant on this page. The sentence here is that an omitted operation is denied — which is what happens, as your own "rejects every invocation" confirms. canRoleInvokeOperation is a discovery filter, and its docblock is explicit that it is "intentionally conservative" and defers per-target checks to verifyPerms. What #2176 broke is that the deferral is no longer only about per-target predicates, so the filter is now wrong at the role level too.

The promise it does contradict is on the other page — reference/mcp/tools-and-resources.md says tools/list is filtered "so each session sees only the operations its user can actually call". Note the bullet just below it currently reads that a structure_user sees schema ops "in addition to anything in permission.operations", which documents the over-advertising as intended, so that page needs a pass either way once core changes.

On the fix itself: that is a core change in HarperFast/harper, outside what a docs PR can carry, so I am not making it here and have surfaced it to @dawsontoth rather than acting on it myself. Worth its own issue — the invocation side is already correct and fails closed, so this is a discovery-accuracy bug rather than an access one.

🤖 Addressed by Claude Code

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@kriszyp Would you consider this to block this PR?


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:

```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; 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**

Groups expand to a predefined set of operations and can be mixed with individual operation names:
Expand Down
11 changes: 11 additions & 0 deletions release-notes/v5-lincoln/5.3.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,17 @@ 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 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 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

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`.
Expand Down
Loading