Skip to content

Commit 645fd00

Browse files
authored
feat: prevent spaces from setting themselves as parent or child (#560)
- Add validation to prevent circular references in space hierarchy - Reject when space.parent equals the space ID - Reject when space.children array contains the space ID - Add comprehensive tests for parent/child validation - Ensure proper error messages for validation failures
1 parent 63e5d5d commit 645fd00

File tree

2 files changed

+36
-0
lines changed

2 files changed

+36
-0
lines changed

src/writer/settings.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,18 @@ export async function validateSpaceSettings(originalSpace: any) {
2828
delete space.hibernated;
2929
delete space.id;
3030

31+
if (space.parent && space.parent === originalSpace.id) {
32+
return Promise.reject('space cannot be its own parent');
33+
}
34+
35+
if (
36+
space.children &&
37+
Array.isArray(space.children) &&
38+
space.children.includes(originalSpace.id)
39+
) {
40+
return Promise.reject('space cannot be its own child');
41+
}
42+
3143
const schemaIsValid: any = snapshot.utils.validateSchema(snapshot.schemas.space, space, {
3244
spaceType,
3345
snapshotEnv: SNAPSHOT_ENV
@@ -72,6 +84,7 @@ export async function verify(body): Promise<any> {
7284
try {
7385
await validateSpaceSettings({
7486
...msg.payload,
87+
id: msg.space,
7588
deleted: space?.deleted,
7689
turbo: space?.turbo
7790
});

test/unit/writer/settings.test.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,29 @@ describe('writer/settings', () => {
131131
verify(editedInput({ validation: { name: 'any' }, strategies: [{ name: 'ticket' }] }))
132132
).rejects.toContain('space with ticket requires voting validation');
133133
});
134+
135+
it('rejects if space tries to set itself as parent', async () => {
136+
return expect(verify(editedInput({ parent: 'fabien.eth' }))).rejects.toContain(
137+
'space cannot be its own parent'
138+
);
139+
});
140+
141+
it('rejects if space tries to include itself in children array', async () => {
142+
return expect(
143+
verify(editedInput({ children: ['other-space.eth', 'fabien.eth', 'another-space.eth'] }))
144+
).rejects.toContain('space cannot be its own child');
145+
});
146+
147+
it('accepts valid parent that is not the space itself', async () => {
148+
return expect(verify(editedInput({ parent: 'parent-space.eth' }))).resolves.toBeUndefined();
149+
});
150+
151+
it('accepts valid children array that does not include the space itself', async () => {
152+
return expect(
153+
verify(editedInput({ children: ['child1.eth', 'child2.eth'] }))
154+
).resolves.toBeUndefined();
155+
});
156+
134157
it.todo('rejects if the submitter does not have permission');
135158
it.todo('rejects if the submitter does not have permission to change admin');
136159
const maxStrategiesForNormalSpace = LIMITS['space.default.strategies_limit'];

0 commit comments

Comments
 (0)