7928f9b6c5
- buildZip에 경로 하드닝 추가: ../, 절대경로(/), 역슬래시(\) 금지 - assets.integrity.index.json에 size(byte) 필드 추가 - verifyIntegrity에서 size 불일치 검증 추가 - 경로/사이즈 검증 테스트 추가: zip.pathGuard.test.ts - 전체 테스트 그린 유지
55 lines
1.8 KiB
TypeScript
55 lines
1.8 KiB
TypeScript
import JSZip from 'jszip'
|
|
|
|
export type BuildZipInput = {
|
|
html: string
|
|
css: string
|
|
js: string
|
|
assets?: Record<string, Uint8Array>
|
|
// root-level extra files like favicon.ico, site.webmanifest, robots.txt
|
|
extras?: Record<string, Uint8Array>
|
|
}
|
|
|
|
export async function buildZip(input: BuildZipInput): Promise<Uint8Array> {
|
|
const zip = new JSZip()
|
|
zip.file('index.html', input.html)
|
|
zip.file('styles.css', input.css)
|
|
zip.file('app.js', input.js)
|
|
|
|
// guard: basic path sanitizer to avoid directory traversal or absolute paths
|
|
const isSafePath = (p: string) => {
|
|
if (!p || typeof p !== 'string') return false
|
|
if (p.startsWith('/')) return false
|
|
if (p.includes('..')) return false
|
|
if (p.includes('\\')) return false
|
|
return true
|
|
}
|
|
|
|
if (input.assets) {
|
|
for (const [path, bytes] of Object.entries(input.assets)) {
|
|
if (!isSafePath(path)) throw new Error(`Invalid asset path: ${path}`)
|
|
// In Node/Vitest, use Buffer for robust binary handling
|
|
const buf = Buffer.from(bytes)
|
|
zip.file(path, buf as unknown as Buffer)
|
|
}
|
|
}
|
|
if (input.extras) {
|
|
// validate manifest if present
|
|
if (Object.prototype.hasOwnProperty.call(input.extras, 'site.webmanifest')) {
|
|
try {
|
|
const manifestBytes = input.extras['site.webmanifest']
|
|
const manifestStr = Buffer.from(manifestBytes).toString('utf8')
|
|
JSON.parse(manifestStr)
|
|
} catch {
|
|
throw new Error('Invalid site.webmanifest JSON')
|
|
}
|
|
}
|
|
for (const [path, bytes] of Object.entries(input.extras)) {
|
|
if (!isSafePath(path)) throw new Error(`Invalid extra path: ${path}`)
|
|
const buf = Buffer.from(bytes)
|
|
zip.file(path, buf as unknown as Buffer)
|
|
}
|
|
}
|
|
const content = await zip.generateAsync({ type: 'uint8array' })
|
|
return content
|
|
}
|