import fs from 'node:fs' import os from 'node:os' import path from 'node:path' import { afterEach, describe, expect, it, vi } from 'vitest' import { CUSTOM_PET_SINGLE_IMAGE_MANIFEST_VERSION, CUSTOM_PET_SINGLE_IMAGE_MAX_DIMENSION, CUSTOM_PET_SINGLE_IMAGE_MOTION_PROFILE, CUSTOM_PET_SPRITESHEET_PIXELS, createCustomPetFromAtlas, createCustomPetCatalogLoader, createCustomPetFromImage, ensureCustomPetsRoot, loadCustomPets, resolveCustomPetsRoot, type CustomPetLoadResult, type ImageSizeInspector, } from './pets' const tempDirs: string[] = [] function makeTempDir(): string { const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'cc-haha-pets-')) tempDirs.push(dir) return dir } function pngHeader(width = 1536, height = 2288): Buffer { const data = Buffer.alloc(24) Buffer.from([137, 80, 78, 71, 13, 10, 26, 10]).copy(data) data.writeUInt32BE(13, 8) data.write('IHDR', 12, 'ascii') data.writeUInt32BE(width, 16) data.writeUInt32BE(height, 20) return data } function pngChunk(type: string, payload: Buffer): Buffer { const chunk = Buffer.alloc(12 + payload.length) chunk.writeUInt32BE(payload.length, 0) chunk.write(type, 4, 'ascii') payload.copy(chunk, 8) return chunk } function apngHeader(width = 1536, height = 2288): Buffer { const ihdrPayload = Buffer.alloc(13) ihdrPayload.writeUInt32BE(width, 0) ihdrPayload.writeUInt32BE(height, 4) const animationControl = Buffer.alloc(8) animationControl.writeUInt32BE(2, 0) animationControl.writeUInt32BE(0, 4) return Buffer.concat([ Buffer.from([137, 80, 78, 71, 13, 10, 26, 10]), pngChunk('IHDR', ihdrPayload), pngChunk('acTL', animationControl), pngChunk('IEND', Buffer.alloc(0)), ]) } function webpHeader(width = 1536, height = 2288): Buffer { const data = Buffer.alloc(30) data.write('RIFF', 0, 'ascii') data.writeUInt32LE(data.length - 8, 4) data.write('WEBP', 8, 'ascii') data.write('VP8X', 12, 'ascii') data.writeUInt32LE(10, 16) data.writeUIntLE(width - 1, 24, 3) data.writeUIntLE(height - 1, 27, 3) return data } function animatedWebpHeader(kind: 'flag' | 'ANIM' | 'ANMF'): Buffer { const header = webpHeader() if (kind === 'flag') { header[20] = header[20]! | 0x02 return header } const animationChunk = Buffer.alloc(8) animationChunk.write(kind, 0, 'ascii') animationChunk.writeUInt32LE(0, 4) const data = Buffer.concat([header, animationChunk]) data.writeUInt32LE(data.length - 8, 4) return data } type PetManifest = { displayName: string description: string spriteVersionNumber: number spritesheetPath: string [key: string]: unknown } type SingleImageManifest = { displayName: string description: string manifestVersion: number renderer: { kind: string version: number imagePath: string motionProfile?: string } [key: string]: unknown } function validManifest(overrides: Partial = {}): PetManifest { return { displayName: 'Pixel Pal', description: 'A tiny test companion.', spriteVersionNumber: 2, spritesheetPath: 'spritesheet.webp', ...overrides, } } function writePet( root: string, entry: string, manifest: PetManifest = validManifest(), image: Buffer = webpHeader(), ): string { const petDir = path.join(root, entry) fs.mkdirSync(petDir, { recursive: true }) fs.writeFileSync(path.join(petDir, 'pet.json'), JSON.stringify(manifest)) const imagePath = path.join(petDir, manifest.spritesheetPath) fs.mkdirSync(path.dirname(imagePath), { recursive: true }) fs.writeFileSync(imagePath, image) return petDir } function validSingleImageManifest( overrides: Partial = {}, ): SingleImageManifest { return { displayName: 'Spring Pal', description: 'A locally animated single-image companion.', manifestVersion: CUSTOM_PET_SINGLE_IMAGE_MANIFEST_VERSION, renderer: { kind: 'single-image', version: 1, imagePath: 'pet.webp', motionProfile: CUSTOM_PET_SINGLE_IMAGE_MOTION_PROFILE, }, ...overrides, } } function writeSingleImagePet( root: string, entry: string, manifest: SingleImageManifest = validSingleImageManifest(), image: Buffer = webpHeader(512, 640), ): string { const petDir = path.join(root, entry) fs.mkdirSync(petDir, { recursive: true }) fs.writeFileSync(path.join(petDir, 'pet.json'), JSON.stringify(manifest)) const renderer = manifest.renderer as SingleImageManifest['renderer'] | undefined const relativeImagePath = typeof renderer?.imagePath === 'string' && !renderer.imagePath.includes('..') ? renderer.imagePath : 'pet.webp' const imagePath = path.join(petDir, relativeImagePath) fs.mkdirSync(path.dirname(imagePath), { recursive: true }) fs.writeFileSync(imagePath, image) return petDir } const validSizeInspector: ImageSizeInspector = vi.fn(async () => ({ width: 1536, height: 2288 })) afterEach(() => { vi.clearAllMocks() for (const dir of tempDirs.splice(0)) { fs.rmSync(dir, { recursive: true, force: true }) } }) describe('custom pet root', () => { it('uses CLAUDE_CONFIG_DIR and never derives the runtime root from CODEX_HOME', () => { expect(resolveCustomPetsRoot({ env: { CLAUDE_CONFIG_DIR: '/tmp/claude-config', CODEX_HOME: '/tmp/forbidden-codex-home', }, homeDir: '/Users/test', })).toBe(path.resolve('/tmp/claude-config/cc-haha/pets')) expect(resolveCustomPetsRoot({ env: { CODEX_HOME: '/tmp/forbidden-codex-home' }, homeDir: '/Users/test', })).toBe(path.resolve('/Users/test/.claude/cc-haha/pets')) }) it('creates and returns the isolated custom-pets directory using only filesystem APIs', async () => { const homeDir = makeTempDir() const root = await ensureCustomPetsRoot({ env: {}, homeDir }) expect(root).toBe(path.join(homeDir, '.claude', 'cc-haha', 'pets')) expect(fs.statSync(root).isDirectory()).toBe(true) }) }) describe('custom pet catalog loading', () => { it('shares one bounded loader invocation across concurrent IPC requests', async () => { let finish: ((result: { root: string; pets: []; errors: [] }) => void) | undefined const load = vi.fn(() => new Promise<{ root: string; pets: []; errors: [] }>((resolve) => { finish = resolve })) const loadCatalog = createCustomPetCatalogLoader(load) const first = loadCatalog() const second = loadCatalog() expect(second).toBe(first) expect(load).toHaveBeenCalledTimes(1) finish?.({ root: '/owned/pets', pets: [], errors: [] }) await Promise.all([first, second]) await Promise.resolve() const third = loadCatalog() expect(load).toHaveBeenCalledTimes(2) finish?.({ root: '/owned/pets', pets: [], errors: [] }) await third }) it('starts a fresh scan after an overlapping pet installation invalidates the catalog', async () => { type DeferredCatalog = { promise: Promise resolve: (result: CustomPetLoadResult) => void } const scans: DeferredCatalog[] = [] const load = vi.fn(() => { let resolve!: DeferredCatalog['resolve'] const promise = new Promise>(finish => { resolve = finish }) scans.push({ promise, resolve }) return promise }) const loadCatalog = createCustomPetCatalogLoader(load) let finishInstall: (() => void) | undefined const beforeInstall = loadCatalog() const install = loadCatalog.invalidateAfter(() => new Promise(resolve => { finishInstall = resolve })) expect(loadCatalog()).toBe(beforeInstall) finishInstall?.() await install const afterInstall = loadCatalog() expect(afterInstall).not.toBe(beforeInstall) expect(load).toHaveBeenCalledTimes(2) scans[0]!.resolve({ root: '/owned/pets', pets: [], errors: [] }) await expect(beforeInstall).resolves.toEqual({ root: '/owned/pets', pets: [], errors: [] }) expect(loadCatalog()).toBe(afterInstall) scans[1]!.resolve({ root: '/owned/pets', pets: [{ id: 'custom:new-pet', displayName: 'New Pet', description: 'Installed while the first scan was in flight.', spriteVersionNumber: 2, spritesheetPath: 'spritesheet.webp', mimeType: 'image/webp', dataUrl: 'data:image/webp;base64,AA==', }], errors: [], }) await expect(afterInstall).resolves.toEqual({ root: '/owned/pets', pets: [{ id: 'custom:new-pet', displayName: 'New Pet', description: 'Installed while the first scan was in flight.', spriteVersionNumber: 2, spritesheetPath: 'spritesheet.webp', mimeType: 'image/webp', dataUrl: 'data:image/webp;base64,AA==', }], errors: [], }) }) }) describe('createCustomPetFromAtlas', () => { it('installs a validated atlas atomically into the app-owned pet root', async () => { const homeDir = makeTempDir() const atlasPath = path.join(homeDir, 'chosen.webp') fs.writeFileSync(atlasPath, webpHeader()) const pet = await createCustomPetFromAtlas({ slug: 'tiny-orbit', displayName: 'Tiny Orbit', description: 'A focused little companion.', atlasPath, }, { env: { CODEX_HOME: path.join(homeDir, 'forbidden-codex-home') }, homeDir, inspectImageSize: validSizeInspector, }) const root = path.join(homeDir, '.claude', 'cc-haha', 'pets') expect(pet.id).toBe('custom:tiny-orbit') expect(fs.existsSync(path.join(homeDir, 'forbidden-codex-home'))).toBe(false) expect(JSON.parse(fs.readFileSync(path.join(root, 'tiny-orbit', 'pet.json'), 'utf-8'))).toEqual({ id: 'tiny-orbit', displayName: 'Tiny Orbit', description: 'A focused little companion.', spriteVersionNumber: 2, spritesheetPath: 'spritesheet.webp', }) expect(fs.readFileSync(path.join(root, 'tiny-orbit', 'spritesheet.webp'))).toEqual(webpHeader()) }) it('rejects duplicate IDs and invalid or symlinked atlases without replacing an installed pet', async () => { const root = makeTempDir() const atlasPath = path.join(root, 'chosen.png') fs.writeFileSync(atlasPath, pngHeader()) const input = { slug: 'safe-pet', displayName: 'Safe Pet', description: 'Created locally.', atlasPath, } await createCustomPetFromAtlas(input, { root, inspectImageSize: validSizeInspector }) await expect(createCustomPetFromAtlas(input, { root, inspectImageSize: validSizeInspector, })).rejects.toThrow('already exists') expect(fs.readFileSync(path.join(root, 'safe-pet', 'spritesheet.png'))).toEqual(pngHeader()) const symlinkPath = path.join(root, 'linked.png') fs.symlinkSync(atlasPath, symlinkPath) await expect(createCustomPetFromAtlas({ ...input, slug: 'linked-pet', atlasPath: symlinkPath, }, { root, inspectImageSize: validSizeInspector })).rejects.toThrow('cannot be a symlink') expect(fs.existsSync(path.join(root, 'linked-pet'))).toBe(false) await expect(createCustomPetFromAtlas({ ...input, slug: '../escape', }, { root, inspectImageSize: validSizeInspector })).rejects.toThrow('lowercase kebab-case') }) }) describe('createCustomPetFromImage', () => { it('installs a validated static image atomically with a versioned renderer manifest', async () => { const homeDir = makeTempDir() const imagePath = path.join(homeDir, 'chosen.webp') fs.writeFileSync(imagePath, webpHeader(512, 640)) const pet = await createCustomPetFromImage({ slug: 'spring-orbit', displayName: 'Spring Orbit', description: 'A smooth local companion.', imagePath, }, { env: { CODEX_HOME: path.join(homeDir, 'forbidden-codex-home') }, homeDir, inspectImageSize: async () => ({ width: 512, height: 640 }), }) const root = path.join(homeDir, '.claude', 'cc-haha', 'pets') expect(pet).toMatchObject({ id: 'custom:spring-orbit', manifestVersion: 1, spriteVersionNumber: 1, imagePath: 'pet.webp', motionProfile: CUSTOM_PET_SINGLE_IMAGE_MOTION_PROFILE, mimeType: 'image/webp', }) expect(fs.existsSync(path.join(homeDir, 'forbidden-codex-home'))).toBe(false) expect(JSON.parse(fs.readFileSync(path.join(root, 'spring-orbit', 'pet.json'), 'utf-8'))).toEqual({ id: 'spring-orbit', displayName: 'Spring Orbit', description: 'A smooth local companion.', manifestVersion: 1, renderer: { kind: 'single-image', version: 1, imagePath: 'pet.webp', motionProfile: CUSTOM_PET_SINGLE_IMAGE_MOTION_PROFILE, }, }) expect(fs.readFileSync(path.join(root, 'spring-orbit', 'pet.webp'))).toEqual( webpHeader(512, 640), ) expect(fs.readdirSync(path.dirname(root)).some(entry => entry.startsWith('.pet-install-'))).toBe(false) }) it('rejects duplicate IDs, symlinked sources, and failed validation without partial installs', async () => { const root = makeTempDir() const imagePath = path.join(root, 'chosen.png') fs.writeFileSync(imagePath, pngHeader(512, 640)) const input = { slug: 'safe-image', displayName: 'Safe Image', description: 'Created locally.', imagePath, } await createCustomPetFromImage(input, { root, inspectImageSize: async () => ({ width: 512, height: 640 }), }) await expect(createCustomPetFromImage(input, { root, inspectImageSize: async () => ({ width: 512, height: 640 }), })).rejects.toThrow('already exists') expect(fs.readFileSync(path.join(root, 'safe-image', 'pet.png'))).toEqual(pngHeader(512, 640)) const symlinkPath = path.join(root, 'linked.png') fs.symlinkSync(imagePath, symlinkPath) await expect(createCustomPetFromImage({ ...input, slug: 'linked-image', imagePath: symlinkPath, }, { root })).rejects.toThrow('cannot be a symlink') expect(fs.existsSync(path.join(root, 'linked-image'))).toBe(false) const animatedPath = path.join(root, 'animated.png') fs.writeFileSync(animatedPath, apngHeader(512, 640)) await expect(createCustomPetFromImage({ ...input, slug: 'animated-image', imagePath: animatedPath, }, { root, inspectImageSize: async () => ({ width: 512, height: 640 }), })).rejects.toThrow('static PNG or WebP') expect(fs.existsSync(path.join(root, 'animated-image'))).toBe(false) expect(fs.readdirSync(path.dirname(root)).some(entry => entry.startsWith('.pet-install-'))).toBe(false) }) it('rejects unsupported motion profiles and unsafe image geometry before installation', async () => { const root = makeTempDir() const hugeImagePath = path.join(root, 'huge.webp') fs.writeFileSync( hugeImagePath, webpHeader(CUSTOM_PET_SINGLE_IMAGE_MAX_DIMENSION + 1, 64), ) await expect(createCustomPetFromImage({ slug: 'huge-image', displayName: 'Huge Image', description: 'Too large.', imagePath: hugeImagePath, }, { root })).rejects.toThrow('between 32 and 4096 pixels') expect(fs.existsSync(path.join(root, 'huge-image'))).toBe(false) await expect(createCustomPetFromImage({ slug: 'unknown-motion', displayName: 'Unknown Motion', description: 'Unsupported motion.', imagePath: hugeImagePath, motionProfile: 'unknown-profile' as typeof CUSTOM_PET_SINGLE_IMAGE_MOTION_PROFILE, }, { root })).rejects.toThrow('motion profile is unsupported') expect(fs.existsSync(path.join(root, 'unknown-motion'))).toBe(false) }) }) describe('loadCustomPets', () => { it('loads legacy v2 atlases and manifest-v1 single images without changing atlas metadata', async () => { const root = makeTempDir() writePet(root, 'atlas-pal') writeSingleImagePet(root, 'spring-pal') const result = await loadCustomPets({ root }) expect(result.errors).toEqual([]) expect(result.pets).toEqual([{ id: 'custom:atlas-pal', displayName: 'Pixel Pal', description: 'A tiny test companion.', spriteVersionNumber: 2, spritesheetPath: 'spritesheet.webp', mimeType: 'image/webp', dataUrl: `data:image/webp;base64,${webpHeader().toString('base64')}`, }, { id: 'custom:spring-pal', displayName: 'Spring Pal', description: 'A locally animated single-image companion.', manifestVersion: 1, spriteVersionNumber: 1, imagePath: 'pet.webp', motionProfile: CUSTOM_PET_SINGLE_IMAGE_MOTION_PROFILE, mimeType: 'image/webp', dataUrl: `data:image/webp;base64,${webpHeader(512, 640).toString('base64')}`, }]) }) it.each([ ['future manifest', { manifestVersion: 2 }, 'invalid-manifest-version'], ['missing renderer', { renderer: undefined }, 'invalid-renderer'], ['wrong renderer kind', { renderer: { ...validSingleImageManifest().renderer, kind: 'video' } }, 'invalid-renderer'], ['unsafe image path', { renderer: { ...validSingleImageManifest().renderer, imagePath: '../pet.webp' } }, 'invalid-image-path'], ['unsupported format', { renderer: { ...validSingleImageManifest().renderer, imagePath: 'pet.gif' } }, 'unsupported-image-format'], ['unsupported motion', { renderer: { ...validSingleImageManifest().renderer, motionProfile: 'jitter-v9' } }, 'invalid-renderer'], ])('rejects a single-image package with %s', async (_label, overrides, code) => { const root = makeTempDir() writeSingleImagePet(root, 'bad-image', validSingleImageManifest( overrides as Partial, )) const result = await loadCustomPets({ root }) expect(result.pets).toEqual([]) expect(result.errors).toEqual([ expect.objectContaining({ entry: 'bad-image', code }), ]) }) it('rejects symlinked and animated single-image assets before runtime decoding', async () => { const root = makeTempDir() const outside = makeTempDir() const linkedDir = path.join(root, 'linked-image') fs.mkdirSync(linkedDir) fs.writeFileSync(path.join(linkedDir, 'pet.json'), JSON.stringify(validSingleImageManifest())) fs.writeFileSync(path.join(outside, 'pet.webp'), webpHeader(512, 640)) fs.symlinkSync(path.join(outside, 'pet.webp'), path.join(linkedDir, 'pet.webp')) writeSingleImagePet( root, 'animated-image', validSingleImageManifest({ renderer: { ...validSingleImageManifest().renderer, imagePath: 'pet.png', }, }), apngHeader(512, 640), ) const inspectImageSize = vi.fn(async () => ({ width: 512, height: 640 })) const result = await loadCustomPets({ root, inspectImageSize }) expect(result.pets).toEqual([]) expect(result.errors).toEqual(expect.arrayContaining([ expect.objectContaining({ entry: 'linked-image', code: 'symlink-image' }), expect.objectContaining({ entry: 'animated-image', code: 'invalid-image' }), ])) expect(inspectImageSize).not.toHaveBeenCalled() }) it('charges a single image by its actual decoded pixels and rejects decoder/header mismatch', async () => { const root = makeTempDir() writeSingleImagePet(root, 'image-a', validSingleImageManifest(), webpHeader(512, 640)) writeSingleImagePet(root, 'image-b', validSingleImageManifest(), webpHeader(512, 640)) const budgetedInspector = vi.fn(async () => ({ width: 512, height: 640 })) const budgeted = await loadCustomPets({ root, inspectImageSize: budgetedInspector, maxDecodedPixels: 512 * 640, }) expect(budgeted.pets.map(pet => pet.id)).toEqual(['custom:image-a']) expect(budgeted.errors).toContainEqual(expect.objectContaining({ entry: 'image-b', code: 'decode-budget-exceeded', })) expect(budgetedInspector).toHaveBeenCalledTimes(1) const mismatched = await loadCustomPets({ root, inspectImageSize: async () => ({ width: 640, height: 512 }), }) expect(mismatched.pets).toEqual([]) expect(mismatched.errors.every(error => error.code === 'invalid-image')).toBe(true) }) it('bounds native decode attempts even when compressed images are tiny', async () => { const root = makeTempDir() for (const entry of ['pet-a', 'pet-b', 'pet-c']) writePet(root, entry) const inspectImageSize = vi.fn(async () => ({ width: 1536, height: 2288 })) const result = await loadCustomPets({ root, inspectImageSize, maxDecodedPixels: CUSTOM_PET_SPRITESHEET_PIXELS * 2, }) expect(result.pets.map((pet) => pet.id)).toEqual(['custom:pet-a', 'custom:pet-b']) expect(inspectImageSize).toHaveBeenCalledTimes(2) expect(result.errors).toContainEqual(expect.objectContaining({ entry: 'pet-c', code: 'decode-budget-exceeded', })) }) it('loads valid direct-child packages as whitelisted metadata and an image data URL', async () => { const root = makeTempDir() writePet(root, 'pixel-pal', validManifest({ displayName: ' Pixel Pal ', description: ' A tiny test companion. ', extraField: '', })) const inspectImageSize = vi.fn(async () => ({ width: 1536, height: 2288 })) const result = await loadCustomPets({ root, inspectImageSize }) expect(result.errors).toEqual([]) expect(result.pets).toEqual([{ id: 'custom:pixel-pal', displayName: 'Pixel Pal', description: 'A tiny test companion.', spriteVersionNumber: 2, spritesheetPath: 'spritesheet.webp', mimeType: 'image/webp', dataUrl: `data:image/webp;base64,${webpHeader().toString('base64')}`, }]) expect(inspectImageSize).toHaveBeenCalledWith(expect.objectContaining({ data: expect.any(Buffer), mimeType: 'image/webp', })) }) it('keeps loading valid packages when another package is malformed', async () => { const root = makeTempDir() writePet(root, 'good-pet') const badDir = path.join(root, 'bad-pet') fs.mkdirSync(badDir) fs.writeFileSync(path.join(badDir, 'pet.json'), '{bad json') const result = await loadCustomPets({ root, inspectImageSize: validSizeInspector }) expect(result.pets.map(pet => pet.id)).toEqual(['custom:good-pet']) expect(result.errors).toEqual([ expect.objectContaining({ entry: 'bad-pet', code: 'invalid-manifest' }), ]) expect(JSON.stringify(result.errors)).not.toContain(root) }) it.each([ ['wrong sprite version', { spriteVersionNumber: 1 }, 'invalid-sprite-version'], ['absolute POSIX path', { spritesheetPath: '/tmp/pet.webp' }, 'invalid-spritesheet-path'], ['absolute Windows path', { spritesheetPath: 'C:\\tmp\\pet.webp' }, 'invalid-spritesheet-path'], ['parent traversal', { spritesheetPath: '../pet.webp' }, 'invalid-spritesheet-path'], ['backslash traversal', { spritesheetPath: '..\\pet.webp' }, 'invalid-spritesheet-path'], ['unsupported image format', { spritesheetPath: 'spritesheet.gif' }, 'unsupported-image-format'], ])('rejects %s', async (_label, overrides, code) => { const root = makeTempDir() writePet(root, 'bad-pet', validManifest(overrides)) const result = await loadCustomPets({ root, inspectImageSize: validSizeInspector }) expect(result.pets).toEqual([]) expect(result.errors).toEqual([ expect.objectContaining({ entry: 'bad-pet', code }), ]) }) it.each([ ['Uppercase', 'invalid-id'], [`p${'a'.repeat(73)}`, 'invalid-id'], ['two--dashes', 'invalid-id'], ])('rejects an unsafe or overlong package slug %s', async (entry, code) => { const root = makeTempDir() writePet(root, entry) const result = await loadCustomPets({ root, inspectImageSize: validSizeInspector }) expect(result.pets).toEqual([]) expect(result.errors).toEqual([ expect.objectContaining({ entry, code }), ]) }) it('rejects a package directory symlink', async () => { const root = makeTempDir() const outside = makeTempDir() writePet(outside, 'target') fs.symlinkSync(path.join(outside, 'target'), path.join(root, 'linked-pet'), 'dir') const result = await loadCustomPets({ root, inspectImageSize: validSizeInspector }) expect(result.pets).toEqual([]) expect(result.errors).toEqual([ expect.objectContaining({ entry: 'linked-pet', code: 'symlink-entry' }), ]) }) it.each(['manifest', 'image', 'nested-directory'])('rejects a symlinked %s', async target => { const root = makeTempDir() const outside = makeTempDir() const petDir = path.join(root, 'linked-content') fs.mkdirSync(petDir) if (target === 'manifest') { fs.writeFileSync(path.join(outside, 'pet.json'), JSON.stringify(validManifest())) fs.symlinkSync(path.join(outside, 'pet.json'), path.join(petDir, 'pet.json')) fs.writeFileSync(path.join(petDir, 'spritesheet.webp'), webpHeader()) } else if (target === 'image') { fs.writeFileSync(path.join(petDir, 'pet.json'), JSON.stringify(validManifest())) fs.writeFileSync(path.join(outside, 'spritesheet.webp'), webpHeader()) fs.symlinkSync(path.join(outside, 'spritesheet.webp'), path.join(petDir, 'spritesheet.webp')) } else { fs.writeFileSync(path.join(petDir, 'pet.json'), JSON.stringify(validManifest({ spritesheetPath: 'assets/spritesheet.webp', }))) fs.mkdirSync(path.join(outside, 'assets')) fs.writeFileSync(path.join(outside, 'assets', 'spritesheet.webp'), webpHeader()) fs.symlinkSync(path.join(outside, 'assets'), path.join(petDir, 'assets'), 'dir') } const result = await loadCustomPets({ root, inspectImageSize: validSizeInspector }) expect(result.pets).toEqual([]) expect(result.errors).toEqual([ expect.objectContaining({ entry: 'linked-content', code: expect.stringContaining('symlink') }), ]) }) it('derives collision-free ids from folder slugs and ignores untrusted manifest id fields', async () => { const root = makeTempDir() writePet(root, 'first', validManifest({ id: 'same-id' })) writePet(root, 'second', validManifest({ id: 'same-id' })) const result = await loadCustomPets({ root, inspectImageSize: validSizeInspector }) expect(result.errors).toEqual([]) expect(result.pets.map(pet => pet.id)).toEqual(['custom:first', 'custom:second']) }) it('rejects malformed, mismatched, oversized, and wrongly sized images per package', async () => { const root = makeTempDir() writePet(root, 'good') writePet(root, 'bad-magic', validManifest(), Buffer.from('not a webp')) writePet(root, 'too-large', validManifest(), Buffer.concat([webpHeader(), Buffer.alloc(64)])) writePet(root, 'wrong-size', validManifest(), webpHeader(1536, 1872)) const inspectImageSize = vi.fn(async ({ data }: { data: Buffer }) => { if (data.equals(webpHeader(1536, 1872))) return { width: 1536, height: 1872 } return { width: 1536, height: 2288 } }) const result = await loadCustomPets({ root, inspectImageSize, maxImageBytes: webpHeader().length + 1, }) expect(result.pets.map(pet => pet.id)).toEqual(['custom:good']) expect(result.errors).toEqual(expect.arrayContaining([ expect.objectContaining({ entry: 'bad-magic', code: 'invalid-image' }), expect.objectContaining({ entry: 'too-large', code: 'image-too-large' }), expect.objectContaining({ entry: 'wrong-size', code: 'invalid-image-dimensions' }), ])) }) it('rejects a giant declared canvas before invoking the runtime image decoder', async () => { const root = makeTempDir() writePet(root, 'decode-bomb', validManifest(), webpHeader(65_536, 65_536)) const inspectImageSize = vi.fn(async () => ({ width: 1536, height: 2288 })) const result = await loadCustomPets({ root, inspectImageSize }) expect(result.pets).toEqual([]) expect(result.errors).toEqual([ expect.objectContaining({ entry: 'decode-bomb', code: 'invalid-image-dimensions' }), ]) expect(inspectImageSize).not.toHaveBeenCalled() }) it('rejects APNG spritesheets before invoking the runtime image decoder', async () => { const root = makeTempDir() writePet(root, 'animated-png', validManifest({ spritesheetPath: 'spritesheet.png', }), apngHeader()) const inspectImageSize = vi.fn(async () => ({ width: 1536, height: 2288 })) const result = await loadCustomPets({ root, inspectImageSize }) expect(result.pets).toEqual([]) expect(result.errors).toEqual([ expect.objectContaining({ entry: 'animated-png', code: 'invalid-image' }), ]) expect(inspectImageSize).not.toHaveBeenCalled() }) it.each(['flag', 'ANIM', 'ANMF'] as const)( 'rejects animated WebP spritesheets identified by %s before runtime decoding', async animationMarker => { const root = makeTempDir() writePet(root, 'animated-webp', validManifest(), animatedWebpHeader(animationMarker)) const inspectImageSize = vi.fn(async () => ({ width: 1536, height: 2288 })) const result = await loadCustomPets({ root, inspectImageSize }) expect(result.pets).toEqual([]) expect(result.errors).toEqual([ expect.objectContaining({ entry: 'animated-webp', code: 'invalid-image' }), ]) expect(inspectImageSize).not.toHaveBeenCalled() }, ) it('caps manifest bytes, total image bytes, and scanned entries', async () => { const root = makeTempDir() writePet(root, 'a') writePet(root, 'b') writePet(root, 'c') const entryLimited = await loadCustomPets({ root, inspectImageSize: validSizeInspector, maxEntries: 1 }) expect(entryLimited.pets).toHaveLength(1) expect(entryLimited.errors).toContainEqual(expect.objectContaining({ code: 'entry-limit' })) const manifestLimited = await loadCustomPets({ root, inspectImageSize: validSizeInspector, maxManifestBytes: 8 }) expect(manifestLimited.pets).toEqual([]) expect(manifestLimited.errors).toHaveLength(3) expect(manifestLimited.errors.every(error => error.code === 'manifest-too-large')).toBe(true) const totalLimited = await loadCustomPets({ root, inspectImageSize: validSizeInspector, maxTotalImageBytes: webpHeader().length, }) expect(totalLimited.pets).toHaveLength(1) expect(totalLimited.errors.filter(error => error.code === 'total-image-bytes-exceeded')).toHaveLength(2) }) it('stops manifest IO and image decoding after the cumulative read budget is exhausted', async () => { const root = makeTempDir() writePet(root, 'pet-00') writePet(root, 'pet-01') for (let index = 2; index < 32; index += 1) { fs.mkdirSync(path.join(root, `pet-${index.toString().padStart(2, '0')}`)) } const inspectImageSize = vi.fn(async () => ({ width: 1536, height: 2288 })) const result = await loadCustomPets({ root, inspectImageSize, maxTotalImageBytes: webpHeader().length * 2, }) expect(result.pets.map(pet => pet.id)).toEqual(['custom:pet-00', 'custom:pet-01']) expect(inspectImageSize).toHaveBeenCalledTimes(2) expect(result.errors).toHaveLength(30) expect(result.errors.every(error => error.code === 'total-image-bytes-exceeded')).toBe(true) }) it('charges malformed image reads to the cumulative budget before decode', async () => { const root = makeTempDir() writePet(root, 'pet-00', validManifest(), Buffer.alloc(20, 0x41)) writePet(root, 'pet-01', validManifest(), Buffer.alloc(20, 0x42)) fs.mkdirSync(path.join(root, 'pet-02')) const inspectImageSize = vi.fn(async () => ({ width: 1536, height: 2288 })) const result = await loadCustomPets({ root, inspectImageSize, maxTotalImageBytes: 40, }) expect(result.pets).toEqual([]) expect(inspectImageSize).not.toHaveBeenCalled() expect(result.errors).toEqual([ expect.objectContaining({ entry: 'pet-00', code: 'invalid-image' }), expect.objectContaining({ entry: 'pet-01', code: 'invalid-image' }), expect.objectContaining({ entry: 'pet-02', code: 'total-image-bytes-exceeded' }), ]) }) it('caps the aggregate base64 data sent over IPC before reading another image', async () => { const root = makeTempDir() writePet(root, 'pet-00') writePet(root, 'pet-01') const firstDataUrl = `data:image/webp;base64,${webpHeader().toString('base64')}` const inspectImageSize = vi.fn(async () => ({ width: 1536, height: 2288 })) const result = await loadCustomPets({ root, inspectImageSize, maxTotalDataUrlBytes: firstDataUrl.length, }) expect(result.pets.map(pet => pet.id)).toEqual(['custom:pet-00']) expect(result.pets.reduce((total, pet) => total + pet.dataUrl.length, 0)).toBeLessThanOrEqual( firstDataUrl.length, ) expect(inspectImageSize).toHaveBeenCalledTimes(1) expect(result.errors).toEqual([ expect.objectContaining({ entry: 'pet-01', code: 'total-image-bytes-exceeded' }), ]) }) it.each(['root', 'package', 'intermediate'] as const)( 'rejects a %s directory replaced while an image is being decoded', async replacedDirectory => { const root = makeTempDir() const spritesheetPath = replacedDirectory === 'intermediate' ? 'assets/spritesheet.webp' : 'spritesheet.webp' const petDir = writePet(root, 'swap-pet', validManifest({ spritesheetPath })) const inspectImageSize = vi.fn(async () => { if (replacedDirectory === 'root') { const backup = `${root}-original` fs.renameSync(root, backup) tempDirs.push(backup) fs.mkdirSync(root) } else if (replacedDirectory === 'package') { fs.renameSync(petDir, path.join(root, 'swap-pet-original')) fs.mkdirSync(petDir) } else { const assetsDir = path.join(petDir, 'assets') fs.renameSync(assetsDir, path.join(petDir, 'assets-original')) fs.mkdirSync(assetsDir) } return { width: 1536, height: 2288 } }) const result = await loadCustomPets({ root, inspectImageSize }) expect(result.pets).toEqual([]) expect(result.errors).toEqual(expect.arrayContaining([ expect.objectContaining({ entry: 'swap-pet', code: replacedDirectory === 'root' ? 'root-invalid' : 'directory-changed', }), ])) }, ) it('uses a bounded built-in PNG/WebP header inspector when no runtime inspector is injected', async () => { const root = makeTempDir() writePet(root, 'png-pet', validManifest({ spritesheetPath: 'spritesheet.png', }), pngHeader()) writePet(root, 'webp-pet', validManifest(), webpHeader()) const result = await loadCustomPets({ root }) expect(result.errors).toEqual([]) expect(result.pets.map(pet => pet.id)).toEqual(['custom:png-pet', 'custom:webp-pet']) }) })