Skip to content

feat: add read-only validation for MongoDB aggregation pipelines and corresponding tests#1819

Merged
Artuomka merged 1 commit into
mainfrom
backend_fixes
Jun 2, 2026
Merged

feat: add read-only validation for MongoDB aggregation pipelines and corresponding tests#1819
Artuomka merged 1 commit into
mainfrom
backend_fixes

Conversation

@Artuomka

@Artuomka Artuomka commented Jun 2, 2026

Copy link
Copy Markdown
Collaborator

Summary by CodeRabbit

  • New Features

    • Added pipeline validation to prevent unauthorized write operations and server-side JavaScript execution in aggregation queries.
  • Security

    • User information responses now properly exclude sensitive OTP secret data.
    • Extended authentication middleware protection to additional user lookup endpoints.
  • Tests

    • Added comprehensive test coverage for aggregation pipeline validation.

Copilot AI review requested due to automatic review settings June 2, 2026 12:26
@Artuomka Artuomka enabled auto-merge June 2, 2026 12:27
@coderabbitai

coderabbitai Bot commented Jun 2, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

This PR implements two security hardening features: (1) MongoDB aggregation pipeline validation that rejects write and server-side JavaScript stages in AI-driven queries, and (2) removal of sensitive OTP secret key from user info API responses with endpoint authentication enforcement.

Changes

MongoDB Aggregation Pipeline Security Validation

Layer / File(s) Summary
Read-only aggregation validator implementation
backend/src/ai-core/tools/query-validators.ts
New isReadOnlyMongoAggregationPipeline function defines a forbidden operator set ($out, $merge, $where, $function, $accumulator) and recursively validates that aggregation pipeline strings contain no forbidden operators, failing safely on parse errors.
Validator integration in AI use case
backend/src/entities/ai/use-cases/request-info-from-table-with-ai-v7.use.case.ts
RequestInfoFromTableWithAIUseCaseV7's executeAggregationPipeline method imports and applies the read-only validator, rejecting non-compliant pipelines with an explicit error message identifying disallowed write and JavaScript stages.
Read-only validator test coverage
backend/test/ava-tests/non-saas-tests/non-saas-mongo-pipeline-readonly-validator.test.ts
AVA test suite validates acceptance of basic and complex read-only pipelines, rejection of all forbidden write and JavaScript operators including nested occurrences, and fail-closed behavior on invalid pipeline JSON.

User Information Exposure Prevention

Layer / File(s) Summary
User info type and builder function updates
backend/src/microservices/saas-microservice/data-structures/found-user-info.ro.ts, backend/src/microservices/saas-microservice/utils/build-found-user-info-ro.ts
FoundUserInfoRO type omits otpSecretKey in addition to password; both buildFoundUserInfoRO and buildFoundUserInfoWithoutCompanyRO builder functions destructure and exclude otpSecretKey from returned user info objects.
Endpoint authentication middleware
backend/src/microservices/saas-microservice/saas.module.ts
SaasModule.configure() extends middleware-protected routes to include GET saas/users/email/:userEmail, requiring authentication on email-based user lookups.

Possibly Related PRs

  • rocket-admin/rocketadmin#1818: Adds table/collection read permission validation to the same executeAggregationPipeline method, complementing this PR's read-only operator restrictions for defense-in-depth pipeline safety.

Poem

🐰 A pipeline walks into a bar,
We check: no writes, no JS so far!
The secrets are hidden, the endpoints are guarded,
Security hardened, vulnerabilities departed! 🔐

Estimated Code Review Effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 16.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Security Check ⚠️ Warning Pipeline validation properly rejects write and JS operators with fail-closed defaults. User info mappers use deny-list pattern, failing open and risking leakage of new UserEntity sensitive fields. Replace Omit deny-list with Pick allow-list in buildFoundUserInfoRO and buildFoundUserInfoWithoutCompanyRO to fail closed and prevent accidental exposure of future UserEntity sensitive fields.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title accurately reflects the main changes: adding a read-only validation function for MongoDB aggregation pipelines and comprehensive tests for it.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch backend_fixes

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copilot AI 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.

Pull request overview

This PR introduces an AST-level validator to ensure MongoDB aggregation pipelines used by the AI tooling are read-only, and adds unit tests around the new validator. It also includes SaaS-related changes (new route and removing otpSecretKey from returned user info), which expands the PR scope beyond the title.

Changes:

  • Add isReadOnlyMongoAggregationPipeline to reject write stages and server-side JS operators at any nesting depth.
  • Enforce the new validator in executeAggregationPipeline within the AI V7 use case.
  • Add AVA tests for the read-only pipeline validator (plus unrelated SaaS route/RO updates).

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
backend/src/ai-core/tools/query-validators.ts Adds AST-walk read-only aggregation pipeline validator.
backend/src/entities/ai/use-cases/request-info-from-table-with-ai-v7.use.case.ts Applies new validator before executing aggregation pipelines.
backend/test/ava-tests/non-saas-tests/non-saas-mongo-pipeline-readonly-validator.test.ts Adds unit tests for the new read-only validator behavior.
backend/src/microservices/saas-microservice/data-structures/found-user-info.ro.ts Excludes otpSecretKey from SaaS user info RO type.
backend/src/microservices/saas-microservice/utils/build-found-user-info-ro.ts Removes otpSecretKey when building SaaS user info RO objects.
backend/src/microservices/saas-microservice/saas.module.ts Adds SaaS route to middleware-protected routes list.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +68 to +76
export function isReadOnlyMongoAggregationPipeline(pipeline: string): boolean {
let parsedPipeline: unknown;
try {
parsedPipeline = JSON.parse(pipeline);
} catch {
return false;
}
return !pipelineContainsForbiddenOperator(parsedPipeline);
}
Comment on lines +50 to +52
test('returns false (fail-closed) for an unparseable pipeline', (t) => {
t.false(isReadOnlyMongoAggregationPipeline('not valid json {'));
});
Comment on lines 122 to 126
.forRoutes(
{ path: 'saas/company/registered', method: RequestMethod.POST },
{ path: 'saas/user/:userId', method: RequestMethod.GET },
{ path: 'saas/users/email/:userEmail', method: RequestMethod.GET },
{ path: 'saas/user/register', method: RequestMethod.POST },

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
backend/test/ava-tests/non-saas-tests/non-saas-mongo-pipeline-readonly-validator.test.ts (1)

50-52: 💤 Low value

Consider adding edge case tests for empty and non-array inputs.

The current coverage is solid for security scenarios. For completeness, you could optionally add tests for:

  • Empty pipeline [] (should pass - valid read-only)
  • Non-array JSON like {} (should pass per implementation, though not a valid MongoDB pipeline format)
🧪 Optional additional tests
+test('allows an empty pipeline', (t) => {
+	t.true(isReadOnlyMongoAggregationPipeline('[]'));
+});
+
+test('allows non-array JSON (not a valid pipeline but parseable)', (t) => {
+	t.true(isReadOnlyMongoAggregationPipeline('{}'));
+});
🤖 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/test/ava-tests/non-saas-tests/non-saas-mongo-pipeline-readonly-validator.test.ts`
around lines 50 - 52, Add two edge-case tests for
isReadOnlyMongoAggregationPipeline: one asserting that an empty pipeline string
'[]' returns true (valid read-only) and another asserting that a non-array JSON
string like '{}' returns true per current implementation; locate the tests near
the existing non-saas-mongo-pipeline-readonly-validator.test cases and add
assertions similar to the current test that call
isReadOnlyMongoAggregationPipeline with those inputs and verify the expected
boolean results.
🤖 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/microservices/saas-microservice/utils/build-found-user-info-ro.ts`:
- Around line 4-12: The mappers buildFoundUserInfoRO and
buildFoundUserInfoWithoutCompanyRO currently return "all except secrets" which
is fragile; change each to construct the return object from an explicit
allowlist of fields (or replace both with a single shared mapper) by explicitly
picking and assigning only the permitted properties from UserEntity into the
FoundUserInfoRO and FoundUserInfoWithoutCompanyRO shapes (e.g., id, email, name,
roles, createdAt, etc.), ensuring the object literal matches the declared return
types and omits any sensitive fields like password, otpSecretKey, company where
appropriate so new UserEntity fields won't leak by default.

---

Nitpick comments:
In
`@backend/test/ava-tests/non-saas-tests/non-saas-mongo-pipeline-readonly-validator.test.ts`:
- Around line 50-52: Add two edge-case tests for
isReadOnlyMongoAggregationPipeline: one asserting that an empty pipeline string
'[]' returns true (valid read-only) and another asserting that a non-array JSON
string like '{}' returns true per current implementation; locate the tests near
the existing non-saas-mongo-pipeline-readonly-validator.test cases and add
assertions similar to the current test that call
isReadOnlyMongoAggregationPipeline with those inputs and verify the expected
boolean results.
🪄 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: ca45e04e-8d7e-4b9a-b7f9-982e2b725623

📥 Commits

Reviewing files that changed from the base of the PR and between 7eac455 and bacdb02.

📒 Files selected for processing (6)
  • backend/src/ai-core/tools/query-validators.ts
  • backend/src/entities/ai/use-cases/request-info-from-table-with-ai-v7.use.case.ts
  • backend/src/microservices/saas-microservice/data-structures/found-user-info.ro.ts
  • backend/src/microservices/saas-microservice/saas.module.ts
  • backend/src/microservices/saas-microservice/utils/build-found-user-info-ro.ts
  • backend/test/ava-tests/non-saas-tests/non-saas-mongo-pipeline-readonly-validator.test.ts

Comment on lines 4 to 12
export function buildFoundUserInfoRO(user: UserEntity): FoundUserInfoRO {
const { password: _password, ...userInfo } = user;
const { password: _password, otpSecretKey: _otpSecretKey, ...userInfo } = user;
return userInfo;
}

export function buildFoundUserInfoWithoutCompanyRO(user: UserEntity): FoundUserInfoWithoutCompanyRO {
const { password: _password, company: _company, ...userInfo } = user;
const { password: _password, company: _company, otpSecretKey: _otpSecretKey, ...userInfo } = user;
return userInfo;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Switch these user mappers to an allowlist DTO.

Lines 5 and 10 still return “everything except known secrets”. That means any future sensitive UserEntity field will leak by default until every redaction site is updated, and the declared return types will not reliably catch that drift. Build the response from an explicit allowlist (or a single shared mapper that does) so this layer fails closed instead of open.

🤖 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/microservices/saas-microservice/utils/build-found-user-info-ro.ts`
around lines 4 - 12, The mappers buildFoundUserInfoRO and
buildFoundUserInfoWithoutCompanyRO currently return "all except secrets" which
is fragile; change each to construct the return object from an explicit
allowlist of fields (or replace both with a single shared mapper) by explicitly
picking and assigning only the permitted properties from UserEntity into the
FoundUserInfoRO and FoundUserInfoWithoutCompanyRO shapes (e.g., id, email, name,
roles, createdAt, etc.), ensuring the object literal matches the declared return
types and omits any sensitive fields like password, otpSecretKey, company where
appropriate so new UserEntity fields won't leak by default.

@Artuomka Artuomka merged commit 8c0225a into main Jun 2, 2026
17 of 18 checks passed
@Artuomka Artuomka deleted the backend_fixes branch June 2, 2026 12:36
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.

2 participants