From 3d120b1a307dd8b0762d733c383a84bd049714fc Mon Sep 17 00:00:00 2001 From: levineam Date: Fri, 14 Aug 2026 14:33:32 -0400 Subject: [PATCH] fix(coding): validate live PR merge arguments --- .../src/adapters/live/pull-request.js | 22 +++++++++++++++++-- .../jarvos-coding/test/live-adapters.test.js | 15 +++++++++++++ 2 files changed, 35 insertions(+), 2 deletions(-) diff --git a/modules/jarvos-coding/src/adapters/live/pull-request.js b/modules/jarvos-coding/src/adapters/live/pull-request.js index 78b9a20b..a8f617f4 100644 --- a/modules/jarvos-coding/src/adapters/live/pull-request.js +++ b/modules/jarvos-coding/src/adapters/live/pull-request.js @@ -4,6 +4,23 @@ const { run: defaultRun } = require('./run'); const PULL_REQUEST_SCHEMA_VERSION = 'jarvos-coding-live-pull-request/v1'; const DEFAULT_MERGE_METHOD = 'squash'; +const ALLOWED_MERGE_METHODS = new Set(['merge', 'rebase', 'squash']); + +function normalizePullRequestNumber(value) { + const number = String(value); + if (!/^[1-9]\d*$/u.test(number)) { + throw new Error('live merge requires a positive integer pull request number'); + } + return number; +} + +function normalizeMergeMethod(value) { + const method = String(value); + if (!ALLOWED_MERGE_METHODS.has(method)) { + throw new Error('live merge method must be one of: merge, rebase, squash'); + } + return method; +} function parseJson(text) { try { @@ -144,8 +161,9 @@ function createLivePullRequest(options = {}) { if (!repo) throw new Error('live merge requires a repo'); if (!prNumber) throw new Error('live merge requires a pull request number'); - const method = input.mergeMethod || mergeMethod; - const args = ['pr', 'merge', String(prNumber), '--repo', repo, `--${method}`, '--delete-branch']; + const number = normalizePullRequestNumber(prNumber); + const method = normalizeMergeMethod(input.mergeMethod || mergeMethod); + const args = ['pr', 'merge', number, '--repo', repo, `--${method}`, '--delete-branch']; if (dryRun || input.dryRun) { return { schemaVersion: PULL_REQUEST_SCHEMA_VERSION, diff --git a/modules/jarvos-coding/test/live-adapters.test.js b/modules/jarvos-coding/test/live-adapters.test.js index d454ca39..270dd1e7 100644 --- a/modules/jarvos-coding/test/live-adapters.test.js +++ b/modules/jarvos-coding/test/live-adapters.test.js @@ -283,6 +283,21 @@ test('live PR adapter revalidates a merged reattachment by number after branch d assert.equal(calls.length, 1); }); +test('live PR adapter rejects option injection in merge inputs', async () => { + const calls = []; + const adapter = createLivePullRequest({ + repo: 'levineam/jarVOS', + run(command, args) { + calls.push([command, args]); + return { status: 0, stdout: '', stderr: '' }; + }, + }); + + await assert.rejects(() => adapter.merge({ number: '--admin' }), /positive integer pull request number/); + await assert.rejects(() => adapter.merge({ number: 112, mergeMethod: 'admin' }), /must be one of/); + assert.equal(calls.length, 0); +}); + test('live tracker defers close until there is merge evidence', async () => { const tracker = createLivePaperclipTracker({ prLink: {