iterate-plugin 3.5.2 → 3.5.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/transcript.ts CHANGED
@@ -53,6 +53,11 @@ const MAX_TIMELINE = 500
53
53
  * grow the manifest payload without limit). */
54
54
  const MAX_FIXES = 200
55
55
 
56
+ /** Max round number accepted by the builder. A model-authored/manifest-backed
57
+ * round value is attacker-influenced JSON: `roundStart(1e9)` / `snapshotConvergence`
58
+ * would otherwise preallocate arrays of that size and OOM the host. */
59
+ const MAX_ROUNDS = 1000
60
+
56
61
  /** Thresholds applied when reducing a string list under a cap. */
57
62
  function clampStringList(source: string[], cap: number): string[] {
58
63
  const out: string[] = []
@@ -207,9 +212,13 @@ export class ReviewTranscriptBuilder {
207
212
  /** Open a review round, capturing the current round index. */
208
213
  roundStart(round: number, maxRounds?: number): void {
209
214
  const r = typeof round === 'number' && Number.isFinite(round) ? Math.floor(round) : 1
210
- this.round = r > 0 ? r : 1
215
+ // Clamp to MAX_ROUNDS: `this.rounds.length < this.round` below preallocates
216
+ // an array of size `round` — an unbounded model-controlled value (e.g. 1e9)
217
+ // would OOM the host. Values above the cap are folded into the cap so the
218
+ // real rounds are never silently dropped.
219
+ this.round = Math.min(r > 0 ? r : 1, MAX_ROUNDS)
211
220
  if (typeof maxRounds === 'number' && Number.isFinite(maxRounds) && maxRounds >= 0) {
212
- this.maxRounds = Math.floor(maxRounds)
221
+ this.maxRounds = Math.min(Math.floor(maxRounds), MAX_ROUNDS)
213
222
  }
214
223
  while (this.rounds.length < this.round) {
215
224
  this.rounds.push({ round: this.rounds.length + 1, threads: [] })
@@ -294,7 +303,10 @@ export class ReviewTranscriptBuilder {
294
303
 
295
304
  /** Record a round's new-finding count for the convergence series. */
296
305
  snapshotConvergence(round: number, newCount: number): void {
297
- const r = typeof round === 'number' && Number.isFinite(round) ? Math.floor(round) : 1
306
+ const rawR = typeof round === 'number' && Number.isFinite(round) ? Math.floor(round) : 1
307
+ // Clamp (mirrors roundStart): the loop below preallocates `r` slots, so an
308
+ // unbounded model-controlled round number would OOM the host.
309
+ const r = Math.min(rawR > 0 ? rawR : 1, MAX_ROUNDS)
298
310
  const n = typeof newCount === 'number' && Number.isFinite(newCount) ? newCount : 0
299
311
  while (this.convergence.length < r) this.convergence.push(-1)
300
312
  this.convergence[r - 1] = Math.floor(n)
@@ -312,7 +324,10 @@ export class ReviewTranscriptBuilder {
312
324
  this.fixes.push({
313
325
  id,
314
326
  timestamp: typeof record.timestamp === 'string' ? record.timestamp : isoNow(),
315
- round: typeof record.round === 'number' ? record.round : this.round,
327
+ round:
328
+ typeof record.round === 'number' && Number.isFinite(record.round)
329
+ ? Math.floor(record.round)
330
+ : this.round,
316
331
  file,
317
332
  summary: typeof record.summary === 'string' ? record.summary : '',
318
333
  linesAdded:
@@ -363,7 +378,10 @@ export class ReviewTranscriptBuilder {
363
378
  const type = typeof entry.type === 'string' ? entry.type : 'decision'
364
379
  this.timeline.push({
365
380
  timestamp: typeof entry.timestamp === 'string' ? entry.timestamp : isoNow(),
366
- round: typeof entry.round === 'number' ? entry.round : this.round,
381
+ round:
382
+ typeof entry.round === 'number' && Number.isFinite(entry.round)
383
+ ? Math.floor(entry.round)
384
+ : this.round,
367
385
  type,
368
386
  data:
369
387
  entry.data && typeof entry.data === 'object'