1import { describe, expect, it } from 'vitest';2import { LIMITS } from '@tribes/protocol';3import type { TribeView } from '@tribes/protocol';4import { createScriptHost } from '../src/index.js';5import { makeView } from './fixtures.js';67const emptyView: TribeView = makeView({8 tick: 1,9 mapSize: 4,10 terrain: '.'.repeat(16),11 settlements: [],12 warbands: [],13 threats: [],14});1516// Catastrophic backtracking: exponential in input length. On the Bellard17// QuickJS build the interrupt handler never fires inside libregexp, so this18// would hang the VM past the gas budget; the pinned quickjs-ng default must19// interrupt it. (Security review finding #1 — do not regress the engine.)20const REDOS_BOT = `function tick(state, memory, api) {21 var s = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaX";22 /(a+)+$/.test(s);23}`;2425describe('regex work is gas-metered (quickjs-ng default engine)', () => {26 it('a catastrophic-backtracking regex is interrupted as over-budget, VM survives', async () => {27 const host = await createScriptHost();28 const vm = host.createTribeVm(REDOS_BOT, {29 gasPerTick: LIMITS.GAS_PER_TICK,30 memoryLimitBytes: LIMITS.VM_MEMORY_LIMIT,31 rngSeed: 'redos:0',32 });33 const started = Date.now();34 const out = vm.runTick(emptyView);35 const elapsed = Date.now() - started;36 expect(out.overBudget).toBe(true);37 expect(out.orders).toEqual([]);38 // Interrupted by gas, not by luck: must return quickly, not hang.39 expect(elapsed).toBeLessThan(5_000);40 // VM remains usable for the next tick.41 const again = vm.runTick(emptyView);42 expect(again.overBudget).toBe(true);43 vm.dispose();44 host.dispose();45 });46});