feat: implement column-level read permissions for table queries#1843
Conversation
- Replaced TableReadGuard with QueryTableGuard in relevant controllers to enforce new query permissions. - Introduced QueryTableGuard to validate user permissions for querying tables. - Updated use cases to filter rows and structures based on readable columns. - Added utility functions for filtering columns by read permissions. - Enhanced CSV export functionality to respect column-level permissions. - Updated tests to cover new permission logic and ensure correct enforcement of column visibility.
📝 WalkthroughWalkthroughIntroduces column-level read authorization into the Cedar policy model by adding a ChangesColumn-Level Cedar Authorization
Sequence Diagram(s)sequenceDiagram
participant Client
participant QueryTableGuard
participant GetTableRowsUseCase
participant CedarPermissionsService
participant CedarAuthorizationService
Client->>QueryTableGuard: GET /table/rows/:connectionId
QueryTableGuard->>CedarAuthorizationService: validate(table:query, tableName)
CedarAuthorizationService-->>QueryTableGuard: allowed / denied
QueryTableGuard-->>Client: 403 if denied
QueryTableGuard->>GetTableRowsUseCase: execute(rowsInput)
GetTableRowsUseCase->>CedarPermissionsService: getReadableColumns(user, connectionId, tableName, allCols)
CedarPermissionsService->>CedarAuthorizationService: validate(column:read, COLUMN_PROBE_ID) [probe]
alt table-wide grant (probe passes)
CedarAuthorizationService-->>CedarPermissionsService: allowed → all columns readable
else per-column
loop each column
CedarPermissionsService->>CedarAuthorizationService: validate(column:read, columnName)
end
end
CedarPermissionsService-->>GetTableRowsUseCase: Set<readableColumns>
GetTableRowsUseCase->>GetTableRowsUseCase: filter rows/structure/list_fields to readableColumns
GetTableRowsUseCase-->>Client: filtered rowsRO response
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 2❌ Failed checks (2 warnings)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Pull request overview
Implements Cedar-backed column-level read permissions for table querying by introducing table:query (pre-query gate) and column:read (post-query filtering), while preserving legacy table:read as an alias for full read.
Changes:
- Replaces table read guarding with
QueryTableGuard(usestable:query) across table read/query endpoints and visualization query execution. - Adds
column:readsupport end-to-end: Cedar schema/entity support, policy generator/parser updates, and row/structure/CSV filtering utilities. - Expands non-SaaS AVA test coverage for column visibility, legacy alias behavior, and permissions round-trips.
Reviewed changes
Copilot reviewed 25 out of 25 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| backend/test/ava-tests/non-saas-tests/non-saas-cedar-save-policy-e2e.test.ts | Adds E2E coverage for column filtering, query denial, legacy alias behavior, CSV export filtering, and permissions persistence. |
| backend/test/ava-tests/non-saas-tests/non-saas-cedar-policy-parser.test.ts | Adds parser tests for table:query + column:read (wildcard and per-column) and generator/parser round-trips. |
| backend/test/ava-tests/non-saas-tests/non-saas-cedar-policy-generator.test.ts | Updates generator expectations for the new alias model (table:query + column:read). |
| backend/test/ava-tests/non-saas-tests/non-saas-cedar-entity-builder.test.ts | Tests new Column entity building and table parent relationship. |
| backend/src/guards/query-table.guard.ts | Renames/repurposes guard to enforce table:query instead of table:read. |
| backend/src/entities/visualizations/panel/use-cases/test-db-query.use.case.ts | Switches referenced table validation from table:read to table:query. |
| backend/src/entities/visualizations/panel/use-cases/execute-panel.use.case.ts | Switches referenced table validation from table:read to table:query. |
| backend/src/entities/table/utils/filter-columns-by-read-permission.util.ts | New utilities to filter rows/structure/column name lists by readable column set. |
| backend/src/entities/table/use-cases/get-table-rows.use.case.ts | Computes readable columns and filters returned rows/metadata accordingly. |
| backend/src/entities/table/use-cases/get-row-by-primary-key.use.case.ts | Applies readable-column filtering to single-row retrieval response. |
| backend/src/entities/table/use-cases/export-csv-from-table.use.case.ts | Filters exported CSV rows to readable columns (array and stream modes). |
| backend/src/entities/table/table.controller.ts | Switches read/structure/row/CSV endpoints to QueryTableGuard. |
| backend/src/entities/table/table-pure-crud-operations/table-pure-crud-operations.controller.ts | Switches CRUD read endpoints to QueryTableGuard. |
| backend/src/entities/table-settings/personal-table-settings/personal-table-settings.controller.ts | Switches settings endpoints to QueryTableGuard. |
| backend/src/entities/table-filters/table-filters.controller.ts | Switches filters read endpoint to QueryTableGuard. |
| backend/src/entities/permission/permission.interface.ts | Adds readableColumns?: string[] to table permission model. |
| backend/src/entities/permission/application/data-structures/create-permissions.ds.ts | Adds DTO validation for readableColumns. |
| backend/src/entities/cedar-authorization/cedar-schema.ts | Adds Column entity type and table:query / column:read actions. |
| backend/src/entities/cedar-authorization/cedar-schema.json | JSON schema mirror update for Column + new actions. |
| backend/src/entities/cedar-authorization/cedar-policy-parser.ts | Parses table:query and column:read into classical permissions + whitelists. |
| backend/src/entities/cedar-authorization/cedar-policy-generator.ts | Generates table:query + wildcard/per-column column:read instead of direct table:read. |
| backend/src/entities/cedar-authorization/cedar-permissions.service.ts | Adds checkColumnRead + getReadableColumns; updates table-read checks to mean query permission. |
| backend/src/entities/cedar-authorization/cedar-entity-builder.ts | Adds Column entity parented by Table to support resource in Table policies. |
| backend/src/entities/cedar-authorization/cedar-authorization.service.ts | Adds Column validation flow and policy reference validation for Column ids. |
| backend/src/entities/cedar-authorization/cedar-action-map.ts | Adds TableQuery / ColumnRead actions, Column resource type, and probe id support. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| if (restrictColumns) { | ||
| rowsRO.rows = filterRowsByReadableColumns(rowsRO.rows, readableColumns); | ||
| rowsRO.structure = filterStructureByReadableColumns(rowsRO.structure, readableColumns); | ||
| rowsRO.list_fields = filterColumnNamesByReadable(rowsRO.list_fields, readableColumns) ?? []; | ||
| rowsRO.sortable_by = filterColumnNamesByReadable(rowsRO.sortable_by, readableColumns) ?? []; | ||
| rowsRO.columns_view = filterColumnNamesByReadable(rowsRO.columns_view, readableColumns); | ||
| rowsRO.table_permissions.readableColumns = Array.from(readableColumns); | ||
| } |
| readonly_fields: tableSettings?.readonly_fields ? tableSettings.readonly_fields : [], | ||
| list_fields: findAvailableFields(builtDAOsTableSettings, tableStructure), | ||
| list_fields: listFields, | ||
| action_events: customActionEvents.map((event) => buildActionEventDto(event)), | ||
| table_actions: customActionEvents.map((el) => buildActionEventDto(el)), | ||
| identity_column: tableSettings?.identity_column ? tableSettings.identity_column : null, |
| const tableResourceIds = [...cedarPolicy.matchAll(/resource\s*==\s*RocketAdmin::Table::"([^"]+)"/g)].map( | ||
| (m) => m[1], | ||
| ); | ||
|
|
||
| for (const tableRef of tableResourceIds) { | ||
| if (!tableRef.startsWith(`${connectionId}/`)) { | ||
| throw new HttpException( | ||
| { message: Messages.CEDAR_POLICY_REFERENCES_FOREIGN_CONNECTION }, | ||
| HttpStatus.BAD_REQUEST, | ||
| ); | ||
| } | ||
| } |
| // The exported file is returned as a binary body (Buffer), not parsed text. | ||
| const csvText = (csvResponse.body as Buffer)?.toString() ?? csvResponse.text; | ||
| // The CSV header is derived from row keys; the hidden column must not appear at all. | ||
| t.true(csvText.includes(allowedColumn)); | ||
| t.false(csvText.includes(hiddenColumn)); |
| @UseGuards(QueryTableGuard) | ||
| @Get('/table/structure/:connectionId') |
There was a problem hiding this comment.
Actionable comments posted: 3
🧹 Nitpick comments (1)
backend/src/entities/table/utils/filter-columns-by-read-permission.util.ts (1)
5-42: ⚡ Quick winUse arrow-function exports for these helpers.
This utility introduces multiple function declarations; the project JS/TS style prefers arrow functions for declarations.
As per coding guidelines, "Prefer arrow functions over function declarations".
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@backend/src/entities/table/utils/filter-columns-by-read-permission.util.ts` around lines 5 - 42, Convert all function declarations in this file to arrow function exports to align with the project's coding style guidelines. Specifically, refactor isAllColumnsReadable, filterRowByReadableColumns, filterRowsByReadableColumns, filterStructureByReadableColumns, and filterColumnNamesByReadable from traditional function declaration syntax to const-based arrow function syntax while preserving their return types and parameters.Source: Coding guidelines
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@backend/src/entities/cedar-authorization/cedar-permissions.service.ts`:
- Around line 298-300: The evaluateColumnRead method call at line 298 uses
COLUMN_PROBE_ID as a synthetic probe to determine if a user can read all
columns, but if a real column or policy resource actually uses that same probe
ID value, it can over-grant access. Ensure that COLUMN_PROBE_ID is guaranteed to
be a unique, non-colliding identifier that cannot match any real column name, or
alternatively refactor the authorization logic to avoid this shortcut and
instead enumerate individual column permissions rather than treating a single
probe result as proxy for all columns.
In `@backend/src/entities/table/use-cases/export-csv-from-table.use.case.ts`:
- Around line 132-147: Remove the `as any` type assertion from the csv.stringify
call in the restrictColumns branch where rowsArray is processed. Additionally,
add explicit type annotations to the Transform stream callback function
parameters in the second restrictColumns branch: annotate the `row` parameter as
`Record<string, unknown>` (consistent with how it is used in the
filterRowByReadableColumns call) and annotate the `_encoding` parameter with the
appropriate encoding type from Node.js stream types. This ensures both the
array-based path and the stream-based path maintain proper type safety without
resorting to generic `any` types.
In `@backend/src/entities/table/use-cases/get-table-rows.use.case.ts`:
- Around line 363-370: The code currently filters several column-related fields
when restrictColumns is enabled, but leaves other metadata fields containing
column names unfiltered (such as primaryColumns and column arrays within
table_settings), allowing restricted users to infer hidden columns. Identify all
response fields on the rowsRO object that carry column names (beyond rows,
structure, list_fields, sortable_by, and columns_view), and apply the same
filtering pattern using the existing filter functions like
filterColumnNamesByReadable to those fields as well. Ensure this filtering is
applied consistently in both the multi-row response path shown in the
restrictColumns block and in the single-row use case response path.
---
Nitpick comments:
In `@backend/src/entities/table/utils/filter-columns-by-read-permission.util.ts`:
- Around line 5-42: Convert all function declarations in this file to arrow
function exports to align with the project's coding style guidelines.
Specifically, refactor isAllColumnsReadable, filterRowByReadableColumns,
filterRowsByReadableColumns, filterStructureByReadableColumns, and
filterColumnNamesByReadable from traditional function declaration syntax to
const-based arrow function syntax while preserving their return types and
parameters.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: ca1c604c-903b-4de6-9371-c99835ef3666
📒 Files selected for processing (25)
backend/src/entities/cedar-authorization/cedar-action-map.tsbackend/src/entities/cedar-authorization/cedar-authorization.service.tsbackend/src/entities/cedar-authorization/cedar-entity-builder.tsbackend/src/entities/cedar-authorization/cedar-permissions.service.tsbackend/src/entities/cedar-authorization/cedar-policy-generator.tsbackend/src/entities/cedar-authorization/cedar-policy-parser.tsbackend/src/entities/cedar-authorization/cedar-schema.jsonbackend/src/entities/cedar-authorization/cedar-schema.tsbackend/src/entities/permission/application/data-structures/create-permissions.ds.tsbackend/src/entities/permission/permission.interface.tsbackend/src/entities/table-filters/table-filters.controller.tsbackend/src/entities/table-settings/personal-table-settings/personal-table-settings.controller.tsbackend/src/entities/table/table-pure-crud-operations/table-pure-crud-operations.controller.tsbackend/src/entities/table/table.controller.tsbackend/src/entities/table/use-cases/export-csv-from-table.use.case.tsbackend/src/entities/table/use-cases/get-row-by-primary-key.use.case.tsbackend/src/entities/table/use-cases/get-table-rows.use.case.tsbackend/src/entities/table/utils/filter-columns-by-read-permission.util.tsbackend/src/entities/visualizations/panel/use-cases/execute-panel.use.case.tsbackend/src/entities/visualizations/panel/use-cases/test-db-query.use.case.tsbackend/src/guards/query-table.guard.tsbackend/test/ava-tests/non-saas-tests/non-saas-cedar-entity-builder.test.tsbackend/test/ava-tests/non-saas-tests/non-saas-cedar-policy-generator.test.tsbackend/test/ava-tests/non-saas-tests/non-saas-cedar-policy-parser.test.tsbackend/test/ava-tests/non-saas-tests/non-saas-cedar-save-policy-e2e.test.ts
| if (this.evaluateColumnRead(cognitoUserName, connectionId, tableName, COLUMN_PROBE_ID, ctx)) { | ||
| return new Set(allColumnNames); | ||
| } |
There was a problem hiding this comment.
Probe-based full-column shortcut can over-grant access.
Line 298 treats an allow on synthetic __probe__ as “all columns readable”. If a real column (or explicit policy resource) uses that same id, users can be escalated from one-column access to all columns.
Suggested fix
- if (this.evaluateColumnRead(cognitoUserName, connectionId, tableName, COLUMN_PROBE_ID, ctx)) {
- return new Set(allColumnNames);
- }
-
const readable = new Set<string>();
for (const columnName of allColumnNames) {
if (this.evaluateColumnRead(cognitoUserName, connectionId, tableName, columnName, ctx)) {
readable.add(columnName);
}
}
return readable;📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| if (this.evaluateColumnRead(cognitoUserName, connectionId, tableName, COLUMN_PROBE_ID, ctx)) { | |
| return new Set(allColumnNames); | |
| } | |
| const readable = new Set<string>(); | |
| for (const columnName of allColumnNames) { | |
| if (this.evaluateColumnRead(cognitoUserName, connectionId, tableName, columnName, ctx)) { | |
| readable.add(columnName); | |
| } | |
| } | |
| return readable; |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@backend/src/entities/cedar-authorization/cedar-permissions.service.ts` around
lines 298 - 300, The evaluateColumnRead method call at line 298 uses
COLUMN_PROBE_ID as a synthetic probe to determine if a user can read all
columns, but if a real column or policy resource actually uses that same probe
ID value, it can over-grant access. Ensure that COLUMN_PROBE_ID is guaranteed to
be a unique, non-colliding identifier that cannot match any real column name, or
alternatively refactor the authorization logic to avoid this shortcut and
instead enumerate individual column permissions rather than treating a single
probe result as proxy for all columns.
| const rowsArray = restrictColumns | ||
| ? (rowsStream as unknown as Array<Record<string, unknown>>).map((row) => | ||
| filterRowByReadableColumns(row, readableColumns), | ||
| ) | ||
| : rowsStream; | ||
| return new StreamableFile(csv.stringify(rowsArray as any, { header: true })); | ||
| } | ||
| if (restrictColumns) { | ||
| const columnFilterTransform = new Transform({ | ||
| objectMode: true, | ||
| transform(row, _encoding, callback) { | ||
| callback(null, filterRowByReadableColumns(row as Record<string, unknown>, readableColumns)); | ||
| }, | ||
| }); | ||
| return new StreamableFile(rowsStream.pipe(columnFilterTransform).pipe(csv.stringify({ header: true }))); | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# First, check if the file exists and examine the code at lines 132-147
fd -p "export-csv-from-table.use.case.ts" backend/Repository: rocket-admin/rocketadmin
Length of output: 139
🏁 Script executed:
# Get the file content to verify the actual code and context
cat -n backend/src/entities/table/use-cases/export-csv-from-table.use.case.ts | sed -n '100,160p'Repository: rocket-admin/rocketadmin
Length of output: 2760
🏁 Script executed:
# Check the current imports at the top of the file
head -30 backend/src/entities/table/use-cases/export-csv-from-table.use.case.tsRepository: rocket-admin/rocketadmin
Length of output: 2383
🏁 Script executed:
# Check what types are available from node:stream
node -e "const stream = require('stream'); console.log(Object.getOwnPropertyNames(stream).filter(name => name.includes('Callback')).join('\n'))"Repository: rocket-admin/rocketadmin
Length of output: 69
🌐 Web query:
node:stream TransformCallback type Node.js TypeScript
💡 Result:
In Node.js, TransformCallback is a TypeScript type alias used within the stream module to define the callback function signature for the _transform and _flush methods [1]. The signature of TransformCallback is defined as: type TransformCallback = (error?: Error | null, data?: any) => void; This callback is used to signal that the transformation of a chunk is complete or that an error has occurred [2]. Key implementation details: - It is typically imported from node:stream [3][4]. - When implementing a custom Transform stream by extending the Transform class, the _transform method accepts this callback as its third argument [1][2]. - You call the callback with an error object (or null) to indicate the status of the operation [2]. You can optionally pass a second argument (data) to push the transformed chunk to the readable side of the stream, which is equivalent to calling this.push(data) followed by callback [5]. Example usage: import { Transform, type TransformCallback } from 'node:stream'; class MyTransform extends Transform { _transform(chunk: any, encoding: BufferEncoding, callback: TransformCallback): void { try { const transformed = chunk.toString.toUpperCase; // Signal completion and optionally pass the data callback(null, transformed); } catch (err) { // Signal error callback(err instanceof Error? err: new Error(String(err))); } } }
Citations:
- 1: https://cdn.jsdelivr.net/npm/@types/node@24.0.1/stream.d.ts
- 2: https://www.sitepoint.com/node-js-streams-with-typescript/
- 3: https://stackoverflow.com/questions/79286291/how-to-extend-a-node-js-transform-stream-type-safe-with-custom-events
- 4: https://blog.mmyoji.com/posts/2022/01-24-transform-stream/
- 5: https://bun.sh/reference/node/stream/default/TransformCallback
Tighten types in the new CSV column-restriction path.
The new branch introduces as any and an unannotated Transform callback. Replace these with explicit stream/callback types to keep this path type-safe.
Proposed typed adjustment
-import { Transform } from 'node:stream';
+import { Transform, TransformCallback } from 'node:stream';
...
- return new StreamableFile(csv.stringify(rowsArray as any, { header: true }));
+ return new StreamableFile(csv.stringify(rowsArray as Array<Record<string, unknown>>, { header: true }));
...
- transform(row, _encoding, callback) {
+ transform(
+ row: Record<string, unknown>,
+ _encoding: BufferEncoding,
+ callback: TransformCallback,
+ ): void {
callback(null, filterRowByReadableColumns(row as Record<string, unknown>, readableColumns));
},The coding guidelines require: "Always add type annotations to function parameters and return types in TypeScript" and "Avoid any types - use specific types or generics instead".
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const rowsArray = restrictColumns | |
| ? (rowsStream as unknown as Array<Record<string, unknown>>).map((row) => | |
| filterRowByReadableColumns(row, readableColumns), | |
| ) | |
| : rowsStream; | |
| return new StreamableFile(csv.stringify(rowsArray as any, { header: true })); | |
| } | |
| if (restrictColumns) { | |
| const columnFilterTransform = new Transform({ | |
| objectMode: true, | |
| transform(row, _encoding, callback) { | |
| callback(null, filterRowByReadableColumns(row as Record<string, unknown>, readableColumns)); | |
| }, | |
| }); | |
| return new StreamableFile(rowsStream.pipe(columnFilterTransform).pipe(csv.stringify({ header: true }))); | |
| } | |
| const rowsArray = restrictColumns | |
| ? (rowsStream as unknown as Array<Record<string, unknown>>).map((row) => | |
| filterRowByReadableColumns(row, readableColumns), | |
| ) | |
| : rowsStream; | |
| return new StreamableFile(csv.stringify(rowsArray as Array<Record<string, unknown>>, { header: true })); | |
| } | |
| if (restrictColumns) { | |
| const columnFilterTransform = new Transform({ | |
| objectMode: true, | |
| transform( | |
| row: Record<string, unknown>, | |
| _encoding: BufferEncoding, | |
| callback: TransformCallback, | |
| ): void { | |
| callback(null, filterRowByReadableColumns(row as Record<string, unknown>, readableColumns)); | |
| }, | |
| }); | |
| return new StreamableFile(rowsStream.pipe(columnFilterTransform).pipe(csv.stringify({ header: true }))); | |
| } |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@backend/src/entities/table/use-cases/export-csv-from-table.use.case.ts`
around lines 132 - 147, Remove the `as any` type assertion from the
csv.stringify call in the restrictColumns branch where rowsArray is processed.
Additionally, add explicit type annotations to the Transform stream callback
function parameters in the second restrictColumns branch: annotate the `row`
parameter as `Record<string, unknown>` (consistent with how it is used in the
filterRowByReadableColumns call) and annotate the `_encoding` parameter with the
appropriate encoding type from Node.js stream types. This ensures both the
array-based path and the stream-based path maintain proper type safety without
resorting to generic `any` types.
Source: Coding guidelines
| if (restrictColumns) { | ||
| rowsRO.rows = filterRowsByReadableColumns(rowsRO.rows, readableColumns); | ||
| rowsRO.structure = filterStructureByReadableColumns(rowsRO.structure, readableColumns); | ||
| rowsRO.list_fields = filterColumnNamesByReadable(rowsRO.list_fields, readableColumns) ?? []; | ||
| rowsRO.sortable_by = filterColumnNamesByReadable(rowsRO.sortable_by, readableColumns) ?? []; | ||
| rowsRO.columns_view = filterColumnNamesByReadable(rowsRO.columns_view, readableColumns); | ||
| rowsRO.table_permissions.readableColumns = Array.from(readableColumns); | ||
| } |
There was a problem hiding this comment.
Restricted users can still infer hidden columns from unfiltered metadata.
When restriction is enabled, only rows, structure, and selected column lists are filtered. Column-name-bearing metadata such as primaryColumns (and column arrays within table_settings) can still expose non-readable columns. Apply the same readable-column filtering to every response field that carries column names (including the single-row use case response path).
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@backend/src/entities/table/use-cases/get-table-rows.use.case.ts` around lines
363 - 370, The code currently filters several column-related fields when
restrictColumns is enabled, but leaves other metadata fields containing column
names unfiltered (such as primaryColumns and column arrays within
table_settings), allowing restricted users to infer hidden columns. Identify all
response fields on the rowsRO object that carry column names (beyond rows,
structure, list_fields, sortable_by, and columns_view), and apply the same
filtering pattern using the existing filter functions like
filterColumnNamesByReadable to those fields as well. Ensure this filtering is
applied consistently in both the multi-row response path shown in the
restrictColumns block and in the single-row use case response path.
Summary by CodeRabbit
Release Notes
New Features
readableColumnsconfiguration to define permitted columns per user and connection.Tests