본문 바로가기
C.W.K.
Stream
Lesson 10 of 10 · published

Anchor 10 — host registry 하나, script realm 넷

~20 min · host-registry, mv3, configuration, classic-scripts, v0.2.1

Level 0Extension 입덕
0 XP0/56 lessons0/13 achievements
0/100 XP to next level100 XP to go0% complete
"Host list가 그냥 설정처럼 보였는데 주소 하나가 옮겨 가자 background, content, popup, side panel, manifest가 서로 다른 말을 하기 시작했어. Anchor 10은 그걸 고쳐. JavaScript owner는 하나로, JSON이 상속 못 하는 경계는 숨기지 않고 명시적으로."

새 owner를 가질 자격을 만든 bug

ChromeEmbed는 네 extension realm에서 같은 cwkPippa frontend를 봐. Service worker는 tab이 이미 Pippa surface인지 판별하고, content script는 self-capture를 막아. Popup은 Frontend selector를 만들고 side panel은 후보 origin을 순서대로 probe해. v0.2.1 전에는 realm마다 host literal을 따로 들고 있었어. Tailnet 주소가 한 번 바뀌자 일부만 교체됐고 extension이 자기 자신과 불일치했지. 이건 보기 싫은 중복이 아니라, consumer 넷이 쓰는 제품 fact 하나가 실제로 실패한 사건이야.

Classic-script loader 넷

  • Service worker: router를 읽기 전에 importScripts('pippa-hosts.js').
  • Content script: manifest의 content_scripts.js에서 pippa-hosts.jscontent-script.js보다 먼저.
  • Popup: popup.html이 registry script를 popup.js보다 먼저.
  • Side panel: sidepanel.html이 registry를 sidepanel.js보다 먼저.

네 surface가 extension 전체의 runtime global 하나를 공유하는 건 아니야. 각 realm이 같은 파일을 자기 classic-script global 환경에 올려. 그래서 선언도 일부러 const가 아니라 var를 써. Extension reload 뒤 이미 registry를 본 isolated world에 content script가 다시 들어갈 수 있거든. Top-level const 재선언은 터지지만 var 재선언은 아무 일도 안 일어나.

Manifest는 없앨 수 없는 중복

manifest.json은 script가 아니라 data고 MV3에는 import가 없어. 그래서 세 묶음은 review와 verifier로 함께 맞춰야 해. host_permissions, content_scripts.exclude_matches, 그리고 extension-page CSP 안의 connect-srcframe-src야. 맡은 일도 다 달라. Permission은 network surface를 합법으로 만들고, exclusion은 ChromeEmbed가 자기 embed route를 다시 읽는 걸 막아. connect-src는 reachability probe를, frame-src는 iframe 시도를 허용해. 그래도 상대 response가 framing을 거절하면 iframe은 못 떠.

Serialize된 function에는 closure가 없어

Worker에는 extension reload보다 먼저 열린 tab을 위한 chrome.scripting.executeScript({ func }) fallback도 있어. Chrome은 그 function을 serialize해서 보내기 때문에 worker scope의 PIPPA_LOCAL_HOSTS를 closure로 잡지 못해. Allowlist를 args로 직접 실어 보내야 해. 평범한 nested function은 바깥 환경을 잡으니 더 헷갈리기 쉬워. Injected function은 closure가 아니라 code shipment야.

Public material에는 invariant가 하나 더 있어

실제 office 주소는 운영 설정이지 quest content가 아니야. 공개 예제는 100.x.x.x만 써. 좋은 consistency check는 두 가지를 동시에 증명해야 해. 공개 surface의 host가 전부 맞고, private literal은 하나도 새지 않았다는 것. Consolidation은 owner, 피할 수 없는 manifest 중복, loader 순서, redaction 경계가 다 보일 때 끝나.

Code

pippa-hosts.js — 공개 checkpoint·javascript
var PIPPA_OFFICE_TAILNET_HOST = '100.x.x.x';
var PIPPA_FRONTEND_PORT = '5173';

var PIPPA_HOST_ENTRIES = [
  { host: 'localhost', label: 'Localhost' },
  { host: '127.0.0.1', label: 'Loopback' },
  { host: PIPPA_OFFICE_TAILNET_HOST, label: 'Office Tailnet' },
];

var PIPPA_LOCAL_HOSTS = PIPPA_HOST_ENTRIES.map((entry) => entry.host);
var PIPPA_PANEL_ORIGINS = PIPPA_HOST_ENTRIES.map(
  (entry) => `http://${entry.host}:${PIPPA_FRONTEND_PORT}`,
);
var PIPPA_PANEL_ORIGIN_OPTIONS = PIPPA_HOST_ENTRIES.map((entry, index) => ({
  value: PIPPA_PANEL_ORIGINS[index],
  label: entry.label,
}));
manifest.json — 함께 맞추는 surface 요약·json
{
  "host_permissions": [
    "http://localhost:5173/*",
    "http://127.0.0.1:5173/*",
    "http://100.x.x.x:5173/*"
  ],
  "content_scripts": [{
    "exclude_matches": [
      "http://localhost:5173/embed/*",
      "http://127.0.0.1:5173/embed/*",
      "http://100.x.x.x:5173/embed/*"
    ],
    "js": ["pippa-hosts.js", "content-script.js"]
  }],
  "content_security_policy": {
    "extension_pages": "script-src 'self'; object-src 'self'; connect-src http://localhost:5173 http://127.0.0.1:5173 http://100.x.x.x:5173; frame-src http://localhost:5173 http://127.0.0.1:5173 http://100.x.x.x:5173;"
  }
}
background.js — serialize된 function에 args 명시·javascript
const [result] = await chrome.scripting.executeScript({
  target: { tabId },
  args: [PIPPA_LOCAL_HOSTS],
  func: (localPippaHosts) => {
    const pageHost = location.hostname;
    return { isPippaHost: localPippaHosts.includes(pageHost) };
  },
});

External links

Exercise

ChromeEmbed folder의 버리는 copy에서 해 봐. dev.example.test라는 네 번째 placeholder host를 PIPPA_HOST_ENTRIES와 manifest의 세 synchronized group에 더해: host_permissions, content_scripts.exclude_matches, CSP의 connect-src와 frame-src. Popup과 side-panel 후보가 다른 JavaScript literal 없이 그 값을 파생하는지 확인해. 그다음 일부러 connect-src에서만 빼고 어떤 probe가 실패하는지, frame-src는 여전히 무엇을 다스리는지 설명해. 다시 복구하고 unpacked extension을 reload한 뒤, 예상 placeholder occurrence를 전부 세고 private IP literal은 하나도 허용하지 않는 grep/agreement check를 돌려.
Hint
문자열 개수보다 의미를 세. Registry는 JavaScript consumer를 소유하고 manifest JSON은 permission, self-capture exclusion, fetch, frame policy를 소유해. Loader 순서도 봐. content-script.js가 pippa-hosts.js보다 먼저면 disk의 registry가 맞아도 runtime에선 undefined야.

Progress

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

댓글 0

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

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