From 39b680bb3724ecf573fec8fdccc3ce3c345e6e87 Mon Sep 17 00:00:00 2001 From: JB Date: Sat, 19 Sep 2026 10:08:10 -0400 Subject: [PATCH] fix: reject malformed cursor IDs and page discriminants --- src/lib/domain/capabilities.ts | 9 +++++++-- tests/contracts.test.ts | 4 ++++ 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/src/lib/domain/capabilities.ts b/src/lib/domain/capabilities.ts index 384e359..7d46c7c 100644 --- a/src/lib/domain/capabilities.ts +++ b/src/lib/domain/capabilities.ts @@ -259,7 +259,12 @@ export function validateInventoryCursorUse(state: InventoryCursorState, lookup: /** Enforce the page discriminants without implementing page retrieval. */ export function validateInventoryPageResult(result: InventoryQueryResult): InventoryPageValidation { - if (result.status !== 'ready') return { valid: true }; + if (result.status !== 'ready') { + return ['partial', 'stale', 'authorization_required', 'unavailable'].includes(result.status) + ? { valid: true } + : { valid: false, reason: 'invalid_page_state' }; + } + if (!['initial', 'continuation', 'after_end'].includes(result.pageState)) return { valid: false, reason: 'invalid_page_state' }; if (result.pageState === 'after_end') { return result.rows.length === 0 && result.nextCursor === null ? { valid: true } @@ -278,7 +283,7 @@ function validCursorPosition(position: unknown): boolean { const candidate = position as { offset?: unknown; lastItemId?: unknown }; if (!Number.isSafeInteger(candidate.offset) || (candidate.offset as number) < 0) return false; if (candidate.offset === 0) return candidate.lastItemId === null; - return typeof candidate.lastItemId === 'string' && /^(0|[1-9][0-9]*)$/.test(candidate.lastItemId); + return typeof candidate.lastItemId === 'string' && /^(0|[1-9][0-9]*)$(?![\s\S])/.test(candidate.lastItemId); } function sameSelectedResourceList(left: readonly SelectedResource[], right: readonly SelectedResource[]): boolean { diff --git a/tests/contracts.test.ts b/tests/contracts.test.ts index def8704..d4d83cb 100644 --- a/tests/contracts.test.ts +++ b/tests/contracts.test.ts @@ -58,6 +58,8 @@ describe('T02 typed contract fixtures', () => { expect(validateInventoryCursorUse(inventoryCursorState, { ...inventoryCursorLookup, selectedResources: [syntheticResource, syntheticResource] })).toEqual({ valid: false, reason: 'resource_mismatch' }); expect(validateInventoryCursorUse({ ...inventoryCursorState, position: { offset: -1, lastItemId: null } } as unknown as typeof inventoryCursorState, inventoryCursorLookup)).toEqual({ valid: false, reason: 'malformed_state' }); expect(validateInventoryCursorUse({ ...inventoryCursorState, position: { offset: 1, lastItemId: '01' } }, inventoryCursorLookup)).toEqual({ valid: false, reason: 'malformed_state' }); + expect(validateInventoryCursorUse({ ...inventoryCursorState, position: { offset: 1, lastItemId: '1\n' } }, inventoryCursorLookup)).toEqual({ valid: false, reason: 'malformed_state' }); + expect(validateInventoryCursorUse({ ...inventoryCursorState, status: 'unexpected' } as unknown as typeof inventoryCursorState, inventoryCursorLookup)).toEqual({ valid: false, reason: 'malformed_state' }); expect(inventoryCursorState.pin.authorizationEpochs['character:90000000000000001']).toBe(1); expect(inventoryResult.status).toBe('ready'); if (inventoryResult.status === 'ready') { @@ -66,6 +68,8 @@ describe('T02 typed contract fixtures', () => { expect(inventoryResult.provenance.input.snapshotGenerations.assets).toBe('assets-g7'); expect(inventoryResult.provenance.input.referenceVersion).toBe('reference-g1'); expect(validateInventoryPageResult(inventoryResult)).toEqual({ valid: true }); + expect(validateInventoryPageResult({ ...inventoryResult, pageState: 'unexpected' } as unknown as typeof inventoryResult)).toEqual({ valid: false, reason: 'invalid_page_state' }); + expect(validateInventoryPageResult({ ...inventoryResult, status: 'unexpected' } as unknown as typeof inventoryResult)).toEqual({ valid: false, reason: 'invalid_page_state' }); expect(validateCompleteReportPins(inventoryResult.provenance.input)).toEqual({ valid: true }); expect(validateCompleteReportPins({ ...inventoryResult.provenance.input, selectedResources: [syntheticResource, syntheticResource] })).toEqual({ valid: false, reason: 'duplicate_resource' }); expect(validateCompleteReportPins({ ...inventoryResult.provenance.input, datasetPins: [inventoryResult.provenance.input.datasetPins[0]!, inventoryResult.provenance.input.datasetPins[0]!] })).toEqual({ valid: false, reason: 'duplicate_pin' });