mirror of
https://github.com/snapotter-hq/SnapOtter.git
synced 2026-08-03 07:46:42 +02:00
fix(ai): align offline bundle import entry cap with the installer
Manual bundle import capped archives at 10,000 entries while the installer (max_entries in install_runtime.py) allows 100,000. OCR ships thousands of small files, so the same bundle installed fine via the app but failed an offline import with "Archive exceeds 10000 entry limit". Raise the manual cap to match. The 20 GB cumulative byte cap and the entry-type and traversal guards stay in place as the DoS backstops. Fixes #719
This commit is contained in:
@@ -1479,7 +1479,13 @@ interface BundleDescriptor {
|
||||
}
|
||||
|
||||
const IMPORT_MAX_BYTES = 20 * 1024 * 1024 * 1024; // 20 GB cumulative
|
||||
const IMPORT_MAX_ENTRIES = 10_000;
|
||||
// Matches the installer's own cap (max_entries in
|
||||
// packages/ai/python/install_runtime.py) so any bundle that installs online
|
||||
// also imports offline. OCR ships thousands of small files and blew the old
|
||||
// 10k cap while staying well under the installer's, so the same bundle
|
||||
// installed via the app but failed a manual import (#719). The 20 GB byte cap
|
||||
// and the entry-type/traversal guards remain the real DoS backstops.
|
||||
const IMPORT_MAX_ENTRIES = 100_000;
|
||||
|
||||
export async function importBundleArchive(
|
||||
stream: Readable,
|
||||
|
||||
@@ -260,6 +260,34 @@ describe("importBundleArchive", () => {
|
||||
expect(readFileSync(modelPath)).toEqual(fakeModel);
|
||||
});
|
||||
|
||||
it("imports a bundle whose file count exceeds the legacy 10k entry cap (#719)", async () => {
|
||||
// OCR-style bundle: thousands of small files. The old 10k manual-import
|
||||
// cap rejected these ("Archive exceeds 10000 entry limit") even though the
|
||||
// installer accepts them, so the same bundle installed via the app but
|
||||
// failed an offline import. Entry count here (~10,052 incl. bundle.json
|
||||
// and the models/ dir) is above the old cap and well under the new one.
|
||||
const count = 10_050;
|
||||
const modelFiles = Array.from({ length: count }, (_, i) => ({
|
||||
path: `f${i}.bin`,
|
||||
content: Buffer.from([i & 0xff]),
|
||||
}));
|
||||
const archivePath = await buildArchive(
|
||||
{
|
||||
bundleId: testBundleId,
|
||||
version: testVersion,
|
||||
models: modelFiles.map((m) => m.path),
|
||||
},
|
||||
modelFiles,
|
||||
);
|
||||
|
||||
const result = await importBundleArchive(createReadStream(archivePath));
|
||||
|
||||
expect(result.bundleId).toBe(testBundleId);
|
||||
expect(result.models).toHaveLength(count);
|
||||
expect(existsSync(join(modelsDir, "f0.bin"))).toBe(true);
|
||||
expect(existsSync(join(modelsDir, `f${count - 1}.bin`))).toBe(true);
|
||||
}, 60_000);
|
||||
|
||||
it("rejects archive with path traversal", async () => {
|
||||
const traversalPath = await buildTraversalArchive();
|
||||
const stream = createReadStream(traversalPath);
|
||||
|
||||
Reference in New Issue
Block a user