From ff11f79324a90e1384af43470c3288114659063c Mon Sep 17 00:00:00 2001 From: Ben Hardill Date: Tue, 21 Jul 2026 11:56:08 +0100 Subject: [PATCH 1/4] Ensure only diff snapshots in the same project fixes FlowFuse/security#126 --- forge/routes/api/project.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/forge/routes/api/project.js b/forge/routes/api/project.js index 96fae12041..b58f08633c 100644 --- a/forge/routes/api/project.js +++ b/forge/routes/api/project.js @@ -1540,6 +1540,13 @@ module.exports = async function (app) { code: 'not_found', error: 'Snapshot not found' }) + } else { + if (targetSnapshot.ProjectId !== request.project.id) { + return reply.code(404).send({ + code: 'not_found', + error: 'Snapshot not found' + }) + } } break } From 5cb876f2a9e3a8aa4991976ef7cd67de902a4db7 Mon Sep 17 00:00:00 2001 From: Ben Hardill Date: Tue, 21 Jul 2026 13:54:56 +0100 Subject: [PATCH 2/4] add test --- test/unit/forge/routes/api/project_spec.js | 47 ++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/test/unit/forge/routes/api/project_spec.js b/test/unit/forge/routes/api/project_spec.js index cfe6871021..dd0d8dbb63 100644 --- a/test/unit/forge/routes/api/project_spec.js +++ b/test/unit/forge/routes/api/project_spec.js @@ -3688,6 +3688,53 @@ describe('Project API', function () { cookies: { sid: TestObjects.tokens.alice } }) + try { + response.statusCode.should.equal(404) + const body = response.json() + body.should.have.property('code', 'not_found') + invokeStub.called.should.equal(false) + } finally { + buildSnapshotStub.restore() + invokeStub.restore() + byIdStub.restore() + snapshotByIdStub.restore() + app.license = originalLicense + } + }) + it('returns 404 when target is in a different team', async function () { + const buildSnapshotStub = sinon.stub(app.db.controllers.ProjectSnapshot, 'buildProjectSnapshot').resolves({ + ProjectId: 99, + settings: {}, + flows: { flows: [], credentials: {} } + }) + const invokeStub = sinon.stub(app.db.controllers.Assistant, 'invokeLLM').resolves({ transactionId: 'x', data: {} }) + + const originalLicense = app.license + app.license = { get: (k) => (k === 'tier' ? 'enterprise' : undefined) } + const originalProjectById = app.db.models.Project.byId + const byIdStub = sinon.stub(app.db.models.Project, 'byId').callsFake(async function (id, opts) { + const project = await originalProjectById.call(this, id, opts) + if (project && project.Team) { + project.Team.getTeamType = async () => ({ + getFeatureProperty: (name, def) => (name === 'generatedSnapshotDescription' ? true : def) + }) + } + return project + }) + + const snapshotByIdStub = sinon.stub(app.db.models.ProjectSnapshot, 'byId').resolves({ + ProjectId: 88, + settings: {}, + flows: { flows: [], credentials: {} } + }) + + const response = await app.inject({ + method: 'POST', + url: `/api/v1/projects/${TestObjects.project1.id}/generate/snapshot-description`, + payload: { target: '88' }, + cookies: { sid: TestObjects.tokens.alice } + }) + try { response.statusCode.should.equal(404) const body = response.json() From 1aa755d79a26d6b2668b997117992ad7dd2c0455 Mon Sep 17 00:00:00 2001 From: Ben Hardill Date: Tue, 21 Jul 2026 14:57:49 +0100 Subject: [PATCH 3/4] Fix other test --- test/unit/forge/routes/api/project_spec.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/unit/forge/routes/api/project_spec.js b/test/unit/forge/routes/api/project_spec.js index dd0d8dbb63..28298018eb 100644 --- a/test/unit/forge/routes/api/project_spec.js +++ b/test/unit/forge/routes/api/project_spec.js @@ -3631,7 +3631,8 @@ describe('Project API', function () { toJSON: () => ({ settings: { env: { OLD: 'value' }, modules: { oldmod: '0.1.0' } }, flows: { flows: [{ id: 'old' }], credentials: {} } - }) + }), + ProjectId: TestObjects.project1.id } } return originalSnapshotById.call(this, sid) From b8692fa9e4bfe7901142a26277e928ab42e3fa67 Mon Sep 17 00:00:00 2001 From: Ben Hardill Date: Tue, 21 Jul 2026 17:23:58 +0100 Subject: [PATCH 4/4] Update test/unit/forge/routes/api/project_spec.js Co-authored-by: Andrea Palmieri <76187074+andypalmi@users.noreply.github.com> --- test/unit/forge/routes/api/project_spec.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/unit/forge/routes/api/project_spec.js b/test/unit/forge/routes/api/project_spec.js index 28298018eb..d7e9a1bfc2 100644 --- a/test/unit/forge/routes/api/project_spec.js +++ b/test/unit/forge/routes/api/project_spec.js @@ -3702,7 +3702,7 @@ describe('Project API', function () { app.license = originalLicense } }) - it('returns 404 when target is in a different team', async function () { + it('returns 404 when target snapshot belongs to a different instance', async function () { const buildSnapshotStub = sinon.stub(app.db.controllers.ProjectSnapshot, 'buildProjectSnapshot').resolves({ ProjectId: 99, settings: {},