-
-
Notifications
You must be signed in to change notification settings - Fork 6
🛡️ Sentinel: [HIGH] Fix IDOR vulnerability in getChatMessages #531
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: main
Are you sure you want to change the base?
Changes from all commits
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,4 @@ | ||
| ## 2026-02-18 - [IDOR in Chat Message Retrieval] | ||
| **Vulnerability:** The `getChatMessages` server action in `lib/actions/chat.ts` was fetching messages by `chatId` without verifying if the requesting user was the owner of the chat or if the chat was public. | ||
| **Learning:** High-level server actions were relying on low-level database utilities that lacked authorization logic, assuming callers would perform checks. This led to an IDOR vulnerability where anyone could read any chat's messages if they knew the `chatId`. | ||
| **Prevention:** Always perform authorization checks in high-level server actions (the entry points for client calls) using the current user's ID from the session and verifying ownership or visibility of the target resource. | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -50,14 +50,24 @@ export async function getChat(id: string, userId: string): Promise<DrizzleChat | | |
| } | ||
|
|
||
| /** | ||
| * Retrieves all messages for a specific chat. | ||
| * Retrieves all messages for a specific chat, ensuring authorization. | ||
| */ | ||
| export async function getChatMessages(chatId: string): Promise<DrizzleMessage[]> { | ||
| if (!chatId) { | ||
| console.warn('getChatMessages called without chatId'); | ||
| return []; | ||
| } | ||
|
|
||
| const userId = await getCurrentUserIdOnServer(); | ||
|
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.
If 🛡️ Proposed fix — pull `getCurrentUserIdOnServer()` inside the `try` block export async function getChatMessages(chatId: string): Promise<DrizzleMessage[]> {
if (!chatId) {
console.warn('getChatMessages called without chatId');
return [];
}
-
- const userId = await getCurrentUserIdOnServer();
-
+ let userId: string | null | undefined;
try {
+ userId = await getCurrentUserIdOnServer();
// Verify user has access to this chat (either as owner or if it's public)
const chat = await dbGetChat(chatId, userId || '');
if (!chat) {
console.warn(`Unauthorized access attempt to messages for chat ${chatId} by user ${userId}`);
return [];
}
return dbGetMessagesByChatId(chatId);
} catch (error) {
console.error(`Error fetching messages for chat ${chatId} in getChatMessages:`, error);
return [];
}
}Also applies to: 63-63 🤖 Prompt for AI Agents |
||
|
|
||
| try { | ||
| // Verify user has access to this chat (either as owner or if it's public) | ||
| const chat = await dbGetChat(chatId, userId || ''); | ||
|
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. 🧹 Nitpick | 🔵 Trivial Implicit coupling between
Additionally, the access check ( ♻️ Proposed refactor — explicit conditional call- const chat = await dbGetChat(chatId, userId || '');
+ const chat = userId
+ ? await dbGetChat(chatId, userId)
+ : await dbGetChat(chatId, '');Also applies to: 71-71 🤖 Prompt for AI Agents |
||
| if (!chat) { | ||
|
Comment on lines
+61
to
+66
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. Passing This is security-sensitive—make the contract explicit by passing SuggestionMake the unauthenticated/public-access path explicit. Options:
const chat = userId
? await dbGetChat(chatId, userId)
: await dbGetPublicChat(chatId)This reduces ambiguity and prevents future regressions in Reply with "@CharlieHelps yes please" if you'd like me to add a commit implementing one of these approaches. |
||
| console.warn(`Unauthorized access attempt to messages for chat ${chatId} by user ${userId}`); | ||
| return []; | ||
| } | ||
|
Comment on lines
+64
to
+69
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. Logging Prefer structured logging with redaction/hashing, or omit IDs and rely on request correlation IDs / auth context already present in server logs. SuggestionRedact or hash identifiers in the warning log (or log only one of them). For example: console.warn('Unauthorized access attempt to chat messages', {
chatId: chatId.slice(0, 8),
hasUser: Boolean(userId),
})Or rely on request-scoped correlation IDs if your logging stack supports it. Reply with "@CharlieHelps yes please" if you'd like me to add a commit with this suggestion. |
||
|
|
||
| return dbGetMessagesByChatId(chatId); | ||
| } catch (error) { | ||
| console.error(`Error fetching messages for chat ${chatId} in getChatMessages:`, error); | ||
|
|
||
This file was deleted.
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.
Two
markdownlintviolations: first-line heading level (MD041) and missing blank line after heading (MD022)Line 1 uses an H2 (
##) but MD041 requires the first line to be an H1 (#). MD022 also requires a blank line between the heading and the following content block.📝 Proposed fix
📝 Committable suggestion
🧰 Tools
🪛 LanguageTool
[style] ~2-~2: You can shorten this phrase to avoid wordiness.
Context: ...ithout verifying if the requesting user was the owner of the chat or if the chat was public. **L...
(BE_THE_MAKER_OF_WORDINESS)
🪛 markdownlint-cli2 (0.21.0)
[warning] 1-1: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
[warning] 1-1: First line in a file should be a top-level heading
(MD041, first-line-heading, first-line-h1)
🤖 Prompt for AI Agents