From c4975411d946201c610b999afcb03214a594c470 Mon Sep 17 00:00:00 2001 From: Manuel Trezza <5673677+mtrezza@users.noreply.github.com> Date: Fri, 6 Mar 2026 18:07:57 +0000 Subject: [PATCH 1/2] feat --- Parse-Dashboard/app.js | 37 ++++-- src/lib/tests/AgentAuth.test.js | 203 ++++++++++++++++++++++++++++++++ 2 files changed, 232 insertions(+), 8 deletions(-) diff --git a/Parse-Dashboard/app.js b/Parse-Dashboard/app.js index c22df1af93..337cad0ef9 100644 --- a/Parse-Dashboard/app.js +++ b/Parse-Dashboard/app.js @@ -87,6 +87,32 @@ module.exports = function(config, options) { cookieSessionStore: options.cookieSessionStore }); + /** + * Checks whether a request is from localhost. + */ + function isLocalRequest(req) { + return req.connection.remoteAddress === '127.0.0.1' || + req.connection.remoteAddress === '::ffff:127.0.0.1' || + req.connection.remoteAddress === '::1'; + } + + /** + * Middleware that enforces remote access restrictions: + * - Requires HTTPS for remote requests (unless allowInsecureHTTP is set) + * - Requires users to be configured for remote access (unless dev mode is enabled) + */ + function enforceRemoteAccessRestrictions(req, res, next) { + if (!options.dev && !isLocalRequest(req)) { + if (!req.secure && !options.allowInsecureHTTP) { + return res.status(403).json({ error: 'Parse Dashboard can only be remotely accessed via HTTPS' }); + } + if (!users) { + return res.status(401).json({ error: 'Configure a user to access Parse Dashboard remotely' }); + } + } + next(); + } + // CSRF error handler app.use(function (err, req, res, next) { if (err.code !== 'EBADCSRFTOKEN') {return next(err)} @@ -109,13 +135,7 @@ module.exports = function(config, options) { agent: config.agent, }; - //Based on advice from Doug Wilson here: - //https://github.com/expressjs/express/issues/2518 - const requestIsLocal = - req.connection.remoteAddress === '127.0.0.1' || - req.connection.remoteAddress === '::ffff:127.0.0.1' || - req.connection.remoteAddress === '::1'; - if (!options.dev && !requestIsLocal) { + if (!options.dev && !isLocalRequest(req)) { if (!req.secure && !options.allowInsecureHTTP) { //Disallow HTTP requests except on localhost, to prevent the master key from being transmitted in cleartext return res.send({ success: false, error: 'Parse Dashboard can only be remotely accessed via HTTPS' }); @@ -329,8 +349,9 @@ module.exports = function(config, options) { } } - // Agent API endpoint — middleware chain: auth check (401) → CSRF validation (403) → handler + // Agent API endpoint — middleware chain: remote access guard → auth check (401) → CSRF validation (403) → handler app.post('/apps/:appId/agent', + enforceRemoteAccessRestrictions, (req, res, next) => { if (users && (!req.user || !req.user.isAuthenticated)) { return res.status(401).json({ error: 'Unauthorized' }); diff --git a/src/lib/tests/AgentAuth.test.js b/src/lib/tests/AgentAuth.test.js index b0cc65a38b..92a322c918 100644 --- a/src/lib/tests/AgentAuth.test.js +++ b/src/lib/tests/AgentAuth.test.js @@ -373,3 +373,206 @@ describe('Agent endpoint security', () => { expect(res.status).not.toBe(403); }); }); + +// --------------------------------------------------------------- +// No-user mode — remote access guard +// --------------------------------------------------------------- + +describe('Agent endpoint no-user mode', () => { + let server; + let port; + + const noUserConfig = { + apps: [ + { + serverURL: 'http://localhost:1337/parse', + appId: 'testAppId', + masterKey: 'testMasterKey', + appName: 'TestApp', + }, + ], + // No users configured + agent: { + models: [ + { + name: 'test-model', + provider: 'openai', + model: 'gpt-4', + apiKey: 'fake-api-key-for-testing', + }, + ], + }, + }; + + beforeAll((done) => { + const parseDashboard = require('../../../Parse-Dashboard/app.js'); + // dev: false to enable the remote access guard + const dashboardApp = parseDashboard(noUserConfig, { + cookieSessionSecret: SESSION_SECRET, + }); + + const parentApp = express(); + parentApp.use('/', dashboardApp); + + server = parentApp.listen(0, () => { + port = server.address().port; + done(); + }); + }); + + afterAll((done) => { + if (server) { + server.close(done); + } else { + done(); + } + }); + + it('allows local requests to the agent endpoint in no-user mode', async () => { + // Requests to 127.0.0.1 are local, so they should pass the guard + const res = await makeRequest(port, { + method: 'POST', + path: '/apps/TestApp/agent', + body: agentBody(), + }); + // Should not be blocked by the no-user guard (may fail at CSRF or later) + expect(res.status).not.toBe(401); + }); +}); + +describe('Agent endpoint no-user mode — remote requests', () => { + let server; + let port; + + const noUserConfig = { + apps: [ + { + serverURL: 'http://localhost:1337/parse', + appId: 'testAppId', + masterKey: 'testMasterKey', + appName: 'TestApp', + }, + ], + agent: { + models: [ + { + name: 'test-model', + provider: 'openai', + model: 'gpt-4', + apiKey: 'fake-api-key-for-testing', + }, + ], + }, + }; + + beforeAll((done) => { + const parseDashboard = require('../../../Parse-Dashboard/app.js'); + const dashboardApp = parseDashboard(noUserConfig, { + cookieSessionSecret: SESSION_SECRET, + }); + + const parentApp = express(); + // Spoof a non-local remote address before the dashboard middleware + parentApp.use((req, _res, next) => { + Object.defineProperty(req.connection, 'remoteAddress', { + value: '203.0.113.1', + writable: true, + configurable: true, + }); + next(); + }); + parentApp.use('/', dashboardApp); + + server = parentApp.listen(0, () => { + port = server.address().port; + done(); + }); + }); + + afterAll((done) => { + if (server) { + server.close(done); + } else { + done(); + } + }); + + it('returns 403 for remote non-HTTPS requests to the agent endpoint', async () => { + const res = await makeRequest(port, { + method: 'POST', + path: '/apps/TestApp/agent', + body: agentBody(), + }); + expect(res.status).toBe(403); + expect(res.body.error).toBe('Parse Dashboard can only be remotely accessed via HTTPS'); + }); +}); + +describe('Agent endpoint no-user mode — remote requests with allowInsecureHTTP', () => { + let server; + let port; + + const noUserConfig = { + apps: [ + { + serverURL: 'http://localhost:1337/parse', + appId: 'testAppId', + masterKey: 'testMasterKey', + appName: 'TestApp', + }, + ], + agent: { + models: [ + { + name: 'test-model', + provider: 'openai', + model: 'gpt-4', + apiKey: 'fake-api-key-for-testing', + }, + ], + }, + }; + + beforeAll((done) => { + const parseDashboard = require('../../../Parse-Dashboard/app.js'); + const dashboardApp = parseDashboard(noUserConfig, { + cookieSessionSecret: SESSION_SECRET, + allowInsecureHTTP: true, + }); + + const parentApp = express(); + // Spoof a non-local remote address before the dashboard middleware + parentApp.use((req, _res, next) => { + Object.defineProperty(req.connection, 'remoteAddress', { + value: '203.0.113.1', + writable: true, + configurable: true, + }); + next(); + }); + parentApp.use('/', dashboardApp); + + server = parentApp.listen(0, () => { + port = server.address().port; + done(); + }); + }); + + afterAll((done) => { + if (server) { + server.close(done); + } else { + done(); + } + }); + + it('returns 401 for remote requests to the agent endpoint in no-user mode when HTTPS is bypassed', async () => { + const res = await makeRequest(port, { + method: 'POST', + path: '/apps/TestApp/agent', + body: agentBody(), + }); + expect(res.status).toBe(401); + expect(res.body.error).toBe('Configure a user to access Parse Dashboard remotely'); + }); +}); From 5b5505ba96228cde1ad6f9bc7e2004b0f79ffbbe Mon Sep 17 00:00:00 2001 From: Manuel Trezza <5673677+mtrezza@users.noreply.github.com> Date: Fri, 6 Mar 2026 18:10:54 +0000 Subject: [PATCH 2/2] lint --- Parse-Dashboard/app.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Parse-Dashboard/app.js b/Parse-Dashboard/app.js index 337cad0ef9..8c2845d8ac 100644 --- a/Parse-Dashboard/app.js +++ b/Parse-Dashboard/app.js @@ -199,7 +199,7 @@ module.exports = function(config, options) { //They didn't provide auth, and have configured the dashboard to not need auth //(ie. didn't supply usernames and passwords) - if (requestIsLocal || options.dev) { + if (isLocalRequest(req) || options.dev) { //Allow no-auth access on localhost only, if they have configured the dashboard to not need auth await Promise.all( response.apps.map(async (app) => {