fix: Prevents client from bypassing item ownership check

This commit is contained in:
Antonella Sgarlatta
2026-10-05 14:32:58 -03:00
committed by Antonella Sgarlatta
parent 3125ba9212
commit dfd4e76c9f
3 changed files with 54 additions and 4 deletions
@@ -580,10 +580,10 @@ export class ContainerConfigLoader {
.toConstantValue(
new ItemSaveValidator([
container.get(TYPES.Sync_OwnershipFilter),
container.get(TYPES.Sync_SharedVaultFilter),
container.get(TYPES.Sync_TimeDifferenceFilter),
container.get(TYPES.Sync_ContentTypeFilter),
container.get(TYPES.Sync_ContentFilter),
container.get(TYPES.Sync_SharedVaultFilter),
container.get(TYPES.Sync_SharedVaultSnjsFilter),
]),
)
@@ -6,6 +6,7 @@ import { Item } from '../Item'
import { OwnershipFilter } from './OwnershipFilter'
import { Uuid, ContentType, Dates, Timestamps, UniqueEntityId } from '@standardnotes/domain-core'
import { ItemHash } from '../ItemHash'
import { SharedVaultAssociation } from '../../SharedVault/SharedVaultAssociation'
describe('OwnershipFilter', () => {
let existingItem: Item
@@ -60,7 +61,7 @@ describe('OwnershipFilter', () => {
})
})
it('should deffer to the shared vault filter if the item hash represents a shared vault item or existing item is a shared vault item', async () => {
it('should not defer to the shared vault filter based on a client-supplied shared_vault_uuid alone', async () => {
const itemHash = ItemHash.create({
uuid: '2-3-4',
content_type: ContentType.TYPES.Note,
@@ -81,6 +82,56 @@ describe('OwnershipFilter', () => {
snjsVersion: '2.200.0',
})
expect(result).toEqual({
passed: false,
conflict: {
unsavedItem: itemHash,
type: 'uuid_conflict',
},
})
})
it('should defer to the shared vault filter when the existing item is associated with a shared vault', async () => {
const sharedVaultExistingItem = Item.create(
{
userUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
updatedWithSession: null,
content: 'foobar',
contentType: ContentType.create(ContentType.TYPES.Note).getValue(),
encItemKey: null,
authHash: null,
itemsKeyId: null,
duplicateOf: null,
deleted: false,
dates: Dates.create(new Date(1616164633241311), new Date(1616164633241311)).getValue(),
timestamps: Timestamps.create(1616164633241311, 1616164633241311).getValue(),
sharedVaultAssociation: SharedVaultAssociation.create({
lastEditedBy: Uuid.create('00000000-0000-0000-0000-000000000001').getValue(),
sharedVaultUuid: Uuid.create('00000000-0000-0000-0000-000000000001').getValue(),
}).getValue(),
},
new UniqueEntityId('00000000-0000-0000-0000-000000000000'),
).getValue()
const itemHash = ItemHash.create({
uuid: '2-3-4',
content_type: ContentType.TYPES.Note,
user_uuid: '00000000-0000-0000-0000-000000000001',
content: 'foobar',
created_at: '2020-01-01T00:00:00.000Z',
updated_at: '2020-01-01T00:00:00.000Z',
created_at_timestamp: 123,
updated_at_timestamp: 123,
key_system_identifier: null,
shared_vault_uuid: null,
}).getValue()
const result = await createFilter().check({
userUuid: '00000000-0000-0000-0000-000000000001',
apiVersion: ApiVersion.v20200115,
itemHash,
existingItem: sharedVaultExistingItem,
snjsVersion: '2.200.0',
})
expect(result).toEqual({
passed: true,
})
@@ -6,8 +6,7 @@ import { Uuid } from '@standardnotes/domain-core'
export class OwnershipFilter implements ItemSaveRuleInterface {
async check(dto: ItemSaveValidationDTO): Promise<ItemSaveRuleResult> {
const deferToSharedVaultFilter =
dto.existingItem?.isAssociatedWithASharedVault() || dto.itemHash.representsASharedVaultItem()
const deferToSharedVaultFilter = dto.existingItem?.isAssociatedWithASharedVault() === true
if (deferToSharedVaultFilter) {
return {
passed: true,