C.W.K.
Stream
Lesson 05 of 06 · published

MV3 보안 모델 — Sandbox, CSP, Permissions

~12 min · mv3, security, csp, permissions, host_permissions, sandbox

Level 0Extension 입덕
0 XP0/54 lessons0/13 achievements
0/100 XP to next level100 XP to go0% complete
"MV3 의 보안 모델은 까칠할 정도로 의견이 강해 — 그게 버그가 아니라 feature."

Extension Sandbox

모든 Chrome extension 은 자기 renderer process 에서 돌아, 상호작용하는 host page 와 격리. Extension 은 chrome.* API 와 사적 origin (chrome-extension://<id>/) 에 접근하지만, host page 의 JavaScript context 에 직접 손 못 대. 통신은 의도된 채널로만 — message / isolated world 에 injected 된 content script / Chrome 의 tab API.

이 격리가 extension 이 user-script injector 보다 안전한 전체 이유. Host page 가 extension storage 를 손쉽게 못 읽고, extension 이 host 의 global scope 를 실수로 오염시킬 수도 없어.

MV3 의 Content Security Policy

MV3 는 extension page (popup / options / side panel) 에 빡센 default CSP 를 박아:

script-src 'self'; object-src 'self';

해석: script 는 extension package 자체에서만 실행. inline <script> 태그 안 됨, eval() 안 됨, new Function() 안 됨, handler 에 JavaScript URI 안 됨. Lesson 4 의 CSP gotcha 가 이 default 의 직접적 결과.

CSP 를 더 빡세게 (rare) 만들 수는 있지만, 푸는 건 빡세게 제한. 'unsafe-eval' 은 완전 금지. 'unsafe-inline' 은 script 에 대해 금지. WebAssembly compilation 진짜 필요하면 'wasm-unsafe-eval' 허용.

host_permissions — 어떤 page 를 만질 수 있어

API-capability 인 permissions list 와 별개로, host_permissions 는 extension 이 DOM read 또는 script inject 가능한 URL 을 선언. 두 패턴:

  • 좁게 — 특정 origin list: ["https://news.ycombinator.com/*", "https://*.github.com/*"]. Chrome 이 install 시 user 한테 정확히 어떤 site 가 영향받는지 보여줌.
  • 광범위["<all_urls>"]. 살벌한 install 경고 발동: "모든 website 의 모든 data 를 읽고 변경." Extension 이 진짜로 어디든 page access 필요할 때만 (ClipDeck 은 결국 필요 — selection capture 가 어느 page 에서든 동작해야).

중간 길은 API permissions list 에 activeTab — just-in-time grant: user 가 toolbar icon 클릭하면 Chrome 이 현재 tab 의 URL + DOM access 를 일시적으로 부여, tab navigation 시 만료. Install 시 경고 없음, 광범위 surface 도 없음.

Remote code 금지

MV3 는 network 로 JavaScript fetch 후 실행 금지. 구체적으로:

  • fetch("https://...") 후 response 에 eval() 안 됨.
  • Extension HTML page 에 <script src="https://cdn.example.com/foo.js"> 안 됨.
  • new Function(remoteString) 안 됨.

허용되는 거: network 로 fetch 한 data (JSON / text / image) 를 data 로 다루기. 경계선은 실행. Server 가 extension 한테 뭘 렌더하라고 알려 — OK. Server 가 extension 한테 뭘 실행하라고 알려 — 금지.

chrome.permissions API — 필요할 때 요청

optional_permissions 또는 optional_host_permissions 에 선언된 permission 은 extension 이 runtime 에 chrome.permissions.request 로 요청 가능. Chrome 이 consent prompt 띄움, user 가 accept 또는 deny. 모든 거 미리 선언해서 install 시 user 겁먹게 하는 거의 정중한 대안.

ClipDeck 은 모든 permission 이 required-and-declared 로 유지 — 사적 tailnet 의 single-soul utility 한테는 유용한 optional-permission flow 없음. 근데 API 가 존재한다는 거 알기는 — fleet-distributable extension 설계할 때, 모든 Mac 을 install 시 겁먹게 하기 싫을 때 중요해.

MV3 보안은 default 가 빡세고, configuration 조이면 더 빡세져. 푸는 건 heavily gated + audited. Default 안에서 build 해; 어쩌다 밖으로 나가야 할 때는 deletion condition 을 문서화.
CSP 에러 메시지는 유난히 cryptic 해. Popup 이나 side panel 이 silent 하게 JS 렌더 실패할 때, Errors panel 이 자주 "Refused to execute inline script" 와 line number 만 보여줘 — 어느 파일 인지 안 알려줌. codebase 에서 inline <script> 태그나 onclick= handler 검색하는 게 복구 경로.

Code

MV3 CSP — default vs Pippa Chrome Embed 의 iframe-허용 override·json
// Default MV3 CSP for extension_pages (you don't have to specify this):
{
  "content_security_policy": {
    "extension_pages": "script-src 'self'; object-src 'self';"
  }
}

// Pippa Chrome Embed v0.1 loosens it slightly to allow iframe:
{
  "content_security_policy": {
    "extension_pages": "script-src 'self'; object-src 'self'; frame-src http://localhost:5173 http://100.x.x.x:5173;"
  }
}
// (100.x.x.x = 너의 Tailscale / private VPN host; CGNAT range 가 100.64.0.0/10)
Permission scope 세 가지 — narrow / broad / activeTab·json
// Narrow — Chrome shows specific sites at install:
{
  "host_permissions": [
    "https://news.ycombinator.com/*",
    "https://*.github.com/*"
  ]
}

// Broad — Chrome shows "all websites" warning:
{
  "host_permissions": ["<all_urls>"]
}

// activeTab — no install warning, just-in-time grant on toolbar click:
{
  "permissions": ["activeTab"]
}

External links

Exercise

clipdeck/manifest.json 열어. 지금은 permissions field 자체가 없어 — Track 1 의 hello-world ClipDeck 은 chrome.tabs.query 로 active tab title 만 읽고, 그건 명시적 permission 없이 됨. manifest 에 "permissions": ["activeTab"] 추가하고 reload. Toolbar 아이콘 클릭 — 동작 같아? 이제 activeTab 빼고 대신 "host_permissions": ["<all_urls>"] 추가. Reload, chrome://extensions 에서 ClipDeck 의 "Details" 클릭. "Site access" 까지 스크롤 — 이제 "On all sites" 라고 떠 (다른 사람한테 ship 했으면 install 시 경고 보였을 거). Popup 전용 extension 한테 왜 activeTab 이 맞는 선택이고, ClipDeck 의 이 단계에서 host_permissions 가 왜 과한지 한 문장으로 써.
Hint
activeTab 은 user 클릭 시에만 임시 access 부여 — install 경고 없음. host_permissions 는 install 부터 영구 access — 경고 발동. ClipDeck 의 나중 track (content script / page 간 selection capture) 은 host_permissions 필요 — Track 1 은 안 필요. 올바른 permission 은 실제 사용하는 capability 에 맞춰 scale.

Progress

Progress is local-only — sign in to sync across devices.
이 페이지에서 버그를 발견하셨거나 피드백이 있으세요?문제 신고
💛 by 피파serious

댓글 0

🔔 답글 알림 (로그인 필요)
로그인댓글을 남기려면 로그인해 주세요.

아직 댓글이 없어요. 첫 댓글을 남겨보세요.