-
Notifications
You must be signed in to change notification settings - Fork 13.5k
feat(moderation): add explainable reasoning for flagged messages #39943
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop
Are you sure you want to change the base?
Changes from all commits
17e8a2a
b9de355
df853df
cad1521
646c58a
f31c339
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,24 @@ | ||
| **/.classpath | ||
| **/.dockerignore | ||
| **/.env | ||
| **/.git | ||
| **/.gitignore | ||
| **/.project | ||
| **/.settings | ||
| **/.toolstarget | ||
| **/.vs | ||
| **/.vscode | ||
| **/*.*proj.user | ||
| **/*.dbmdl | ||
| **/*.jfm | ||
| **/charts | ||
| **/docker-compose* | ||
| **/compose* | ||
| **/Dockerfile* | ||
| **/node_modules | ||
| **/npm-debug.log | ||
| **/obj | ||
| **/secrets.dev.yaml | ||
| **/values.dev.yaml | ||
| LICENSE | ||
| README.md | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| { | ||
| "configurations": [ | ||
| { | ||
| "name": "Containers: Node.js Launch", | ||
| "type": "docker", | ||
| "request": "launch", | ||
| "preLaunchTask": "docker-run: debug", | ||
| "platform": "node" | ||
| } | ||
| ] | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,39 @@ | ||
| { | ||
| "version": "2.0.0", | ||
| "tasks": [ | ||
| { | ||
| "type": "docker-build", | ||
| "label": "docker-build", | ||
| "platform": "node", | ||
| "dockerBuild": { | ||
| "dockerfile": "${workspaceFolder}/Dockerfile", | ||
| "context": "${workspaceFolder}", | ||
| "pull": true | ||
| } | ||
| }, | ||
| { | ||
| "type": "docker-run", | ||
| "label": "docker-run: release", | ||
| "dependsOn": [ | ||
| "docker-build" | ||
| ], | ||
| "platform": "node" | ||
| }, | ||
| { | ||
| "type": "docker-run", | ||
| "label": "docker-run: debug", | ||
| "dependsOn": [ | ||
| "docker-build" | ||
| ], | ||
| "dockerRun": { | ||
| "env": { | ||
| "DEBUG": "*", | ||
| "NODE_ENV": "development" | ||
| } | ||
| }, | ||
| "node": { | ||
| "enableDebugging": true | ||
| } | ||
| } | ||
| ] | ||
| } |
| Original file line number | Diff line number | Diff line change | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,10 @@ | ||||||||||
| FROM node:lts-alpine | ||||||||||
| ENV NODE_ENV=production | ||||||||||
| WORKDIR /usr/src/app | ||||||||||
| COPY ["package.json", "package-lock.json*", "npm-shrinkwrap.json*", "./"] | ||||||||||
| RUN npm install --production --silent && mv node_modules ../ | ||||||||||
|
Comment on lines
+4
to
+5
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: #!/bin/bash
# Check if yarn.lock exists and examine workspace configuration
echo "=== Checking for lock files ==="
ls -la yarn.lock package-lock.json 2>/dev/null || echo "Lock files not found at root"
echo -e "\n=== Workspace configuration in package.json ==="
jq '.workspaces' package.json 2>/dev/null || cat package.json | grep -A 10 '"workspaces"'
echo -e "\n=== Package manager field ==="
jq '.packageManager' package.json 2>/dev/nullRepository: RocketChat/Rocket.Chat Length of output: 383 🏁 Script executed: cat -n Dockerfile | head -20Repository: RocketChat/Rocket.Chat Length of output: 423 Critical: This monorepo uses yarn workspaces but the Dockerfile uses The root Additionally, moving 🔧 Proposed fix using yarn with --immutable FROM node:lts-alpine
ENV NODE_ENV=production
WORKDIR /usr/src/app
-COPY ["package.json", "package-lock.json*", "npm-shrinkwrap.json*", "./"]
-RUN npm install --production --silent && mv node_modules ../
+COPY ["package.json", "yarn.lock", "./"]
+RUN yarn install --production --immutable
COPY . .
EXPOSE 3000
RUN chown -R node /usr/src/app
USER node
CMD ["node", "index.js"]📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||
| COPY . . | ||||||||||
| EXPOSE 3000 | ||||||||||
| RUN chown -R node /usr/src/app | ||||||||||
| USER node | ||||||||||
| CMD ["node", "index.js"] | ||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: #!/bin/bash
# Check if index.js exists at root and find potential entrypoints
echo "=== Checking for index.js at root ==="
ls -la index.js 2>/dev/null || echo "index.js not found at root"
echo -e "\n=== Main field in package.json ==="
jq '.main' package.json
echo -e "\n=== Looking for potential entrypoints ==="
fd -t f -d 2 'index.js|main.js|server.js' . 2>/dev/null | head -10Repository: RocketChat/Rocket.Chat Length of output: 220 🏁 Script executed: #!/bin/bash
# Verify Dockerfile exists and check its content
echo "=== Dockerfile content ==="
if [ -f "Dockerfile" ]; then
cat -n Dockerfile
else
echo "Dockerfile not found at root"
fi
echo -e "\n=== Repository structure (top-level directories) ==="
ls -la | grep "^d" | head -20
echo -e "\n=== Root package.json details ==="
cat -n package.json | head -40
echo -e "\n=== Looking for .dockerignore, docker-compose, etc ==="
ls -la | grep -i docker
echo -e "\n=== Check for monorepo indicators ==="
if [ -f "lerna.json" ]; then echo "=== lerna.json found ==="; cat -n lerna.json; fi
if [ -f "pnpm-workspace.yaml" ]; then echo "=== pnpm-workspace.yaml found ==="; cat -n pnpm-workspace.yaml; fiRepository: RocketChat/Rocket.Chat Length of output: 3578 🏁 Script executed: #!/bin/bash
# Search for entrypoints with greater depth and different patterns
echo "=== Searching for index.js, main.js, server.js up to depth 5 ==="
fd -t f -d 5 'index\.js$' . 2>/dev/null | head -15
echo -e "\n=== Checking apps/ subdirectory if it exists ==="
if [ -d "apps" ]; then
echo "apps/ directory found, listing structure:"
ls -la apps/
fi
echo -e "\n=== Checking for TypeScript/compiled entry points ==="
fd -t f -d 5 'index\.ts$|server\.ts$|main\.ts$' . 2>/dev/null | head -15
echo -e "\n=== Check if packages/app-* subdirectories exist ==="
find . -maxdepth 2 -type d -name "app-*" | head -10Repository: RocketChat/Rocket.Chat Length of output: 1241 🏁 Script executed: #!/bin/bash
# Check full Dockerfile for build stages
echo "=== Full Dockerfile ==="
cat -n Dockerfile
echo -e "\n=== apps/meteor/package.json ==="
head -50 apps/meteor/package.json
echo -e "\n=== Check for build output or entry in root ==="
ls -la | grep -E '\.meteor|build|dist|server'
echo -e "\n=== Check apps/meteor structure ==="
ls -la apps/meteor/ | head -20Repository: RocketChat/Rocket.Chat Length of output: 3979 🏁 Script executed: #!/bin/bash
# Check docker-compose to understand how container is run
echo "=== docker-compose-local.yml (first 50 lines) ==="
head -50 docker-compose-local.yml
echo -e "\n=== docker-compose-ci.yml (first 50 lines) ==="
head -50 docker-compose-ci.yml
echo -e "\n=== Look for any .meteorrc, .meteor.json, or meteor config ==="
find . -maxdepth 3 -name ".meteorrc" -o -name "meteor.json" -o -name ".meteor" -type d 2>/dev/null | head -10Repository: RocketChat/Rocket.Chat Length of output: 3487 🏁 Script executed: #!/bin/bash
# Check if there are build instructions or scripts at root
echo "=== Build-related scripts at root ==="
ls -la | grep -i build
echo -e "\n=== Check .dockerignore content ==="
cat -n .dockerignore
echo -e "\n=== Look for any npm/yarn scripts that might build ==="
cd apps/meteor && grep -A 5 '"scripts"' package.json | head -20Repository: RocketChat/Rocket.Chat Length of output: 1104 🏁 Script executed: #!/bin/bash
# Check the actual Dockerfile used in builds
echo "=== apps/meteor/.docker/Dockerfile.alpine ==="
if [ -f "apps/meteor/.docker/Dockerfile.alpine" ]; then
cat -n apps/meteor/.docker/Dockerfile.alpine | head -50
else
echo "File not found"
fi
echo -e "\n=== Check all Dockerfiles in the repository ==="
find . -name "Dockerfile*" -type f | head -20
echo -e "\n=== Check .github workflows to see how the image is built ==="
ls -la .github/workflows/ | grep -i dockerRepository: RocketChat/Rocket.Chat Length of output: 2952 🏁 Script executed: #!/bin/bash
# Understand the build process better
echo "=== Check if Meteor generates the index.js during build ==="
# Look for references to the build output
find . -maxdepth 3 -name "*.ts" -o -name "*.js" | xargs grep -l "index.js" 2>/dev/null | head -10
echo -e "\n=== Check apps/meteor/.docker directory ==="
ls -la apps/meteor/.docker/
echo -e "\n=== Look for entry point configuration in meteor settings ==="
cat apps/meteor/.meteorignore 2>/dev/null
ls -la apps/meteor/.meteor/Repository: RocketChat/Rocket.Chat Length of output: 1060 🏁 Script executed: #!/bin/bash
# Check recent changes to root Dockerfile
echo "=== Recent git log for root Dockerfile ==="
git log --oneline -10 Dockerfile 2>/dev/null || echo "Could not get git log"
echo -e "\n=== Check if root Dockerfile is referenced in any CI/CD or build scripts ==="
grep -r "Dockerfile" .github/ .gitpod* .devcontainer/ 2>/dev/null | grep -v ".alpine" | grep -v ".debian" | head -10
echo -e "\n=== Check if root Dockerfile is used in docker-compose files ==="
grep -l "dockerfile" docker-compose*.yml 2>/dev/null | xargs grep -h "dockerfile" 2>/dev/null
echo -e "\n=== Look at the actual Meteor build output structure ==="
ls -la apps/meteor/bundle 2>/dev/null || echo "No bundle directory found (expected - it's created during build)"Repository: RocketChat/Rocket.Chat Length of output: 1863 🏁 Script executed: #!/bin/bash
# Check what the proper entry point should be for Meteor
echo "=== Check apps/meteor/.docker/Dockerfile.alpine for actual CMD ==="
tail -20 apps/meteor/.docker/Dockerfile.alpine
echo -e "\n=== Check Dockerfile.debian for comparison ==="
tail -20 apps/meteor/.docker/Dockerfile.debianRepository: RocketChat/Rocket.Chat Length of output: 1039 The root Dockerfile is incomplete and not used in production builds. Line 10 references The root Dockerfile lacks the build stage required to generate this bundle via 🤖 Prompt for AI Agents |
||||||||||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -278,6 +278,31 @@ export async function sendMessageNotifications(message: IMessage, room: IRoom, u | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const sender = await roomCoordinator.getRoomDirectives(room.t).getMsgSender(message); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const reasons: any[] = []; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (message.msg && message.msg.length > 200) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| reasons.push({ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| type: 'long_message', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| message: 'Message unusually long (possible spam)', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (/(.)\1{5,}/.test(message.msg || '')) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| reasons.push({ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| type: 'repetition', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| message: 'Repeated characters detected', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (reasons.length > 0) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| (message as any).moderationReasons = reasons; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+282
to
+300
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: #!/bin/bash
# Verify whether moderationReasons is part of the formal message contract and where it is used.
rg -n "interface IMessage|moderationReasons" packages/core-typings/src/IMessage/IMessage.ts
rg -n "moderationReasons" apps/meteor --type ts --type js
rg -n "afterSaveMessage\\(|callbacks\\.run\\('afterSaveMessage'" apps/meteor/app/lib/server/lib/afterSaveMessage.ts apps/meteor/app/lib/server/lib/sendNotificationsOnMessage.tsRepository: RocketChat/Rocket.Chat Length of output: 722 🏁 Script executed: cat -n packages/core-typings/src/IMessage/IMessage.ts | head -200Repository: RocketChat/Rocket.Chat Length of output: 6380 🏁 Script executed: rg -i "moderation|reason" packages/core-typings/src/IMessage/ --context 3Repository: RocketChat/Rocket.Chat Length of output: 48 🏁 Script executed: rg -n "TODO|FIXME" apps/meteor/app/lib/server/lib/sendNotificationsOnMessage.tsRepository: RocketChat/Rocket.Chat Length of output: 48 🏁 Script executed: cat packages/core-typings/src/IMessage/IMessage.ts | wc -lRepository: RocketChat/Rocket.Chat Length of output: 69 🏁 Script executed: tail -100 packages/core-typings/src/IMessage/IMessage.tsRepository: RocketChat/Rocket.Chat Length of output: 2621 🏁 Script executed: rg "moderationReasons" apps/meteor --type ts --type js -B 5 -A 5Repository: RocketChat/Rocket.Chat Length of output: 965 🏁 Script executed: cat -n apps/meteor/app/lib/server/lib/sendNotificationsOnMessage.ts | sed -n '295,310p'Repository: RocketChat/Rocket.Chat Length of output: 505 🏁 Script executed: rg "import.*logger|import.*Logger|from.*logger" apps/meteor/app/lib/server/lib/sendNotificationsOnMessage.tsRepository: RocketChat/Rocket.Chat Length of output: 48 🏁 Script executed: rg "console\.log" apps/meteor/app/lib/server/lib/sendNotificationsOnMessage.tsRepository: RocketChat/Rocket.Chat Length of output: 106 Formalize moderation metadata typing instead of Line 299 bypasses the type contract with Additionally, lines 301–304 use Suggested tightening in this file+type ModerationReason = {
+ type: 'long_message' | 'repetition';
+ message: string;
+};
+
+type MessageWithModerationReasons = IMessage & {
+ moderationReasons?: ModerationReason[];
+};
-
- const reasons: any[] = [];
+ const reasons: ModerationReason[] = [];
...
if (reasons.length > 0) {
- (message as any).moderationReasons = reasons;
+ (message as MessageWithModerationReasons).moderationReasons = reasons;📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| console.log('Moderation reasoning:', { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. P2: Unbounded console logging in the message notification path can be abused to flood logs and degrade server performance. Prompt for AI agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| userId: message.u?._id ?? 'unknown', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| reasons, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+301
to
+304
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: # First, let's check the current state of the file around lines 301-304
cat -n apps/meteor/app/lib/server/lib/sendNotificationsOnMessage.ts | sed -n '290,320p'Repository: RocketChat/Rocket.Chat Length of output: 1002 🏁 Script executed: # Check if SystemLogger is imported in this file
grep -n "SystemLogger\|import.*logger" apps/meteor/app/lib/server/lib/sendNotificationsOnMessage.tsRepository: RocketChat/Rocket.Chat Length of output: 48 🏁 Script executed: # Look at the broader context of this function to understand what it does
cat -n apps/meteor/app/lib/server/lib/sendNotificationsOnMessage.ts | sed -n '270,330p'Repository: RocketChat/Rocket.Chat Length of output: 2177 🏁 Script executed: # Check how SystemLogger is used elsewhere in the codebase for similar scenarios
rg "SystemLogger.debug" -A 3 -B 1 --max-count=5Repository: RocketChat/Rocket.Chat Length of output: 4540 🏁 Script executed: # Check if there are other console.log statements in this file
grep -n "console\.log\|console\.error\|console\.warn" apps/meteor/app/lib/server/lib/sendNotificationsOnMessage.tsRepository: RocketChat/Rocket.Chat Length of output: 110 🏁 Script executed: # Check if there are other moderation-related logs in the codebase
rg "moderation|Moderation" -i --max-count=10 -B 2 -A 2Repository: RocketChat/Rocket.Chat Length of output: 50379 🏁 Script executed: # Verify the correct import path for SystemLogger
rg "from.*logger.*system|import.*SystemLogger" -l | head -5Repository: RocketChat/Rocket.Chat Length of output: 302 🏁 Script executed: # Check a similar file to see how imports are structured
cat -n apps/meteor/app/mail-messages/server/functions/sendMail.ts | head -20Repository: RocketChat/Rocket.Chat Length of output: 832 🏁 Script executed: # Look for moderation context - check if this relates to spam detection
rg "long_message|repetition" -B 5 -A 5Repository: RocketChat/Rocket.Chat Length of output: 5537 Replace Line 301 logs spam detection events with Suggested change+import { SystemLogger } from '../../../../server/lib/logger/system';
...
- console.log('Moderation reasoning:', {
- userId: message.u?._id ?? 'unknown',
- reasons,
- });
+ SystemLogger.debug({
+ msg: 'Moderation reasoning',
+ mid: message._id,
+ userId: message.u?._id ?? 'unknown',
+ reasonTypes: reasons.map((reason) => reason.type),
+ });🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!sender) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return message; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| export function escapeRegExp(value: string): string { | ||
| return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); | ||
| } |
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
P1: Overly broad
.dockerignoreglob excludes real source paths (composer*/compose*) from Docker build context, risking incomplete or broken images.Prompt for AI agents