fix(security): trust file: URLs only inside the app renderer directory
This commit is contained in:
parent
b306034bfc
commit
5322d981cc
5 changed files with 299 additions and 17 deletions
69
packages/core/__tests__/url-policy-redteam-r2-9.test.ts
Normal file
69
packages/core/__tests__/url-policy-redteam-r2-9.test.ts
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
// file: 앱 오리진 판정을 렌더러 디렉터리로 제한하는 회귀 테스트(드롭한 파일이 앱 창을 대체하던 문제).
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { isAppNavigationTarget, isAppOrigin, isFileUrlWithinRoot } from '../src/url-policy'
|
||||
|
||||
const ROOT = 'file:///C:/Program%20Files/D3RO%20Voice/resources/app.asar/out/renderer/'
|
||||
const ORIGINS = ['file://', 'http://localhost:5173']
|
||||
const OPTIONS = { fileRoot: ROOT }
|
||||
|
||||
describe('isFileUrlWithinRoot', () => {
|
||||
it('accepts files under the renderer root (encoding, drive case, hash/query ignored)', () => {
|
||||
expect(isFileUrlWithinRoot(`${ROOT}index.html#/meetings`, ROOT)).toBe(true)
|
||||
expect(isFileUrlWithinRoot(`${ROOT}popups/result-popup/index.html?x=1`, ROOT)).toBe(true)
|
||||
expect(isFileUrlWithinRoot('file:///c:/program files/d3ro voice/resources/app.asar/out/renderer/index.html', ROOT)).toBe(true)
|
||||
expect(isFileUrlWithinRoot(`${ROOT}index.html`, ROOT.slice(0, -1))).toBe(true)
|
||||
})
|
||||
|
||||
it('accepts a non-ASCII install path encoded the way Chromium encodes it', () => {
|
||||
const root = 'file:///C:/Users/%EC%9C%A4%EC%B0%AC/AppData/Local/Programs/d3ro-voice/resources/app.asar/out/renderer/'
|
||||
expect(isFileUrlWithinRoot('file:///C:/Users/윤찬/AppData/Local/Programs/d3ro-voice/resources/app.asar/out/renderer/index.html', root)).toBe(true)
|
||||
})
|
||||
|
||||
it.each([
|
||||
'file:///C:/Users/me/Downloads/x.html',
|
||||
'file:///C:/Program%20Files/D3RO%20Voice/resources/app.asar/out/renderer-evil/index.html',
|
||||
'file:///C:/Program%20Files/D3RO%20Voice/resources/app.asar/out/index.html',
|
||||
'file://evil-host/share/x.html',
|
||||
'file://evil-host/C:/Program%20Files/D3RO%20Voice/resources/app.asar/out/renderer/index.html',
|
||||
`${ROOT}..%2F..%2Fx.html`,
|
||||
`${ROOT}..%5C..%5Cx.html`,
|
||||
`${ROOT}%2e%2e/%2e%2e/x.html`,
|
||||
'http://localhost:5173/index.html',
|
||||
'not a url',
|
||||
])('rejects %s', (url) => {
|
||||
expect(isFileUrlWithinRoot(url, ROOT)).toBe(false)
|
||||
})
|
||||
|
||||
it('rejects everything when the root itself is not a file URL', () => {
|
||||
expect(isFileUrlWithinRoot(`${ROOT}index.html`, 'https://example.com/')).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe('isAppOrigin with fileRoot', () => {
|
||||
it('trusts only the renderer bundle and the dev server', () => {
|
||||
expect(isAppOrigin(`${ROOT}index.html#/settings`, ORIGINS, OPTIONS)).toBe(true)
|
||||
expect(isAppOrigin('http://localhost:5173/#/settings', ORIGINS, OPTIONS)).toBe(true)
|
||||
expect(isAppOrigin('file:///C:/Users/me/Downloads/evil.html', ORIGINS, OPTIONS)).toBe(false)
|
||||
expect(isAppOrigin('file://host/share/evil.html', ORIGINS, OPTIONS)).toBe(false)
|
||||
expect(isAppOrigin('https://evil.example/', ORIGINS, OPTIONS)).toBe(false)
|
||||
})
|
||||
|
||||
it('keeps the dev server origin exact even with a fileRoot', () => {
|
||||
expect(isAppOrigin('http://localhost:5174/', ORIGINS, OPTIONS)).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe('isAppNavigationTarget', () => {
|
||||
it('allows app HTML pages and dev server routes', () => {
|
||||
expect(isAppNavigationTarget(`${ROOT}index.html`, ORIGINS, OPTIONS)).toBe(true)
|
||||
expect(isAppNavigationTarget(`${ROOT}popups/command-popup/index.html#x`, ORIGINS, OPTIONS)).toBe(true)
|
||||
expect(isAppNavigationTarget('http://localhost:5173/popups/result-popup/index.html', ORIGINS, OPTIONS)).toBe(true)
|
||||
})
|
||||
|
||||
it('blocks dropped files and non-HTML files inside the bundle', () => {
|
||||
expect(isAppNavigationTarget('file:///C:/Users/me/Music/meeting.mp3', ORIGINS, OPTIONS)).toBe(false)
|
||||
expect(isAppNavigationTarget('file:///C:/Users/me/Downloads/x.html', ORIGINS, OPTIONS)).toBe(false)
|
||||
expect(isAppNavigationTarget(`${ROOT}assets/index-abc.js`, ORIGINS, OPTIONS)).toBe(false)
|
||||
expect(isAppNavigationTarget(`${ROOT}`, ORIGINS, OPTIONS)).toBe(false)
|
||||
})
|
||||
})
|
||||
Loading…
Add table
Add a link
Reference in a new issue