feat: implement permission checks for qai requests and add tests#1818
Conversation
📝 WalkthroughWalkthroughThis PR introduces MongoDB pipeline collection extraction as a new AI tool, then integrates CedarPermissionsService throughout the request-info-from-table-with-ai-v7 use case to enforce table read permissions before allowing AI data inspection and filtering results by user access. ChangesPermission-Aware AI Data Inspection
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Poem
🚥 Pre-merge checks | ✅ 5 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (5 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
This PR strengthens AI request safety by enforcing table/collection read permissions before executing AI-triggered database tooling, and introduces a MongoDB aggregation pipeline analyzer to identify referenced collections for permission checks.
Changes:
- Add permission gating for AI tool calls (table structure inspection, raw SQL, Mongo aggregation) based on Cedar table read permissions.
- Introduce
collectMongoPipelineCollectionsto extract referenced MongoDB collections from$lookup/$graphLookup/$unionWithstages (including nested sub-pipelines). - Add AVA unit tests for Mongo pipeline collection collection.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 4 comments.
| File | Description |
|---|---|
| backend/src/entities/ai/use-cases/request-info-from-table-with-ai-v7.use.case.ts | Adds Cedar-based permission checks before AI tools can inspect schemas or execute SQL/Mongo queries. |
| backend/src/ai-core/tools/collect-mongo-pipeline-collections.ts | Implements Mongo aggregation pipeline referenced-collection discovery used for permission enforcement. |
| backend/test/ava-tests/non-saas-tests/non-saas-collect-mongo-pipeline-collections.test.ts | Adds unit coverage for Mongo pipeline referenced-collection discovery. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| if (key === '$lookup' || key === '$graphLookup') { | ||
| const from = (value as { from?: unknown })?.from; | ||
| if (typeof from === 'string' && from.length > 0) { | ||
| collected.add(from); | ||
| } | ||
| } else if (key === '$unionWith') { | ||
| // `$unionWith` accepts either a collection-name string or `{ coll: <name>, pipeline: [...] }`. | ||
| if (typeof value === 'string' && value.length > 0) { | ||
| collected.add(value); | ||
| } else { | ||
| const coll = (value as { coll?: unknown })?.coll; | ||
| if (typeof coll === 'string' && coll.length > 0) { | ||
| collected.add(coll); | ||
| } | ||
| } | ||
| } | ||
| collectReferencedCollections(value, collected); | ||
| } |
| const collected = new Set<string>(); | ||
| collectReferencedCollections(parsedPipeline, collected); | ||
| return { kind: 'tables', tables: Array.from(collected) }; |
| test('resolves a $lookup target collection', (t) => { | ||
| t.deepEqual(tablesOf('[{"$lookup":{"from":"salaries","localField":"id","foreignField":"user_id","as":"s"}}]'), [ | ||
| 'salaries', | ||
| ]); | ||
| }); |
| @@ -256,6 +274,14 @@ export class RequestInfoFromTableWithAIUseCaseV7 | |||
| 'Invalid SQL query. Please ensure it is a read-only SELECT statement without any forbidden keywords.', | |||
| ); | |||
| } | |||
| await assertUserCanReadQueryTables({ | |||
| query, | |||
| connectionType: foundConnection.type as ConnectionTypesEnum, | |||
| connectionId: foundConnection.id, | |||
| validateTableRead: (referencedTableName) => | |||
| this.cedarPermissions.improvedCheckTableRead(userId, foundConnection.id, referencedTableName), | |||
| listAllTableNames: async () => (await dataAccessObject.getTablesFromDB()).map((table) => table.tableName), | |||
| }); | |||
| const wrappedQuery = wrapQueryWithLimit(query, foundConnection.type as ConnectionTypesEnum); | |||
| const queryResult = await dataAccessObject.executeRawQuery(wrappedQuery, inputTableName, userEmail); | |||
| result = encodeToToon(queryResult); | |||
| @@ -272,6 +298,13 @@ export class RequestInfoFromTableWithAIUseCaseV7 | |||
| 'Invalid MongoDB command. Please ensure it is a read-only aggregation pipeline without any forbidden keywords.', | |||
| ); | |||
| } | |||
| await this.assertUserCanReadPipelineCollections( | |||
| pipeline, | |||
| inputTableName, | |||
| userId, | |||
| foundConnection.id, | |||
| dataAccessObject, | |||
| ); | |||
| const pipelineResult = await dataAccessObject.executeRawQuery(pipeline, inputTableName, userEmail); | |||
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
backend/src/entities/ai/use-cases/request-info-from-table-with-ai-v7.use.case.ts (1)
345-396:⚠️ Potential issue | 🟠 Major | ⚡ Quick winFilter the relation metadata before returning it.
Lines 345-349 load all relationship metadata, and Lines 392-393 return
tableForeignKeysplusreferencedTableNamesAndColumnsunchanged. That still exposes unreadable table names and join-column details even whenreferencedTablesStructures/foreignTablesStructuresare filtered out. Apply the same read check to those two collections, or omit unreadable entries entirely, before encoding the tool result.🤖 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/ai/use-cases/request-info-from-table-with-ai-v7.use.case.ts` around lines 345 - 396, Filter out unreadable entries from referencedTableNamesAndColumns and tableForeignKeys before returning them: for each entry in referencedTableNamesAndColumns and each foreignKey in tableForeignKeys call this.cedarPermissions.improvedCheckTableRead(userId, foundConnection.id, <table name>) and only include entries where the check returns true (omitting or redacting join-column details for unreadable targets); mirror the same per-table permission logic used to build referencedTablesStructures and foreignTablesStructures so the returned collections do not expose unreadable table names or join columns (use the same userEmail/dao context if you need to re-check structure).
🧹 Nitpick comments (1)
backend/src/ai-core/tools/collect-mongo-pipeline-collections.ts (1)
9-38: 💤 Low valueUse arrow functions for the new top-level helpers.
Both additions are function declarations, which diverges from the repository's TS style and makes this utility layer inconsistent with the rest of the codebase.
As per coding guidelines, "Prefer arrow functions over function declarations".
Also applies to: 49-59
🤖 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/ai-core/tools/collect-mongo-pipeline-collections.ts` around lines 9 - 38, The top-level helper collectReferencedCollections is declared with a function declaration; convert it to an arrow-function style (e.g., assign a const to an arrow function) to follow the repo TS style, and do the same for the other top-level helper in this file that was added around the same area (the second function declaration referenced in the review). Keep the exact parameter types and behavior unchanged (node: unknown, collected: Set<string>): void), replace the function declaration with a const binding to an arrow function, and export/usage should remain identical.
🤖 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.
Outside diff comments:
In
`@backend/src/entities/ai/use-cases/request-info-from-table-with-ai-v7.use.case.ts`:
- Around line 345-396: Filter out unreadable entries from
referencedTableNamesAndColumns and tableForeignKeys before returning them: for
each entry in referencedTableNamesAndColumns and each foreignKey in
tableForeignKeys call this.cedarPermissions.improvedCheckTableRead(userId,
foundConnection.id, <table name>) and only include entries where the check
returns true (omitting or redacting join-column details for unreadable targets);
mirror the same per-table permission logic used to build
referencedTablesStructures and foreignTablesStructures so the returned
collections do not expose unreadable table names or join columns (use the same
userEmail/dao context if you need to re-check structure).
---
Nitpick comments:
In `@backend/src/ai-core/tools/collect-mongo-pipeline-collections.ts`:
- Around line 9-38: The top-level helper collectReferencedCollections is
declared with a function declaration; convert it to an arrow-function style
(e.g., assign a const to an arrow function) to follow the repo TS style, and do
the same for the other top-level helper in this file that was added around the
same area (the second function declaration referenced in the review). Keep the
exact parameter types and behavior unchanged (node: unknown, collected:
Set<string>): void), replace the function declaration with a const binding to an
arrow function, and export/usage should remain identical.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: ee7c0efb-ef4e-4dd2-b08d-38d5b24dc392
📒 Files selected for processing (3)
backend/src/ai-core/tools/collect-mongo-pipeline-collections.tsbackend/src/entities/ai/use-cases/request-info-from-table-with-ai-v7.use.case.tsbackend/test/ava-tests/non-saas-tests/non-saas-collect-mongo-pipeline-collections.test.ts
Summary by CodeRabbit
New Features
Tests