Skip to content

feat(permissions): apply the operations allowlist to table read/write gating - #1631

Merged
dawsontoth merged 5 commits into
stagefrom
permission-hooks-operations-allowlist
Aug 18, 2026
Merged

feat(permissions): apply the operations allowlist to table read/write gating#1631
dawsontoth merged 5 commits into
stagefrom
permission-hooks-operations-allowlist

Conversation

@dawsontoth

@dawsontoth dawsontoth commented Aug 17, 2026

Copy link
Copy Markdown
Contributor

Studio's table permission checks ignored permission.operations, so a role whose allowlist excludes a
data operation still saw Add Records, Import Data, and the inline edit/delete affordances — each of
which the server denies at gate 1, before the role's table CRUD grants are consulted.

checkSchemaTablePermission and useInstanceSchemaTableAttributePermission now consult the allowlist
first, through a new pure src/hooks/checkOperationPermission.ts.

Stacks on #1628 (base role-operations-allowlist), which added the catalog and the roles-UI editor.

For the human reviewer

Read checkOperationPermission.ts first — its header states the
invariant the whole change rests on — then checkSchemaTablePermission.ts
for the gate order, then the call sites.

The manage and browse-manage hooks are deliberately left alone, which is the opposite of what the
task asked for.
#1627 states the allowlist "denies unlisted operations even for super_user roles," so
the obvious change was to gate useInstanceManagePermission (Users, Roles, Config, Logs, Status) and
useInstanceBrowseManagePermission (DDL, app editors). I implemented exactly that, then read
verifyPerms and reverted it:

utility/operation_authorization.ts:553   if (isSuperUser && !isSuSystemOperation) return null;   // "admins can do (almost) anything"
utility/operation_authorization.ts:559   structure_user + create/drop database   → return null
utility/operation_authorization.ts:564   structure_user + STRUCTURE_USER_OPS     → return null
utility/operation_authorization.ts:589   ── the operations allowlist gate lives here ──

Measured, not inferred (see Verification): a super_user with operations: [] — the strongest
possible test, since a gate that applied at all would deny everything — is still ALLOWED insert,
search, restart and get_configuration. A restricted-super_user's admin actions do not 403, so gating
that chrome would have hidden UI that works. #1628's 7eff23fc reached the same conclusion
independently. That leaves table DML as the only surface gate 1 can deny, which is what this gates.

Decisions worth questioning:

  • Add Records and Import Data no longer share a flag, and each import method is gated by the
    operation it issues.
    Add Records is strictly insert. Import Data's methods are modeled as the
    paths they can take, because sample and file stay ambiguous until submit — a bundled dataset
    bulk-loads while random records insert, and a .json upload inserts while any other extension
    bulk-loads. A method is offered while either path is open; the resolved source is re-checked at
    submit, where the choice is finally known. CSV paths require get_job alongside the load,
    because importData.ts polls the job — a CSV
    grant without it leaves a committed load reporting failure, and the obvious retry duplicates rows.
  • The gate is version-blind, by shape rather than by version. getOperationsAllowlist only says
    yes to an array of strings, and what a pre-5.0 instance puts under that key is a database record
    (permissionsTranslator writes one for any upgraded v4 role owning a database named operations).
    Asking the instance version instead would mean a query per check on a hook that does not receive
    this module's entityId — the bug feat(roles): surface operation-level grants (permission.operations) in the roles/users UI #1628's b7c01cf4 just removed.
  • The gate simulation does not reuse expandEffectiveOperations. That helper folds alias spellings
    together to describe a role's effective reach; the server's gate does not fold, so a lone
    search_by_id grant must stay the dead entry it is. Simulating the gate is this module's whole job,
    so it expands verbatim and canonicalizes only the operation being checked.
  • Not reusing isElevatedRole (from feat(roles): surface operation-level grants (permission.operations) in the roles/users UI #1628; counts structure_user and cluster_user). It answers
    whether the allowlist is unenforceable anywhere for the role. For DML both are still gated:
    STRUCTURE_USER_OPS is create/drop table and attribute only, and cluster_user appears nowhere in
    operation_authorization.ts. Measured: {structure_user: true, operations: ['read_only']} → insert
    DENIED.
  • Malformed allowlists restrict nothing (fail-open), because a denial that can't be proven must not
    hide UI. This differs from feat(roles): surface operation-level grants (permission.operations) in the roles/users UI #1628's editor, which surfaces a malformed value as active — different
    jobs: it warns, this hides.
  • read gating is live but inert today — no production call site passes 'read', and the
    attribute hook has no callers. Mapped for symmetry and covered by tests.
  • The database-scoped Import launcher has no table to check a grant against (it creates one), so it
    is gated on the operations half alone — otherwise a structure_user with an empty allowlist could
    start a load denied after create_table succeeded, leaving an empty table behind.

Verification

  • End-to-end route: executed verifyPerms / verifyPermsAST directly against the Harper checkout
    (main, mocha, the same harness as unitTests/utility/operation_authorization.test.js), because the
    premise this PR rests on is not covered by any existing test — Harper's allowlist suite sets
    super_user: false everywhere. Throwaway probes, since deleted (Harper checkout clean):

    role + allowlist operation verdict
    super_user: true, operations: [] insert, search_by_conditions, restart, get_configuration ALLOWED (all four)
    super_user: true, operations: ['read_only'] insert, restart ALLOWED
    super_user: false, operations: [] insert DENIED
    structure_user: true, operations: ['read_only'] insert DENIED

    Two further probes settled @kriszyp's review claims, and both confirmed them — see the PR comment for
    detail. They are Harper-side, not defects in this diff: SQL dispatches through verifyPermsAST, which
    has no allowlist gate (operations: [] still permits SQL writes); and 9 of 23 sampled picker names
    can never match the gate because their authorization entry omits api_name (deploy_component among
    them, so Surface role operation-level grants (permission.operations) in the roles/users UI #1627's own deploy-only CI user is not buildable today). feat(roles): surface operation-level grants (permission.operations) in the roles/users UI #1628 tracks the second as
    role operations allowlist: grants are gate-inert for ops registered without api_name (deploy_component, get_status, …); sql bypasses the allowlist harper#2175 with a GATE_INERT_OPERATIONS set; the SQL bypass still wants an issue.

  • The Studio-side tests prove an allowlist shows or hides the right control. What remains unproven from
    Studio: a full round-trip against a live 5.x instance carrying such a role.

  • src/hooks/checkOperationPermission.test.ts (new): absent / malformed / empty / exact / unknown /
    group / both alias directions, a database record under operations reading as no allowlist, expansion
    caching, per-action mapping, structure_user and cluster_user, per-method and per-source import
    capability, and a mapping-integrity test asserting every mapped name is a canonical, non-group catalog
    entry. That last one earned its keep: an earlier draft mapped list_certificates and list_ssh_keys,
    which harper-pro registers at runtime and the catalog therefore lacks, so they would never have matched.

  • checkSchemaTablePermission.test.ts extended: allowlist over a CRUD grant, over structure_user, the
    super_user pass-through, and the Add-Records-vs-Import divergence.

  • TableContextMenuItems.test.tsx and ImportDataModal.permissions.test.tsx (both new) mock only the
    auth store and the router, so they run the real allowlist → hook → rendered-control chain: entries
    hidden for a read-only allowlist, Import Data surviving a CSV grant while Add Records drops, Import
    Data hidden when a CSV load has no get_job, the URL method dropped for an insert-only role, and the
    default selection falling back to an allowed method.

  • Full gate, matching verify-pr.yaml exactlytsc -b ✓, pnpm test:coverage ✓ (301 files /
    2369 passed
    , 11 skipped), pnpm lint ✓, pnpm build ✓; dprint no-op. The one uncovered branch in
    the new module is the ?? [] guard TypeScript makes unreachable.

  • test:e2e:docker is not part of CI's PR gate and its specs cover auth/signup/org-users only —
    nothing in the instance browse UI — so it cannot exercise this change.

Review coverage

Authored by Claude Opus 5. Outside coverage via the bundled pre-push CLI (--author claude), plus a
human review; every round changed the design:

round lens outcome
plan gpt-5.6-sol (codex) rejected the aggregate manage/browse-manage booleans; the narrowing to DML came from this
impl gpt-5.6-sol (codex) CHANGES — the shared insert flag conflated Add Records with Import Data
impl --full gpt-5.6-sol (codex) CHANGES — CSV imports missing get_job; the ungated third Import launcher
impl --full gemini via agy (default model) crash risk on an off-type action lookup; no component tests
post-push gemini-code-assist (CI bot) wanted csv_file_load/import_from_s3 added — declined with rationale, ImportSource issues neither
post-push @kriszyp (with KrAIs/GPT-5) the two server findings above, plus per-source import gating — implemented

cursor-grok ✗ (pruned by policy), cursor-composer ✗ (cursor-agent not installed), Harper domain
adjudication ✗ (exit 1) — so the decision list above is mine, not an adjudicated ledger.

Rebased twice onto a moving #1628 (b7c01cf4, 61d068be), both times with semantic merges rather than
textual ones: the version-blind reversal and the expandEffectiveOperations split above both came out of
those. No cross-model round has run against the current head; the changes since the last reviewed SHA are
the import-method gating, the version-blind revert, and tests.

Docs

No companion PR to HarperFast/documentation: this makes Studio's UI agree with server behavior that
already ships, and adds no API, config, CLI, or default. The allowlist feature's own documentation
belongs with #1628.

Human-Review-Need: 4 @ eae63ad

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Code Review

This pull request introduces granular permission checks for database operations, specifically targeting the "Import Data" feature. It adds helper functions and hooks to verify if a user has the necessary permissions (including bulk load operations and job polling) before displaying import options. It also maps standard table actions to their respective operation names and handles operation aliases. The feedback suggests expanding the CSV_LOAD_OPERATIONS array to include other bulk import operations like csv_file_load and import_from_s3 to prevent users with those permissions from being incorrectly blocked.

Comment thread src/hooks/checkOperationPermission.ts Outdated
@dawsontoth
dawsontoth force-pushed the permission-hooks-operations-allowlist branch from 5689d6b to 93251b6 Compare August 17, 2026 19:57
@dawsontoth
dawsontoth marked this pull request as ready for review August 17, 2026 20:02
@dawsontoth
dawsontoth requested a review from a team as a code owner August 17, 2026 20:02

@kriszyp kriszyp left a comment

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.

This will be great, an excellent UI improvement!

Some things to address (sorry for the inline anchor failure):


Proposed inline comments (anchors failed):

  • src/features/instance/config/roles/operations/operationsCatalog.ts: Blocking correctness issue: this says an empty allowlist denies every operation, but I traced Harper v5.0.0, v5.1.26, and v5.2.2 and found that SQL dispatch is special-cased through verifyPermsAST; the permission.operations gate exists only in verifyPerms. Consequently, operations: []—or any allowlist omitting sql—still permits SQL according to table CRUD, including INSERT/UPDATE/DELETE. The editor would give administrators a false security guarantee. Please enforce the operations gate for SQL in Harper with regression coverage before surfacing this boundary, or restrict Studio support to a server version containing that fix and describe the actual behavior.

— KrAIs (GPT-5)

(src/features/instance/config/roles/operations/operationsCatalog.ts:13 is not part of this PR's diff, so this is a file-level comment)

  • src/features/instance/config/roles/operations/operationsCatalog.ts: Several enum-valid names offered here are not authorization-grantable in the supported server tags. For example, Harper v5.2.2 registers setConfiguration without an api_name; the gate therefore compares the handler name setConfiguration with the allowlist value set_configuration and denies it even though role validation accepted the JSON. The same mismatch affects entries such as restart_service, cleanup_orphan_blobs, read_transaction_log, set_component_file, deploy_component, and the status operations. Please derive the offered set from effective authorization registrations—or fix that server invariant first—and add a contract test proving every picker entry reaches its handler.

— KrAIs (GPT-5)

(src/features/instance/config/roles/operations/operationsCatalog.ts:177 is not part of this PR's diff, so this is a file-level comment)

  • src/hooks/checkOperationPermission.ts:18: The browse flow needs exact operation capabilities rather than one “any read operation” result. DatabaseTableView still unconditionally issues search_by_value, search_by_conditions, and search_by_hash based on state, while both Export CSV entry points remain visible. For example, a role allowing only search_by_hash satisfies this generic read mapping but cannot load the default table view, which calls search_by_value. Please expose/check the concrete operation for each query and button, including edit-fetch and export prerequisites.

— KrAIs (GPT-5)

  • src/hooks/checkOperationPermission.ts:89: This OR is only safe if the modal exposes the matching source. Today an insert-only role passes and opens a table-scoped modal whose default CSV-file path calls csv_data_load; conversely, csv_url_load + get_job passes while the modal still offers CSV files and JSON/sample paths that require other operations. Those permitted-looking choices then fail at submit time. Please return per-source capabilities and hide/disable unsupported methods, or require every method displayed by this launcher.

— KrAIs (GPT-5)

  • src/integrations/api/localRolePermission.ts:38: This collapses an operation-specific bypass into a role-level boolean. Harper bypasses structure_user only for its DDL operations; DML and component/environment operations still reach the allowlist, and cluster_user is not a general bypass. The new table tests already encode that DML remains gated, while the editor and user summaries say the list has no effect. Scoped or empty structure_user arrays are also truthy here. Please model the bypass per operation (and database for scoped structure roles), update the warnings, and audit the many non-DDL consumers of useInstanceBrowseManagePermission.

— KrAIs (GPT-5)

  • src/integrations/api/localRolePermission.ts:55: This conflicts with the pre-5.0 compatibility handled above. On Harper 4.x, { operations: { tables: ... } } can be a valid database permission, but the roles table and add/edit-user summaries call this helper without a server version and label that role invalid. Please pass the instance version into those presentations or otherwise preserve the object-shaped database interpretation on pre-5.0 servers.

— KrAIs (GPT-5)

  • src/features/instance/config/roles/operations/OperationsAllowlistEditor.tsx:271: The comment correctly limits component-registered operation grants to 5.2+, but OperationsPicker receives no version and offers this custom candidate on every supported 5.0/5.1 instance too. Those releases validate only enum and group names, so saving the generated value is rejected. Please pass the version through and enable the custom path only where dynamic grants are supported; newer catalog entries can retain their separately flagged escape hatch.

— KrAIs (GPT-5)

  • src/integrations/api/api.patch.d.ts:158: Now that scoped structure_user arrays are modeled explicitly—and the save helper correctly notes that they still need table grants outside their DDL scope—calculateDefaultPermissions should not return early for every truthy array. As written, editing such a role never adds newly discovered databases, tables, or attributes to its explicit permission template. Please reserve the early return for structure_user === true and add a scoped-role regression test.

— KrAIs (GPT-5)

🤖 Reviewed with Codex

@dawsontoth

Copy link
Copy Markdown
Contributor Author

Thanks — this was a high-value review. Both server-behavior claims are confirmed by execution, not just reading: I ran verifyPerms / verifyPermsAST through Harper's own mocha harness on main (throwaway probe, deleted; Harper checkout clean). Results below, because they're worse than the review states and they land mostly on #1628 and on Harper itself rather than on this PR.

1. SQL bypasses the allowlist — confirmed

Same role, {super_user: false, operations: [], table CRUD all true}:

dispatch verdict
operations-API insert (verifyPerms) DENIED
SQL INSERT (verifyPermsAST) ALLOWED

So operations: [] does not deny everything, and any allowlist omitting sql still permits SQL writes per table CRUD. The gate genuinely exists only in verifyPerms.

This is a Harper server issue plus a false promise in #1628's editor copy, not a defect in this PR's gating (which hides controls whose own operations are denied — those really are denied). But #1628 currently tells an admin they've restricted a role when they haven't, which is the failure mode its own commit message calls the worst one a permissions UI has. Filing against Harper and correcting #1628's wording both look necessary; happy to do either.

2. Names offered but not grantable — confirmed, and broader

requiredPermissions entries registered without an api_name fall back to the camelCase handler name, so the gate compares e.g. setConfiguration against the allowlist's set_configuration. End-to-end on main:

  • operations: ['set_configuration']set_configuration DENIED
  • operations: ['get_configuration']get_configuration ALLOWED (control — it registers an api_name)

Of 23 sampled names Studio's picker offers, 9 can never match: set_configuration, restart_service, cleanup_orphan_blobs, read_transaction_log, set_component_file, deploy_component, get_status, set_status, and sql. 29 authorization entries carry no api_name at all, so the real list is longer than the sample.

Note deploy_component — the deploy-only CI user in #1627's motivating example cannot actually be built today. Again #1628/Harper territory; this PR's gating depends only on insert, update, delete, the searches, csv_data_load, csv_url_load, and get_job, all of which I verified are grantable.

3–4. Import gating — fixed in e202ffc

You were right that the any-of launcher gate lets through methods that can't run. Each method is now modeled as the set of paths it can take, because sample and file don't resolve to an operation until submit (bundled dataset bulk-loads vs. random records insert; .json upload inserts vs. any other extension bulk-loads). A method is offered while either path is open, the modal renders only allowed methods and defaults to one of them, and the resolved source is re-checked at submit where the choice is finally known. New ImportDataModal.permissions.test.tsx proves an insert-only role loses the URL method, a csv_url_load role keeps only URL, and the default selection falls back to an allowed method.

On the read mapping: TABLE_ACTION_OPERATIONS.read is currently inert — no production call site passes 'read', and the attribute hook has no callers — so per-query gating for search_by_value vs search_by_hash, and the ungated Export CSV entry points, are real gaps but pre-existing ones this PR doesn't introduce. I'd rather do those as a follow-up than widen this PR further; say the word if you'd prefer them here.

5–8

isElevatedRole, the pre-5.0 operations-as-database-name compatibility, OperationsPicker's missing version, and calculateDefaultPermissions' early return are all in #1628's files, not this diff. Worth noting your #5 argues for what this PR already does — it deliberately does not use isElevatedRole, precisely because DML stays gated for structure_user; measured: {structure_user: true, operations: ['read_only']}insert DENIED. So the divergence is in #1628's warning copy, which says the list has no effect.

🤖 Addressed by Claude Code

@dawsontoth
dawsontoth force-pushed the permission-hooks-operations-allowlist branch 2 times, most recently from 4943b8d to eae63ad Compare August 17, 2026 23:07
dawsontoth added a commit that referenced this pull request Aug 18, 2026
Three rounds of review on #1631 turned on facts that reading the Harper source
does not settle on its own: verifyPerms clears super users and structure-user DDL
before the gate, SQL never reaches it, ~23 names are inert grants, and alias
grants only work in one direction. Each was measured by calling verifyPerms
directly, so the recipe for doing that is here too -- it is the cheapest way to
answer the next question of this shape.

Also records the seam that broke mid-review: describing a role's effective reach
folds alias spellings, simulating the gate must not, and one shared helper cannot
do both.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
dawsontoth added a commit that referenced this pull request Aug 18, 2026
Three rounds of review on #1631 turned on facts that reading the Harper source
does not settle on its own: verifyPerms clears super users and structure-user DDL
before the gate, SQL never reaches it, ~23 names are inert grants, and alias
grants only work in one direction. Each was measured by calling verifyPerms
directly, so the recipe for doing that is here too -- it is the cheapest way to
answer the next question of this shape.

Also records the seam that broke mid-review: describing a role's effective reach
folds alias spellings, simulating the gate must not, and one shared helper cannot
do both.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@dawsontoth
dawsontoth force-pushed the permission-hooks-operations-allowlist branch from 697b8b0 to 47e5d06 Compare August 18, 2026 16:18

@DavidCockerill DavidCockerill left a comment

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.

Approving at 11db476. The gate order is right and the composition is clean — checkTableActionAllowed(...) && checkTableGrant(...), with the original body extracted verbatim so the pre-existing deny-on-no-permission behaviour is preserved rather than restated.

Endorsing the deviation. Leaving useInstanceManagePermission and useInstanceBrowseManagePermission ungated is correct even though #1627's wording points the other way. verifyPerms returns on isSuperUser && !isSuSystemOperation before the allowlist is consulted, so gating those hooks would have hidden UI the server allows — a fail-closed bug dressed as a security fix. Implementing the obvious version first, reading verifyPerms, and reverting it is the right sequence, and the two one-line comments left behind are the right amount of explanation.

Verified and dropped — four candidates I chased rather than sent:

  • checkAnyOperationAllowed returning true for an absent or malformed allowlist is fail-open in a permissions path, and correct here: it gates affordances only, Harper denies at gate 1, so the worst case is a button that 403s. Failing closed would hide controls from roles that hold the grant.
  • expandLikeTheGate iterating a non-iterable would throw inside a hook. Not reachable — getOperationsAllowlist (localRolePermission.ts:89-94) returns operations only when Array.isArray(...) and every entry is a string, else undefined.
  • The database-scoped Import Data launcher consults no grant, and the operation it starts creates a table — but the button sits inside {canManage && …}, so browse-manage still applies and canImport is an additional gate.
  • @gemini-code-assist's CSV_LOAD_OPERATIONS finding: @dawsontoth's decline is right and I checked it. csv_file_load and import_from_s3 appear only in operationsCatalog.ts, never at a Studio call site, and ImportSource has exactly three variants. A role granted only csv_file_load should see Import Data hidden, because Studio cannot issue it.

One thing for the record, not a request on this PR: localRolePermission.ts documents a breaks-auth class where a truthy non-iterable under operations makes listUsers throw behind a truthiness-only guard, failing authentication instance-wide. That is #1628 territory and you have clearly already mapped it — noting so it is not lost.

This is the complete set of my concerns at this head.

— DAIvid (Claude Opus 5)

Base automatically changed from role-operations-allowlist to stage August 18, 2026 17:20
dawsontoth and others added 5 commits August 18, 2026 14:06
… gating

Studio's table permission checks ignored `permission.operations`, so a role whose
allowlist excludes a data operation still saw Add Records, Import Data, and the
inline edit/delete affordances -- every one of which the server denies at gate 1,
before the role's table CRUD grants are consulted.

checkSchemaTablePermission and useInstanceSchemaTableAttributePermission now run
the allowlist first, via a new pure checkOperationPermission module that mirrors
the server's matching: groups expand, entries match verbatim, and the checked
operation resolves to its canonical api_name (so a granted legacy alias is the
dead entry it is on the server, while a canonical grant covers both spellings).
That verbatim expansion is deliberately not expandEffectiveOperations, which
folds alias spellings together to describe a role's effective reach -- the gate
does not fold, and simulating it is this module's whole job.

Absent and malformed allowlists restrict nothing -- a denial that can't be proven
must not hide UI -- and an empty array denies everything, as on the server. The
check stays version-blind like the table-grant lookup beside it: only an array of
strings is an allowlist, and what a pre-5.0 instance puts under that key is a
database record, so shape alone separates the two without a version query on a
hook that would not receive this module's entityId.

The manage and browse-manage hooks are deliberately NOT gated. verifyPerms
returns early for super users ("admins can do (almost) anything") before the
allowlist is reached, so those operations succeed however narrow the allowlist
is; hiding the admin chrome would hide UI that works. Only DML is gated, which is
also why this cannot lean on isElevatedRole: structure_user short-circuits for
DDL alone and cluster_user is absent from operation_authorization.ts, so both
still reach gate 1 for insert/update/delete/search.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Review found the import launcher's any-of gate still shows methods that cannot
run: an insert-only role opened a modal whose default file path bulk-loads, and a
csv_url_load role saw sample and file methods it could not use.

Each Import Data method is now modeled as the set of paths it can take -- `sample`
and `file` are genuinely ambiguous until submit (a bundled dataset bulk-loads
while random records insert; a .json upload inserts while any other extension
bulk-loads), so a method is offered while either path is open, and the resolved
source is checked again at submit where the choice is finally known. The modal
renders only allowed methods and defaults to one of them instead of the
contextual preference.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Three rounds of review on #1631 turned on facts that reading the Harper source
does not settle on its own: verifyPerms clears super users and structure-user DDL
before the gate, SQL never reaches it, ~23 names are inert grants, and alias
grants only work in one direction. Each was measured by calling verifyPerms
directly, so the recipe for doing that is here too -- it is the cheapest way to
answer the next question of this shape.

Also records the seam that broke mid-review: describing a role's effective reach
folds alias spellings, simulating the gate must not, and one shared helper cannot
do both.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The sample picker's two groups issue different operations -- bundled datasets
bulk-load, the random generator inserts -- so a role holding one and not the
other was offered a source that could only fail. Each group is now gated by its
own operation, which leaves the submit-time check for `file` alone, where the
extension decides the operation and nothing earlier can know it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…hose

The Import launcher checks the insert grant for the table it was opened from, but
the modal's database and table fields are editable -- so a role could launch from
a table it can write, retarget an existing table it cannot, and submit. Submit now
re-asks for the chosen destination. A table that does not exist yet has no grant
to check: creating it is the browse-manage authority the launcher already required.

Also corrects the AGENTS.md claim that table read/write is the only surface the
allowlist can deny. Browse-manage is only half short-circuited -- its
application-editor operations are not in STRUCTURE_USER_OPS, so for a non-super
role they do reach the gate, which is where listing them grants them.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@dawsontoth
dawsontoth force-pushed the permission-hooks-operations-allowlist branch from 11db476 to 1faf64c Compare August 18, 2026 18:09
@github-actions

Copy link
Copy Markdown

Coverage Report

Status Category Percentage Covered / Total
🔵 Lines 57.04% 7069 / 12393
🔵 Statements 57.6% 7601 / 13196
🔵 Functions 49.56% 1778 / 3587
🔵 Branches 51.59% 5071 / 9829
File Coverage
File Stmts Branches Functions Lines Uncovered Lines
Changed Files
src/features/instance/config/roles/operations/operationsCatalog.ts 100% 96% 100% 100%
src/features/instance/databases/components/DatabaseOverview.tsx 2.63% 0% 0% 2.85% 17-167
src/features/instance/databases/components/DatabaseTableView.tsx 0% 0% 0% 0% 76-577
src/features/instance/databases/components/TableContextMenuItems.tsx 61.53% 76.92% 20% 61.53% 34-35, 41-61
src/features/instance/databases/modals/ImportDataModal.tsx 35.59% 23.14% 44% 35.08% 69-89, 200-226, 230-234, 239, 243-312, 322-323, 377-436
src/hooks/checkOperationPermission.ts 100% 92.85% 100% 100%
src/hooks/checkSchemaTablePermission.ts 100% 100% 100% 100%
src/hooks/usePermissions.ts 47.11% 41.91% 58.33% 47.05% 55-61, 82-103, 122, 125, 127, 138-148, 169-176, 188-197, 239-241, 260-302
src/integrations/api/instance/database/importData.ts 0% 0% 0% 0% 27-104
Generated in workflow #1756 for commit 1faf64c by the Vitest Coverage Report Action

@dawsontoth
dawsontoth added this pull request to the merge queue Aug 18, 2026
Merged via the queue into stage with commit 0f2f98c Aug 18, 2026
2 checks passed
@dawsontoth
dawsontoth deleted the permission-hooks-operations-allowlist branch August 18, 2026 18:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants