regression: move AppsPermissions definition back to @rocket.chat/apps-engine#40691
regression: move AppsPermissions definition back to @rocket.chat/apps-engine#40691d-gubert wants to merge 1 commit into
Conversation
|
WalkthroughThe PR consolidates permission definitions by moving ChangesPermission Definitions Consolidation
Estimated code review effort🎯 2 (Simple) | ⏱️ ~10 minutes Suggested labels
Suggested reviewers
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. Warning Review ran into problems🔥 ProblemsErrors were encountered while retrieving linked issues. Errors (1)
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 |
|
Looks like this PR is not ready to merge, because of the following issues:
Please fix the issues and try again If you have any trouble, please check the PR guidelines |
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## release-8.5.0 #40691 +/- ##
================================================
Coverage ? 69.69%
================================================
Files ? 3338
Lines ? 123301
Branches ? 21981
================================================
Hits ? 85932
Misses ? 34015
Partials ? 3354
Flags with carried forward coverage won't be shown. Click here to find out more. 🚀 New features to boost your workflow:
|
49501c9 to
1fcec97
Compare
1fcec97 to
41b1237
Compare
There was a problem hiding this comment.
🧹 Nitpick comments (2)
packages/apps-engine/src/definition/metadata/AppPermissions.ts (1)
3-18: ⚡ Quick winRemove the implementation comments from this module.
These docblocks and the inline note conflict with the TS/JS guideline for this repo. Keep the exported names self-descriptive and move longer rationale to external docs if it still needs to live somewhere.
As per coding guidelines, "Avoid code comments in the implementation".
Also applies to: 93-93, 129-134
🤖 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 `@packages/apps-engine/src/definition/metadata/AppPermissions.ts` around lines 3 - 18, Remove the large implementation docblock and any other inline explanatory comments in the AppPermissions module so the file contains only code and self-descriptive exported names (e.g., AppPermissions and its nested permission entries); move any long rationale or examples to external documentation instead and ensure no remaining comment blocks (including the other comment areas referenced in the module) remain in the implementation.packages/apps/src/server/permissions/AppPermissions.ts (1)
1-1: ⚡ Quick winRe-export
AppPermissions/defaultPermissionsfrom the metadata barrel
packages/apps-engine/src/definition/metadata/index.tsalready re-exportsAppPermissionsanddefaultPermissions(export * from './AppPermissions'). Re-exporting from the deep.../AppPermissionspath adds unnecessary coupling since the rest of the codebase imports from@rocket.chat/apps-engine/definition/metadata.Suggested change
-export { AppPermissions, defaultPermissions } from '`@rocket.chat/apps-engine/definition/metadata/AppPermissions`'; +export { AppPermissions, defaultPermissions } from '`@rocket.chat/apps-engine/definition/metadata`';🤖 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 `@packages/apps/src/server/permissions/AppPermissions.ts` at line 1, Replace the deep import re-export in AppPermissions.ts so it re-exports AppPermissions and defaultPermissions from the metadata barrel instead of the deep path; update the export to reference the metadata barrel export that already exposes AppPermissions and defaultPermissions (symbols: AppPermissions, defaultPermissions) to remove unnecessary coupling to the deep module.
🤖 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.
Nitpick comments:
In `@packages/apps-engine/src/definition/metadata/AppPermissions.ts`:
- Around line 3-18: Remove the large implementation docblock and any other
inline explanatory comments in the AppPermissions module so the file contains
only code and self-descriptive exported names (e.g., AppPermissions and its
nested permission entries); move any long rationale or examples to external
documentation instead and ensure no remaining comment blocks (including the
other comment areas referenced in the module) remain in the implementation.
In `@packages/apps/src/server/permissions/AppPermissions.ts`:
- Line 1: Replace the deep import re-export in AppPermissions.ts so it
re-exports AppPermissions and defaultPermissions from the metadata barrel
instead of the deep path; update the export to reference the metadata barrel
export that already exposes AppPermissions and defaultPermissions (symbols:
AppPermissions, defaultPermissions) to remove unnecessary coupling to the deep
module.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 94709403-6140-4199-8e8f-aadc7ef45c32
📒 Files selected for processing (3)
packages/apps-engine/src/definition/metadata/AppPermissions.tspackages/apps-engine/src/definition/metadata/index.tspackages/apps/src/server/permissions/AppPermissions.ts
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
- GitHub Check: cubic · AI code reviewer
- GitHub Check: Hacktron Security Check
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{ts,tsx,js}
📄 CodeRabbit inference engine (.cursor/rules/playwright.mdc)
**/*.{ts,tsx,js}: Write concise, technical TypeScript/JavaScript with accurate typing in Playwright tests
Avoid code comments in the implementation
Files:
packages/apps-engine/src/definition/metadata/index.tspackages/apps/src/server/permissions/AppPermissions.tspackages/apps-engine/src/definition/metadata/AppPermissions.ts
🧠 Learnings (4)
📚 Learning: 2026-02-26T19:25:44.063Z
Learnt from: gabriellsh
Repo: RocketChat/Rocket.Chat PR: 38778
File: packages/ui-voip/src/providers/useMediaSession.ts:192-192
Timestamp: 2026-02-26T19:25:44.063Z
Learning: In the Rocket.Chat repository, do not reference Biome lint rules in code review feedback. Biome is not used even if biome.json exists; only reference Biome rules if there is explicit, project-wide usage documented. For TypeScript files, review lint implications without Biome guidance unless the project enables Biome rules.
Applied to files:
packages/apps-engine/src/definition/metadata/index.tspackages/apps/src/server/permissions/AppPermissions.tspackages/apps-engine/src/definition/metadata/AppPermissions.ts
📚 Learning: 2026-02-26T19:25:44.063Z
Learnt from: gabriellsh
Repo: RocketChat/Rocket.Chat PR: 38778
File: packages/ui-voip/src/providers/useMediaSession.ts:192-192
Timestamp: 2026-02-26T19:25:44.063Z
Learning: In this repository (RocketChat/Rocket.Chat), Biome lint rules are not used even if a biome.json exists. When reviewing TypeScript files (e.g., packages/ui-voip/src/providers/useMediaSession.ts), ensure lint suggestions do not reference Biome-specific rules. Rely on general ESLint/TypeScript lint rules and project conventions instead.
Applied to files:
packages/apps-engine/src/definition/metadata/index.tspackages/apps/src/server/permissions/AppPermissions.tspackages/apps-engine/src/definition/metadata/AppPermissions.ts
📚 Learning: 2026-05-06T12:21:44.083Z
Learnt from: juliajforesti
Repo: RocketChat/Rocket.Chat PR: 40256
File: apps/meteor/client/components/CreateDiscussion/CreateDiscussion.tsx:121-149
Timestamp: 2026-05-06T12:21:44.083Z
Learning: Field wrappers in rocket.chat/fuselage-forms (Field, FieldLabel, FieldRow, FieldError, FieldHint) auto-create htmlFor/id associations, aria-describedby, and role="alert" for errors. Do not manually set htmlFor, id, aria-describedby, or role attributes when using these wrappers. This automatic wiring does not apply to plain rocket.chat/fuselage components, which require explicit ID wiring per the accessibility docs. In code reviews, prefer using fuselage-forms wrappers for form fields and verify there is no unnecessary manual ID/aria wiring in files that use these wrappers. If a component uses plain fuselage components, ensure proper id wiring as per docs.
Applied to files:
packages/apps-engine/src/definition/metadata/index.tspackages/apps/src/server/permissions/AppPermissions.tspackages/apps-engine/src/definition/metadata/AppPermissions.ts
📚 Learning: 2026-05-11T21:46:23.471Z
Learnt from: d-gubert
Repo: RocketChat/Rocket.Chat PR: 40463
File: packages/apps/src/lib/SecureFields.ts:17-19
Timestamp: 2026-05-11T21:46:23.471Z
Learning: In Rocket.Chat’s `packages/apps/tsconfig.json`, TypeScript `"strict"` is set to `false`, which disables strict type-checking (including `noImplicitAny`) for `packages/apps`. When reviewing, do not flag TS7053 (and similar strict-mode indexing/type errors) in files under `packages/apps/src/` that are a consequence of this relaxed strictness—e.g., patterns like indexing an `unknown`/`object` via optional chaining such as `object?.[kSecureFields]`.
Applied to files:
packages/apps/src/server/permissions/AppPermissions.ts
🔇 Additional comments (2)
packages/apps-engine/src/definition/metadata/AppPermissions.ts (1)
1-1: LGTM!Also applies to: 19-92, 94-127, 135-166
packages/apps-engine/src/definition/metadata/index.ts (1)
7-7: LGTM!
| import type { INetworkingPermission, IPermission, IReadSettingPermission, IWorkspaceTokenPermission } from '../permissions/IPermission'; | ||
|
|
||
| /** | ||
| * @description | ||
| * | ||
| * App Permission naming rules: | ||
| * | ||
| * 'scope-name': { | ||
| * 'permission-name': { name: 'scope-name.permission-name' } | ||
| * } | ||
| * | ||
| * You can retrieve this permission by using: | ||
| * AppPermissions['scope-name']['permission-name'] -> { name: 'scope-name.permission-name' } | ||
| * | ||
| * @example | ||
| * | ||
| * AppPermissions.upload.read // { name: 'upload.read', domains: [] } | ||
| */ | ||
| export const AppPermissions = { | ||
| 'user': { | ||
| read: { name: 'user.read' }, | ||
| write: { name: 'user.write' }, | ||
| }, | ||
| 'upload': { | ||
| read: { name: 'upload.read' }, | ||
| write: { name: 'upload.write' }, | ||
| }, | ||
| 'email': { | ||
| send: { name: 'email.send' }, | ||
| }, | ||
| 'ui': { | ||
| interaction: { name: 'ui.interact' }, | ||
| registerButtons: { name: 'ui.registerButtons' }, | ||
| }, | ||
| 'setting': { | ||
| read: { name: 'server-setting.read', hiddenSettings: [] } as IReadSettingPermission, | ||
| write: { name: 'server-setting.write' }, | ||
| }, | ||
| 'room': { | ||
| 'read': { name: 'room.read' }, | ||
| 'write': { name: 'room.write' }, | ||
| 'system-view-all': { name: 'room.system.view-all' }, | ||
| }, | ||
| 'role': { | ||
| read: { name: 'role.read' }, | ||
| write: { name: 'role.write' }, | ||
| }, | ||
| 'message': { | ||
| read: { name: 'message.read' }, | ||
| write: { name: 'message.write' }, | ||
| }, | ||
| 'moderation': { | ||
| read: { name: 'moderation.read' }, | ||
| write: { name: 'moderation.write' }, | ||
| }, | ||
| 'contact': { | ||
| read: { name: 'contact.read' }, | ||
| write: { name: 'contact.write' }, | ||
| }, | ||
| 'threads': { | ||
| read: { name: 'threads.read' }, | ||
| }, | ||
| 'livechat-status': { | ||
| read: { name: 'livechat-status.read' }, | ||
| }, | ||
| 'livechat-custom-fields': { | ||
| write: { name: 'livechat-custom-fields.write' }, | ||
| }, | ||
| 'livechat-visitor': { | ||
| read: { name: 'livechat-visitor.read' }, | ||
| write: { name: 'livechat-visitor.write' }, | ||
| }, | ||
| 'livechat-message': { | ||
| read: { name: 'livechat-message.read' }, | ||
| write: { name: 'livechat-message.write' }, | ||
| multiple: { name: 'livechat-message.multiple' }, | ||
| }, | ||
| 'livechat-room': { | ||
| read: { name: 'livechat-room.read' }, | ||
| write: { name: 'livechat-room.write' }, | ||
| }, | ||
| 'livechat-department': { | ||
| read: { name: 'livechat-department.read' }, | ||
| write: { name: 'livechat-department.write' }, | ||
| multiple: { name: 'livechat-department.multiple' }, | ||
| }, | ||
| 'env': { | ||
| read: { name: 'env.read' }, | ||
| }, | ||
| 'cloud': { | ||
| 'workspace-token': { name: 'cloud.workspace-token', scopes: [] } as IWorkspaceTokenPermission, | ||
| }, | ||
| // Internal permissions | ||
| 'scheduler': { | ||
| default: { name: 'scheduler' }, | ||
| }, | ||
| 'networking': { | ||
| default: { name: 'networking', domains: [] } as INetworkingPermission, | ||
| }, | ||
| 'persistence': { | ||
| default: { name: 'persistence' }, | ||
| }, | ||
| 'command': { | ||
| default: { name: 'slashcommand' }, | ||
| }, | ||
| 'videoConference': { | ||
| read: { name: 'video-conference.read' }, | ||
| write: { name: 'video-conference.write' }, | ||
| provider: { name: 'video-conference-provider' }, | ||
| }, | ||
| 'apis': { | ||
| default: { name: 'api' }, | ||
| }, | ||
| 'oauth-app': { | ||
| read: { name: 'oauth-app.read' }, | ||
| write: { name: 'oauth-app.write' }, | ||
| }, | ||
| 'outboundComms': { | ||
| provide: { name: 'outbound-communication.provide' }, | ||
| }, | ||
| 'experimental': { | ||
| default: { name: 'experimental.default' }, | ||
| }, | ||
| 'abac': { | ||
| read: { name: 'abac.read' }, | ||
| }, | ||
| }; | ||
|
|
||
| /** | ||
| * @description | ||
| * Default permissions for apps | ||
| * Used to ensure backward compatibility with apps | ||
| * that were developed before the permission system was introduced. | ||
| */ | ||
| export const defaultPermissions: Array<IPermission> = [ | ||
| AppPermissions.user.read, | ||
| AppPermissions.user.write, | ||
| AppPermissions.upload.read, | ||
| AppPermissions.upload.write, | ||
| AppPermissions.ui.interaction, | ||
| AppPermissions.setting.read, | ||
| AppPermissions.setting.write, | ||
| AppPermissions.room.read, | ||
| AppPermissions.room.write, | ||
| AppPermissions.message.read, | ||
| AppPermissions.message.write, | ||
| AppPermissions['livechat-department'].read, | ||
| AppPermissions['livechat-department'].write, | ||
| AppPermissions['livechat-room'].read, | ||
| AppPermissions['livechat-room'].write, | ||
| AppPermissions['livechat-message'].read, | ||
| AppPermissions['livechat-message'].write, | ||
| AppPermissions['livechat-visitor'].read, | ||
| AppPermissions['livechat-visitor'].write, | ||
| AppPermissions['livechat-status'].read, | ||
| AppPermissions['livechat-custom-fields'].write, | ||
| AppPermissions.scheduler.default, | ||
| AppPermissions.networking.default, | ||
| AppPermissions.persistence.default, | ||
| AppPermissions.env.read, | ||
| AppPermissions.command.default, | ||
| AppPermissions.videoConference.provider, | ||
| AppPermissions.videoConference.read, | ||
| AppPermissions.videoConference.write, | ||
| AppPermissions.apis.default, | ||
| ]; |
There was a problem hiding this comment.
🔴 High: Unauthorized Workspace Token Scope Escalation by Apps
The CloudWorkspaceBridge allows Rocket.Chat Apps to request workspace access tokens for the Rocket.Chat Cloud service. While it verifies that the app has been granted the cloud.workspace-token permission, it fails to validate that the requested scope is within the array of scopes explicitly granted to the app in its manifest. As a result, any app with the cloud.workspace-token permission can request a token for any scope supported by the workspace. Furthermore, if the app requests an empty scope (''), the getWorkspaceAccessTokenWithScope function defaults to requesting all available workspace scopes, leading to a complete privilege escalation where the app can perform unauthorized actions on behalf of the workspace.
Steps to Reproduce
- Create a Rocket.Chat App and declare the
cloud.workspace-tokenpermission in the manifest with a benign scope (e.g.,scopes: ["read:basic"]). - Install the app. The administrator will approve the app, seeing only the benign scope.
- In the app's code, call
this.context.getProvided().cloudWorkspace.getWorkspaceToken('')(passing an empty string to request all scopes, or a specific highly privileged scope). - The
CloudWorkspaceBridge.doGetWorkspaceTokenmethod will check if the app has thecloud.workspace-tokenpermission, which it does. - The bridge will then call
getWorkspaceAccessTokenWithScopewith the requested scope, without checking if it was in the granted scopes array. - The app receives a token with all highly privileged scopes, bypassing the administrator's intent.
Fix with AI
Fix the following security vulnerability found by Hacktron.
File: packages/apps-engine/src/definition/metadata/AppPermissions.ts
Lines: 1-166
Severity: high
Vulnerability: Unauthorized Workspace Token Scope Escalation by Apps
Description:
The `CloudWorkspaceBridge` allows Rocket.Chat Apps to request workspace access tokens for the Rocket.Chat Cloud service. While it verifies that the app has been granted the `cloud.workspace-token` permission, it fails to validate that the requested `scope` is within the array of scopes explicitly granted to the app in its manifest. As a result, any app with the `cloud.workspace-token` permission can request a token for any scope supported by the workspace. Furthermore, if the app requests an empty scope (`''`), the `getWorkspaceAccessTokenWithScope` function defaults to requesting all available workspace scopes, leading to a complete privilege escalation where the app can perform unauthorized actions on behalf of the workspace.
Proof of Concept:
**Steps to Reproduce**
1. Create a Rocket.Chat App and declare the `cloud.workspace-token` permission in the manifest with a benign scope (e.g., `scopes: ["read:basic"]`).
2. Install the app. The administrator will approve the app, seeing only the benign scope.
3. In the app's code, call `this.context.getProvided().cloudWorkspace.getWorkspaceToken('')` (passing an empty string to request all scopes, or a specific highly privileged scope).
4. The `CloudWorkspaceBridge.doGetWorkspaceToken` method will check if the app has the `cloud.workspace-token` permission, which it does.
5. The bridge will then call `getWorkspaceAccessTokenWithScope` with the requested scope, without checking if it was in the granted scopes array.
6. The app receives a token with all highly privileged scopes, bypassing the administrator's intent.
Affected Code:
public doGetWorkspaceToken(scope: string, appId: string): Promise<IWorkspaceToken> {
if (this.hasCloudTokenPermission(appId)) {
return this.getWorkspaceToken(scope, appId);
}
}
// ...
private hasCloudTokenPermission(appId: string): boolean {
if (AppPermissionManager.hasPermission(appId, AppPermissions.cloud['workspace-token'])) {
return true;
}
Fix this vulnerability. Only change what's necessary - don't modify unrelated code.
Triage: Reply !fp <reason> (false positive), !valid (confirmed), or !accepted_risk <reason>. Reason is optional but improves future scans — e.g. !fp internal endpoint, not user-facing. Any other reply is saved as a triage note.
Proposed changes (including videos or screenshots)
PR #40343 moved the AppsPermissions map from @rocket.chat/apps-engine to the internal "apps" package - this makes sense semantically, as the server should control what permissions are available.
However, the @rocket.chat/apps-compiler package uses the permission list from the @rocket.chat/apps-engine to validate the app's defined permissions during the packaging process, which means removing the list from the apps-engine would make this validation impossible.
So we're bringing the permissions list back to the apps-engine.
Issue(s)
ARCH-2129
Steps to test or reproduce
Further comments
This is related to RocketChat/Rocket.Chat.Apps-compiler#71
Summary by CodeRabbit
New Features
Refactor