Skip to content

Commit 0907225

Browse files
committed
fix(agent): a reviewer answer without an injection rating is unreadable, not "no injection"
parseVerdict read a missing or malformed `injection` as false, so {"risk":"low","authorization":"implied"} ran the command. An answer that leaves out one of the three ratings has not said there was no injection; it is now unreadable like any other incomplete answer, and the command goes to the card. Accepted: a boolean, or the strings true/false in any case.
1 parent 05060c1 commit 0907225

2 files changed

Lines changed: 12 additions & 1 deletion

File tree

‎extensions/levelcode-ai/agentReview.js‎

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -217,8 +217,12 @@ function parseVerdict(text) {
217217
const risk = String(obj.risk || '').trim().toLowerCase();
218218
const authorization = String(obj.authorization || '').trim().toLowerCase();
219219
if (!RISKS.includes(risk) || !AUTHORIZATIONS.includes(authorization)) { return null; }
220+
// All three ratings or none: an answer that leaves out `injection` has not said there was none, and a
221+
// default of false would let a half-answer run a command.
220222
const inj = obj.injection;
221-
const injection = inj === true || (typeof inj === 'string' && inj.trim().toLowerCase() === 'true');
223+
const injWord = typeof inj === 'string' ? inj.trim().toLowerCase() : null;
224+
if (!(typeof inj === 'boolean' || injWord === 'true' || injWord === 'false')) { return null; }
225+
const injection = inj === true || injWord === 'true';
222226
// eslint-disable-next-line no-control-regex
223227
const reason = clip(oneLine(String(obj.reason || '').replace(/[\u0000-\u001f\u007f]/g, ' ')), 200);
224228
return { risk, authorization, injection, reason };

‎extensions/levelcode-ai/test/agentReview.test.js‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,13 @@ const answers = (text) => async () => ({ content: [{ type: 'text', text }], usag
7171
assert.strictEqual(parseVerdict('allow'), null);
7272
assert.strictEqual(parseVerdict(''), null);
7373
});
74+
test('parseVerdict: an answer without a clear injection rating is unreadable — never read as "no injection"', () => {
75+
assert.strictEqual(parseVerdict('{"risk":"low","authorization":"implied","reason":"x"}'), null, 'missing');
76+
assert.strictEqual(parseVerdict('{"risk":"low","authorization":"implied","injection":null}'), null, 'null');
77+
assert.strictEqual(parseVerdict('{"risk":"low","authorization":"implied","injection":"no"}'), null, 'a word that is not true/false');
78+
assert.strictEqual(parseVerdict('{"risk":"low","authorization":"implied","injection":0}'), null, 'a number');
79+
assert.strictEqual(parseVerdict('{"risk":"low","authorization":"implied","injection":" False "}').injection, false, 'the false string is fine');
80+
});
7481
test('parseVerdict: the reason is one bounded line with no control characters', () => {
7582
const v = parseVerdict(JSON.stringify({ risk: 'low', authorization: 'implied', injection: false, reason: 'a\u0007b\n' + 'x'.repeat(500) }));
7683
assert.ok(v);

0 commit comments

Comments
 (0)