Skip to content

fix: prevent mass assignment in updateProfile#2902

Open
Nancy-3012 wants to merge 1 commit into
ritesh-1918:mainfrom
Nancy-3012:fix/mass-assignment-profile-endpoint
Open

fix: prevent mass assignment in updateProfile#2902
Nancy-3012 wants to merge 1 commit into
ritesh-1918:mainfrom
Nancy-3012:fix/mass-assignment-profile-endpoint

Conversation

@Nancy-3012

@Nancy-3012 Nancy-3012 commented Jun 17, 2026

Copy link
Copy Markdown

Whitelist only safe fields (full_name, avatar_url, phone, company) before passing updates to Supabase. Prevents privilege escalation via role/status/company_id injection.

Fixes #2894

Summary by CodeRabbit

  • Bug Fixes
    • Profile updates now only allow changes to authorized fields: full name, avatar, phone, and company. Updates attempting to modify other profile fields are filtered out.

Whitelist only safe fields (full_name, avatar_url, phone, company)
before passing updates to Supabase. Prevents privilege escalation
via role/status/company_id injection.

Fixes ritesh-1918#2894
@vercel

vercel Bot commented Jun 17, 2026

Copy link
Copy Markdown

@Nancy-3012 is attempting to deploy a commit to the ritesh Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai

coderabbitai Bot commented Jun 17, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

updateProfile in authStore.js now filters the incoming updates object to an explicit allowlist of four user-editable fields (full_name, avatar_url, phone, company), returns early if the filtered set is empty, and passes only safeUpdates to the Supabase profiles table update.

Changes

Frontend profile update field allowlist

Layer / File(s) Summary
Field allowlist filtering and Supabase update
Frontend/src/store/authStore.js
updateProfile builds safeUpdates by filtering updates to full_name, avatar_url, phone, and company; returns early if the current profile has no id or if safeUpdates is empty; then applies safeUpdates (instead of raw updates) to the Supabase profiles update query.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~8 minutes

Poem

🐇 Hop, hop, only safe fields may pass!
No sneaky role shall slip through the grass.
full_name, avatar_url, phone, company — just four,
The bunny guards the profile store door.
Mass assignment? Not today, no more! 🛡️

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Linked Issues check ⚠️ Warning The code change implements field whitelisting in updateProfile, but the linked issue requires both field allowlist AND authorization checks (ownership/role validation). The implementation addresses the allowlist requirement but lacks authorization logic. Add authorization checks to verify the user is updating their own profile or has admin privileges before allowing the update, as specified in issue #2894.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The pull request title 'fix: prevent mass assignment in updateProfile' accurately describes the main change—implementing field whitelisting to prevent mass assignment attacks in the updateProfile function.
Out of Scope Changes check ✅ Passed The changes are narrowly focused on implementing field whitelisting in updateProfile, which is directly in scope for issue #2894's mass assignment vulnerability fix.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

@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: 2

🤖 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 `@Frontend/src/store/authStore.js`:
- Around line 252-256: The updateProfile function in authStore.js silently drops
avatar updates because the avatar upload flow in Profile.jsx sends the data with
the key profile_picture, but the ALLOWED_FIELDS allowlist at line 252 expects
avatar_url. Before filtering updates against ALLOWED_FIELDS, add logic to
normalize the legacy key by mapping profile_picture to avatar_url so that avatar
updates are properly included in safeUpdates instead of being filtered out and
causing an early return at line 256.
- Around line 247-265: The updateProfile function implements client-side field
whitelisting which is good practice but insufficient for security. Add Row-Level
Security (RLS) policies to the profiles table in your Supabase database to
enforce server-side authorization that prevents unauthorized users from
modifying other users' profiles. Create an RLS policy that allows users to
update only their own profile records (matching user ID) or allows admin users
to update any profile, ensuring the eq('id', profile.id) condition cannot be
bypassed by direct API calls that circumvent the frontend filtering.
🪄 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: 6941364a-e8b8-4c6a-aa75-b1bc6e86fdfa

📥 Commits

Reviewing files that changed from the base of the PR and between da8faf2 and 41ff315.

📒 Files selected for processing (1)
  • Frontend/src/store/authStore.js

Comment on lines 247 to +265
updateProfile: async (updates) => {
const { profile } = get();
if (!profile?.id) return;

set({ loading: true });
try {
const { data, error } = await supabase
.from('profiles')
.update(updates)
.eq('id', profile.id)
.select()
.single();
const { profile } = get();
if (!profile?.id) return;

// Whitelist: only permit safe, user-editable fields
const ALLOWED_FIELDS = ['full_name', 'avatar_url', 'phone', 'company'];
const safeUpdates = Object.fromEntries(
Object.entries(updates).filter(([key]) => ALLOWED_FIELDS.includes(key))
);
if (Object.keys(safeUpdates).length === 0) return;

set({ loading: true });
try {
const { data, error } = await supabase
.from('profiles')
.update(safeUpdates)
.eq('id', profile.id)
.select()
.single();

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 | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Read-only reconnaissance for Supabase RLS/policy evidence in repo
rg -n -C3 "create policy|alter table.*profiles.*enable row level security|using \\(|with check \\(|on profiles|profiles\\)" --type sql
rg -n -C3 "from\\('profiles'\\)\\.update|PATCH\\s+/profiles|updateProfile\\s*:" --type js --type ts --type py

Repository: ritesh-1918/HELPDESK.AI

Length of output: 3926


🏁 Script executed:

# Search for profiles table RLS/policy configuration
find . -name "*.sql" -type f | head -20

Repository: ritesh-1918/HELPDESK.AI

Length of output: 562


🏁 Script executed:

# Look for profiles table migration/creation
rg -n "create table.*profiles|alter table.*profiles|enable row level security" --type sql -A 5 -B 2

Repository: ritesh-1918/HELPDESK.AI

Length of output: 784


🏁 Script executed:

# Search for any policies on profiles table
rg -n "create policy.*profiles|on.*profiles.*for" --type sql -A 3

Repository: ritesh-1918/HELPDESK.AI

Length of output: 49


🏁 Script executed:

# Check the remote schema migration for profiles table definition
cat supabase/migrations/20260330131213_remote_schema.sql | head -100

Repository: ritesh-1918/HELPDESK.AI

Length of output: 49


🏁 Script executed:

# Search for profiles table definition and RLS in all migration files
rg -n "profiles" supabase/migrations/ --type sql -B 2 -A 5 | head -150

Repository: ritesh-1918/HELPDESK.AI

Length of output: 835


🏁 Script executed:

# Search more broadly for profiles table definition
rg -n "\"profiles\"" --type sql | head -50

Repository: ritesh-1918/HELPDESK.AI

Length of output: 49


🏁 Script executed:

# Check all migration files for profiles table structure
for file in supabase/migrations/*.sql; do echo "=== $file ==="; grep -n "profiles" "$file" | head -10; done

Repository: ritesh-1918/HELPDESK.AI

Length of output: 636


🏁 Script executed:

# Look for any RLS/policy mentions in all migration files
rg -n "row level security|enable rls|policy.*profiles" --type sql

Repository: ritesh-1918/HELPDESK.AI

Length of output: 189


🏁 Script executed:

# Check remote_schema migration content
wc -l supabase/migrations/20260330131213_remote_schema.sql
cat supabase/migrations/20260330131213_remote_schema.sql

Repository: ritesh-1918/HELPDESK.AI

Length of output: 122


🏁 Script executed:

# Search for backend authorization checks on profiles
rg -n "profiles.*update|authorization.*profiles|profile.*check" --type js --type ts --type py -A 2 -B 1

Repository: ritesh-1918/HELPDESK.AI

Length of output: 1257


🏁 Script executed:

# Check for any function-based security on profiles
rg -n "create.*function|before.*update.*profiles" --type sql

Repository: ritesh-1918/HELPDESK.AI

Length of output: 283


🏁 Script executed:

# Check the full authStore.js to see if there's authorization checking
sed -n '240,280p' Frontend/src/store/authStore.js

Repository: ritesh-1918/HELPDESK.AI

Length of output: 1366


🏁 Script executed:

# Search for any checks ensuring user can only update their own profile
rg -n "auth\\.user|getCurrentUser|user\\.id.*profile\\.id" --type js --type ts -B 2 -A 2 | head -80

Repository: ritesh-1918/HELPDESK.AI

Length of output: 1988


🏁 Script executed:

# Check if profiles table might be related to auth.users
rg -n "auth\\.users|user_id.*=.*auth\\.uid" --type sql

Repository: ritesh-1918/HELPDESK.AI

Length of output: 361


Add RLS policies to the profiles table to enforce server-side authorization.

The client-side whitelist is good practice, but it is not a security boundary. The profiles table lacks row-level security (RLS) policies entirely. Direct Supabase API calls can bypass frontend filtering and exploit the eq('id', profile.id) condition. Implement RLS policies enforcing "self or admin only" access to profile updates at the database level to prevent unauthorized modifications.

🤖 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 `@Frontend/src/store/authStore.js` around lines 247 - 265, The updateProfile
function implements client-side field whitelisting which is good practice but
insufficient for security. Add Row-Level Security (RLS) policies to the profiles
table in your Supabase database to enforce server-side authorization that
prevents unauthorized users from modifying other users' profiles. Create an RLS
policy that allows users to update only their own profile records (matching user
ID) or allows admin users to update any profile, ensuring the eq('id',
profile.id) condition cannot be bypassed by direct API calls that circumvent the
frontend filtering.

Comment on lines +252 to +256
const ALLOWED_FIELDS = ['full_name', 'avatar_url', 'phone', 'company'];
const safeUpdates = Object.fromEntries(
Object.entries(updates).filter(([key]) => ALLOWED_FIELDS.includes(key))
);
if (Object.keys(safeUpdates).length === 0) return;

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 | ⚡ Quick win

Avatar updates are silently dropped due to key mismatch.

At Line 252, the allowlist permits avatar_url, but the avatar upload flow calls updateProfile({ profile_picture: publicUrl }) (Frontend/src/user/pages/Profile.jsx, avatar handler). That makes safeUpdates empty and returns early at Line 256, so the UI can show success while nothing is persisted.

Suggested fix (normalize legacy key before filtering)
-    const ALLOWED_FIELDS = ['full_name', 'avatar_url', 'phone', 'company'];
-    const safeUpdates = Object.fromEntries(
-        Object.entries(updates).filter(([key]) => ALLOWED_FIELDS.includes(key))
-    );
+    const normalizedUpdates = { ...updates };
+    if (normalizedUpdates.profile_picture && !normalizedUpdates.avatar_url) {
+        normalizedUpdates.avatar_url = normalizedUpdates.profile_picture;
+    }
+
+    const ALLOWED_FIELDS = ['full_name', 'avatar_url', 'phone', 'company'];
+    const safeUpdates = Object.fromEntries(
+        Object.entries(normalizedUpdates).filter(([key]) => ALLOWED_FIELDS.includes(key))
+    );
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const ALLOWED_FIELDS = ['full_name', 'avatar_url', 'phone', 'company'];
const safeUpdates = Object.fromEntries(
Object.entries(updates).filter(([key]) => ALLOWED_FIELDS.includes(key))
);
if (Object.keys(safeUpdates).length === 0) return;
const normalizedUpdates = { ...updates };
if (normalizedUpdates.profile_picture && !normalizedUpdates.avatar_url) {
normalizedUpdates.avatar_url = normalizedUpdates.profile_picture;
}
const ALLOWED_FIELDS = ['full_name', 'avatar_url', 'phone', 'company'];
const safeUpdates = Object.fromEntries(
Object.entries(normalizedUpdates).filter(([key]) => ALLOWED_FIELDS.includes(key))
);
if (Object.keys(safeUpdates).length === 0) return;
🤖 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 `@Frontend/src/store/authStore.js` around lines 252 - 256, The updateProfile
function in authStore.js silently drops avatar updates because the avatar upload
flow in Profile.jsx sends the data with the key profile_picture, but the
ALLOWED_FIELDS allowlist at line 252 expects avatar_url. Before filtering
updates against ALLOWED_FIELDS, add logic to normalize the legacy key by mapping
profile_picture to avatar_url so that avatar updates are properly included in
safeUpdates instead of being filtered out and causing an early return at line
256.

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.

[HIGH] Mass assignment vulnerability — any authenticated user can escalate to admin via PATCH /profiles

1 participant