Compare commits

...
Author SHA1 Message Date
Sonarly Claude Code 04c2c3c3e5 fix(permissions): skip field update permission check for insert when field not in user input
https://sonarly.com/issue/20520?type=bug

Record creation fails with PERMISSION_DENIED when any field used in row-level permissions has Edit disabled, because the insert code path shares the same field-level update permission check as the update code path.

Fix: Separated the `insert` case from the `update` case in `validateOperationIsPermittedOrThrow` in `permissions.utils.ts`.

**Problem:** The `case 'insert':` fell through to `case 'update':`, causing both operations to run `validateUpdateFieldPermissionOrThrow`. For inserts, `updatedColumns` contains ALL columns in the SQL INSERT (derived from TypeORM's `valuesSet`), including join columns for relation fields that the user never explicitly set. If any of these fields had `canUpdate: false` (Edit disabled), the entire insert was rejected.

**Fix:** The `insert` case now has its own block that:
1. Checks `canUpdateObjectRecords` (unchanged — object-level permission)
2. Validates read field permissions on returned columns (unchanged)
3. Skips `validateUpdateFieldPermissionOrThrow` — field-level update restrictions should not apply to record creation

This is safe because:
- Row-level security constraints are validated separately in `WorkspaceInsertQueryBuilder.validateRLSPredicatesForInsert()`, which checks that the newly created record satisfies RLS predicates (e.g., "Owner is (Me) or Owner is Empty")
- The `update` case is completely unchanged and continues to enforce field-level update permissions
- Users can still only create records that satisfy their RLS predicates
2026-04-01 20:01:07 +00:00
@@ -139,6 +139,19 @@ export const validateOperationIsPermittedOrThrow = ({
});
break;
case 'insert':
if (!permissionsForEntity?.canUpdateObjectRecords) {
throw new PermissionsException(
PermissionsExceptionMessage.PERMISSION_DENIED,
PermissionsExceptionCode.PERMISSION_DENIED,
);
}
validateReadFieldPermissionOrThrow({
restrictedFields: permissionsForEntity.restrictedFields,
selectedColumns,
columnNameToFieldMetadataIdMap,
});
break;
case 'update':
if (!permissionsForEntity?.canUpdateObjectRecords) {
throw new PermissionsException(