fix: harden media and WebAssembly delivery

This commit is contained in:
2026-07-24 15:09:33 +02:00
parent 1f00e6def1
commit a9c31c8986
2 changed files with 104 additions and 2 deletions

View File

@@ -4,10 +4,18 @@ import { describe, expect, it } from 'vitest';
const projectRoot = process.cwd();
const compose = readFileSync(path.join(projectRoot, 'compose.yaml'), 'utf8');
const composeExample = readFileSync(
path.join(projectRoot, 'compose.example.yaml'),
'utf8'
);
const containerfile = readFileSync(
path.join(projectRoot, 'Containerfile.release'),
'utf8'
);
const environmentExample = readFileSync(
path.join(projectRoot, '.env.example'),
'utf8'
);
const nginxConfig = readFileSync(
path.join(projectRoot, 'deploy', 'nginx.conf'),
'utf8'
@@ -16,6 +24,23 @@ const releaseLock = JSON.parse(
readFileSync(path.join(projectRoot, 'release', 'toolbox.lock.json'), 'utf8')
) as { releaseVersion: string };
const immutableCacheControl = 'public, max-age=31536000, immutable';
const immutableUriPatterns = Array.from(
nginxConfig.matchAll(
new RegExp(
`^\\s*"~([^"]+)"\\s+"${immutableCacheControl.replaceAll(
', ',
',\\s+'
)}";$`,
'gmu'
)
),
(match) => new RegExp(match[1], 'u')
);
const isImmutable = (uri: string): boolean =>
immutableUriPatterns.some((pattern) => pattern.test(uri));
describe('production deployment', () => {
it('keeps the release version and checksum pins synchronized', () => {
const containerVersion = containerfile.match(
@@ -30,10 +55,22 @@ describe('production deployment', () => {
const composeChecksum = compose.match(
/\$\{TOOLBOX_RELEASE_SHA256:-([a-f0-9]{64})\}/u
)?.[1];
const environmentVersion = environmentExample.match(
/^TOOLBOX_RELEASE_VERSION=(\S+)$/mu
)?.[1];
const environmentChecksum = environmentExample.match(
/^TOOLBOX_RELEASE_SHA256=([a-f0-9]{64})$/mu
)?.[1];
const localImageVersion = composeExample.match(
/^\s+image: git\.add-ideas\.de\/zemion\/toolbox:(\S+)$/mu
)?.[1];
expect(containerVersion).toBe(releaseLock.releaseVersion);
expect(composeVersion).toBe(containerVersion);
expect(composeChecksum).toBe(containerChecksum);
expect(environmentVersion).toBe(containerVersion);
expect(environmentChecksum).toBe(containerChecksum);
expect(localImageVersion).toBe(containerVersion);
});
it('verifies the release before extraction and retains a rootless final image', () => {
@@ -45,6 +82,11 @@ describe('production deployment', () => {
expect(containerfile).toContain(
'COPY --from=release --chown=101:101 /tmp/toolbox/'
);
for (const app of ['pdf', 'xslt', 'onenote', 'av']) {
expect(containerfile).toContain(
`test -f /tmp/toolbox/apps/${app}/toolbox-app.json`
);
}
expect(containerfile).toMatch(/\nUSER 101:101\nEXPOSE 8080\n/u);
});
@@ -73,4 +115,56 @@ describe('production deployment', () => {
);
expect(nginxConfig).not.toMatch(/^\s*~[^\n]*\{\d/mu);
});
it('caches only hashed assets and the versioned FFmpeg runtime immutably', () => {
expect(immutableUriPatterns).toHaveLength(2);
for (const uri of [
'/assets/index-Bp9nK_3a.js',
'/apps/av/vendor/ffmpeg/0.12.10/version.json',
'/apps/av/vendor/ffmpeg/0.12.10/st/ffmpeg-core.js',
'/apps/av/vendor/ffmpeg/0.12.10/st/ffmpeg-core.wasm',
'/apps/av/vendor/ffmpeg/0.12.10/mt/ffmpeg-core.js',
'/apps/av/vendor/ffmpeg/0.12.10/mt/ffmpeg-core.wasm',
'/apps/av/vendor/ffmpeg/0.12.10/mt/ffmpeg-core.worker.js',
]) {
expect(isImmutable(uri), uri).toBe(true);
}
for (const uri of [
'/',
'/index.html',
'/SOURCE.md',
'/LICENSE',
'/toolbox.catalog.json',
'/toolbox.release.json',
'/apps/av/toolbox-app.json',
'/apps/av/THIRD_PARTY_NOTICES.md',
'/apps/av/vendor/ffmpeg/version.json',
'/apps/av/vendor/ffmpeg/latest/st/ffmpeg-core.wasm',
'/apps/av/vendor/ffmpeg/0.12/st/ffmpeg-core.wasm',
'/apps/av/vendor/ffmpeg/01.2.3/st/ffmpeg-core.wasm',
'/apps/av/vendor/ffmpeg/0.12.10/st/other.wasm',
'/apps/av/vendor/ffmpeg/0.12.10/mt/ffmpeg-core.worker.wasm',
'/apps/av/vendor/ffmpeg/0.12.10/ffmpeg-core.wasm',
]) {
expect(isImmutable(uri), uri).toBe(false);
}
});
it('serves WebAssembly through an explicit MIME-mapped location', () => {
expect(nginxConfig).toMatch(
/location ~\* \\\.wasm\$\s*\{\s*types\s*\{\s*application\/wasm wasm;\s*\}\s*try_files \$uri =404;\s*\}/mu
);
expect(nginxConfig).not.toMatch(
/location ~\* [^\n]*\(\?:[^\n|)]*\bwasm\b[^\n)]*\)/u
);
});
it('allows same-origin WebAssembly workers and local blob media previews', () => {
expect(nginxConfig).toContain("script-src 'self' 'wasm-unsafe-eval'");
expect(nginxConfig).toContain("worker-src 'self' blob:");
expect(nginxConfig).toContain("media-src 'self' blob:");
expect(nginxConfig).not.toContain("'unsafe-eval'");
});
});