T08 · Insecure Dependencies
Warning
- Location
- setup.mjs:145
- Finding
- Unpinned Dependencies and Unverified Remote Font Downloads<![CDATA[ ## Vulnerability Details **File Location**: `package.json:11-12`; `setup.mjs:81`; `setup.mjs:145-174` **Vulnerability Type**: Supply-chain integrity weakness **Risk Level**: Medium ### Vulnerable Code `package.json:11-12`: ```json "canvas": "^3.1.0", "@google/generative-ai": "^0.21.0" ``` `setup.mjs:81`: ```js execSync('npm install', { cwd: __dirname, stdio: 'inherit' }); ``` `setup.mjs:145-174`: ```js async function downloadFonts() { // 동일 URL → 한 번만 다운로드, 여러 파일명에 복사 const urlCache = new Map(); // url → Buffer for (const fontFile of REQUIRED_FONTS) { const dst = path.join(FONTS_DIR, fontFile); if (fs.existsSync(dst)) continue; const url = FONT_DOWNLOAD_URLS[fontFile]; if (!url) { logWarn(`다운로드 URL 없음: ${fontFile} (수동 다운로드 필요)`); log(` Google Fonts에서 다운로드: https://fonts.google.com/noto`); log(` 저장 위치: ${dst}`); continue; } // 이미 같은 URL에서 다운로드 했으면 캐시에서 복사 if (urlCache.has(url)) { fs.writeFileSync(dst, urlCache.get(url)); logOk(`캐시에서 복사: ${fontFile}`); continue; } log(`다운로드 중: ${fontFile}...`); try { const response = await fetch(url, { redirect: 'follow' }); if (!response.ok) throw new Error(`HTTP ${response.status}`); const buffer = Buffer.from(await response.arrayBuffer()); fs.writeFileSync(dst, buffer); urlCache.set(url, buffer); logOk(`다운로드 완료: ${fontFile} (${(buffer.length / 1024 / 1024).toFixed(1)}MB)`); } catch (error) { logWarn(`다운로드 실패: ${fontFile} - ${error.message}`); log(` 수동으로 다운로드해주세요: ${url}`); log(` 저장 위치: ${dst}`); } } } ``` ### Technical Analysis The project does not include a dependency lockfile, while its dependencies use caret version ranges. Running `npm install` can therefore resolve package versions that differ from those reviewed during the audit. The command also permits dependency lifecycle scripts to execute unless separately disabled. The setup process addit ...[truncated 2149 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions 1. Pin dependencies to exact reviewed versions rather than caret ranges. 2. Generate and commit a lockfile, and use `npm ci` for reproducible installation. 3. Use `npm ci --ignore-scripts` where possible. If lifecycle scripts are required, explicitly review and allow only the necessary packages and scripts. 4. Pin font URLs to immutable commit hashes or versioned release assets rather than mutable branch paths. 5. Maintain expected SHA-256 hashes for every downloaded font and verify each artifact before writing it to disk. 6. Validate the final redirect destination against an explicit host and path allowlist. 7. Reject responses with unexpected content types, excessive sizes, or invalid font signatures. 8. Download to a temporary file, validate it, and then atomically move it into the font directory. 9. Run setup and rendering under a minimally privileged account without unnecessary secrets in the process environment. 10. Add automated dependency and artifact-integrity checks to the release process. ]]>
