Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 20 additions & 2 deletions modules/jarvos-coding/src/adapters/live/pull-request.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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,
Expand Down
15 changes: 15 additions & 0 deletions modules/jarvos-coding/test/live-adapters.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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: {
Expand Down
Loading