From 06c441ce3dcbd28619eff811c90b455a10718700 Mon Sep 17 00:00:00 2001 From: Kris Zyp Date: Tue, 2 Jun 2026 21:37:39 -0600 Subject: [PATCH 1/4] feat: expose urlPath in deploy_component operation and CLI Components registered via package can now set urlPath at deploy time. The value is persisted in the root config alongside package/install, where the OptionsWatcher already reads and scopes it per component. Co-Authored-By: Claude Sonnet 4.6 --- components/operations.js | 1 + components/operationsValidation.js | 1 + 2 files changed, 2 insertions(+) diff --git a/components/operations.js b/components/operations.js index b2dbba014a..2434b4bebc 100644 --- a/components/operations.js +++ b/components/operations.js @@ -384,6 +384,7 @@ async function deployComponent(req) { allowInstallScripts: req.install_allow_scripts, }; } + if (req.urlPath !== undefined) applicationConfig.urlPath = req.urlPath; await configUtils.addConfig(req.project, applicationConfig); } diff --git a/components/operationsValidation.js b/components/operationsValidation.js index d5d43190f9..751464eec5 100644 --- a/components/operationsValidation.js +++ b/components/operationsValidation.js @@ -242,6 +242,7 @@ function deployComponentValidator(req) { install_timeout: Joi.number().optional(), install_allow_scripts: Joi.boolean().optional(), force: Joi.boolean().optional(), + urlPath: Joi.string().optional(), }); return validator.validateBySchema(req, deployProjSchema); From 6b2982dbec47f20f1c3af4ff450e0ac3676bf9d6 Mon Sep 17 00:00:00 2001 From: Kris Zyp Date: Tue, 2 Jun 2026 22:03:41 -0600 Subject: [PATCH 2/4] refine: tighten urlPath validation and add unit tests - Rejects urlPath containing '..' at the API boundary (prevents invalid config being written to disk before downstream validation fires) - Rejects empty urlPath strings - Requires package when urlPath is present (urlPath is only persisted in the package-based config path; payload deployments set it in their own harper-config.yaml) - Adds deployComponentValidator unit tests covering accept, reject, and path-traversal cases Co-Authored-By: Claude Sonnet 4.6 --- components/operationsValidation.js | 11 ++++-- .../operationsValidation.test.js | 35 +++++++++++++++++++ 2 files changed, 44 insertions(+), 2 deletions(-) diff --git a/components/operationsValidation.js b/components/operationsValidation.js index 751464eec5..0f72ed45f2 100644 --- a/components/operationsValidation.js +++ b/components/operationsValidation.js @@ -242,8 +242,15 @@ function deployComponentValidator(req) { install_timeout: Joi.number().optional(), install_allow_scripts: Joi.boolean().optional(), force: Joi.boolean().optional(), - urlPath: Joi.string().optional(), - }); + urlPath: Joi.string() + .min(1) + .custom((value, helpers) => { + if (value.includes('..')) return helpers.error('any.invalid'); + return value; + }) + .optional() + .messages({ 'any.invalid': 'urlPath must not contain ".."' }), + }).with('urlPath', 'package'); return validator.validateBySchema(req, deployProjSchema); } diff --git a/unitTests/server/fastifyRoutes/operationsValidation.test.js b/unitTests/server/fastifyRoutes/operationsValidation.test.js index cd4f376b6b..86eeb90d80 100644 --- a/unitTests/server/fastifyRoutes/operationsValidation.test.js +++ b/unitTests/server/fastifyRoutes/operationsValidation.test.js @@ -222,4 +222,39 @@ describe('Test operationsValidation module', () => { expect(result.message).to.equal('Project name can only contain alphanumeric, dash and underscores characters'); }); }); + + describe('Test deployComponentValidator function', () => { + it('accepts valid package-based deploy request', () => { + const result = validator.deployComponentValidator({ project: 'my-app', package: '@scope/pkg' }); + expect(result).to.be.undefined; + }); + + it('accepts urlPath alongside package', () => { + const result = validator.deployComponentValidator({ project: 'my-app', package: '@scope/pkg', urlPath: '/api' }); + expect(result).to.be.undefined; + }); + + it('rejects urlPath without package', () => { + const result = validator.deployComponentValidator({ project: 'my-app', urlPath: '/api' }); + expect(result).to.not.be.null; + expect(result.message).to.include('urlPath'); + }); + + it('rejects urlPath containing ..', () => { + const result = validator.deployComponentValidator({ project: 'my-app', package: 'pkg', urlPath: '../etc/passwd' }); + expect(result).to.not.be.null; + expect(result.message).to.include('"'); + }); + + it('rejects empty urlPath', () => { + const result = validator.deployComponentValidator({ project: 'my-app', package: 'pkg', urlPath: '' }); + expect(result).to.not.be.null; + }); + + it('rejects missing project', () => { + const result = validator.deployComponentValidator({ package: 'pkg' }); + expect(result).to.not.be.null; + expect(result.message).to.include('project'); + }); + }); }); From f8a8bb351b9000a4cc6f28553514ecfe794997aa Mon Sep 17 00:00:00 2001 From: Kris Zyp Date: Tue, 2 Jun 2026 22:04:09 -0600 Subject: [PATCH 3/4] test: use expect(result).to.be.ok in rejection assertions Avoids false-positive if validateBySchema returns undefined (success). Co-Authored-By: Claude Sonnet 4.6 --- .../server/fastifyRoutes/operationsValidation.test.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/unitTests/server/fastifyRoutes/operationsValidation.test.js b/unitTests/server/fastifyRoutes/operationsValidation.test.js index 86eeb90d80..276a2b3b68 100644 --- a/unitTests/server/fastifyRoutes/operationsValidation.test.js +++ b/unitTests/server/fastifyRoutes/operationsValidation.test.js @@ -236,24 +236,24 @@ describe('Test operationsValidation module', () => { it('rejects urlPath without package', () => { const result = validator.deployComponentValidator({ project: 'my-app', urlPath: '/api' }); - expect(result).to.not.be.null; + expect(result).to.be.ok; expect(result.message).to.include('urlPath'); }); it('rejects urlPath containing ..', () => { const result = validator.deployComponentValidator({ project: 'my-app', package: 'pkg', urlPath: '../etc/passwd' }); - expect(result).to.not.be.null; - expect(result.message).to.include('"'); + expect(result).to.be.ok; + expect(result.message).to.include('urlPath'); }); it('rejects empty urlPath', () => { const result = validator.deployComponentValidator({ project: 'my-app', package: 'pkg', urlPath: '' }); - expect(result).to.not.be.null; + expect(result).to.be.ok; }); it('rejects missing project', () => { const result = validator.deployComponentValidator({ package: 'pkg' }); - expect(result).to.not.be.null; + expect(result).to.be.ok; expect(result.message).to.include('project'); }); }); From 22685f88101201b1fcb547af4b6c1e2c90319b7c Mon Sep 17 00:00:00 2001 From: Kris Zyp Date: Mon, 8 Jun 2026 15:19:29 -0600 Subject: [PATCH 4/4] chore(format): prettier-fix operationsValidation.test.js Co-Authored-By: Claude Opus 4.7 --- unitTests/server/fastifyRoutes/operationsValidation.test.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/unitTests/server/fastifyRoutes/operationsValidation.test.js b/unitTests/server/fastifyRoutes/operationsValidation.test.js index 276a2b3b68..748e91cade 100644 --- a/unitTests/server/fastifyRoutes/operationsValidation.test.js +++ b/unitTests/server/fastifyRoutes/operationsValidation.test.js @@ -241,7 +241,11 @@ describe('Test operationsValidation module', () => { }); it('rejects urlPath containing ..', () => { - const result = validator.deployComponentValidator({ project: 'my-app', package: 'pkg', urlPath: '../etc/passwd' }); + const result = validator.deployComponentValidator({ + project: 'my-app', + package: 'pkg', + urlPath: '../etc/passwd', + }); expect(result).to.be.ok; expect(result.message).to.include('urlPath'); });