<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>API Inspector on monkshark.dev</title><link>https://monkshark.github.io/tags/api-inspector/</link><description>Recent content in API Inspector on monkshark.dev</description><generator>Hugo -- gohugo.io</generator><language>ko</language><lastBuildDate>Wed, 10 Jun 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://monkshark.github.io/tags/api-inspector/index.xml" rel="self" type="application/rss+xml"/><item><title>#3 - eval이 Promise를 안 기다려서, 전역에 써두고 폴링했다</title><link>https://monkshark.github.io/p/api-inspector-engineering/</link><pubDate>Wed, 10 Jun 2026 00:00:00 +0000</pubDate><guid>https://monkshark.github.io/p/api-inspector-engineering/</guid><description>&lt;p&gt;1부에서 못 박은 &amp;ldquo;최소권한&amp;quot;이, 사실은 3부의 모든 고생을 미리 예약해 둔 셈이었다. 권한을 안 받기로 했으니, 보통 권한으로 푸는 것들을 전부 우회로 풀어야 했다.&lt;/p&gt;
&lt;h2 id="권한-없이-요청을-다시-쏘기"&gt;&lt;a href="#%ea%b6%8c%ed%95%9c-%ec%97%86%ec%9d%b4-%ec%9a%94%ec%b2%ad%ec%9d%84-%eb%8b%a4%ec%8b%9c-%ec%8f%98%ea%b8%b0" class="header-anchor"&gt;&lt;/a&gt;권한 없이 요청을 다시 쏘기
&lt;/h2&gt;&lt;p&gt;&amp;ldquo;요청을 고쳐서 다시 보낸다&amp;quot;는 Postman의 핵심이다. 보통은 host 권한을 받아 백그라운드에서 아무 오리진에나 fetch를 쏜다. 하지만 그건 &amp;ldquo;네트워크를 가로채지 않는다&amp;quot;는 약속을 깨는 일이었다.&lt;/p&gt;
&lt;p&gt;그래서 다른 길을 골랐다. &lt;code&gt;chrome.devtools.inspectedWindow.eval&lt;/code&gt;로, 검사 중인 페이지 안에서 직접 fetch를 실행하는 것이다. 페이지가 자기 오리진으로 보내는 요청이라 쿠키·세션·CORS를 페이지가 알아서 처리해 주고, 확장은 추가 권한을 한 톨도 안 받는다. &amp;ldquo;이 사이트 API를 값만 바꿔 다시 찔러본다&amp;quot;는, 실제로 제일 흔한 시나리오가 이걸로 그대로 커버됐다.&lt;/p&gt;
&lt;h2 id="eval은-promise를-기다려-주지-않는다"&gt;&lt;a href="#eval%ec%9d%80-promise%eb%a5%bc-%ea%b8%b0%eb%8b%a4%eb%a0%a4-%ec%a3%bc%ec%a7%80-%ec%95%8a%eb%8a%94%eb%8b%a4" class="header-anchor"&gt;&lt;/a&gt;eval은 Promise를 기다려 주지 않는다
&lt;/h2&gt;&lt;p&gt;문제는 여기서 터졌다. inspectedWindow.eval은 표현식의 값을 돌려주는데, async fetch가 돌려주는 Promise를 기다려 주지 않는다. 아직 끝나지 않은 Promise가 그대로 넘어와서, 결과를 받을 방법이 없었다.&lt;/p&gt;
&lt;p&gt;돌아서 갔다. eval로 페이지 전역에 결과를 써두고, 그걸 폴링으로 읽는 방식이다.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;span class="lnt"&gt;6
&lt;/span&gt;&lt;span class="lnt"&gt;7
&lt;/span&gt;&lt;span class="lnt"&gt;8
&lt;/span&gt;&lt;span class="lnt"&gt;9
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-js" data-lang="js"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;__result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;__pending__&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kr"&gt;async&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kr"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;init&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kr"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;__result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;body&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;})();&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;started&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;})()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;그러고 나서 &lt;code&gt;window.__result&lt;/code&gt;를 짧은 간격으로 다시 eval해, &lt;code&gt;__pending__&lt;/code&gt;이 아니게 되는 순간을 잡는다. 비동기를 동기 폴링으로 묶어, 결국 권한 없이도 재전송이 돌아갔다.&lt;/p&gt;
&lt;h2 id="curl이-실행이-안-됐다"&gt;&lt;a href="#curl%ec%9d%b4-%ec%8b%a4%ed%96%89%ec%9d%b4-%ec%95%88-%eb%90%90%eb%8b%a4" class="header-anchor"&gt;&lt;/a&gt;cURL이 실행이 안 됐다
&lt;/h2&gt;&lt;p&gt;내가 만들어 준 cURL을 실제로 터미널에 붙여 넣어 봤더니 그냥 안 됐다. 원인을 따라가 보니, Chrome DevTools가 주는 HAR 안에는 &lt;code&gt;:authority&lt;/code&gt;·&lt;code&gt;:method&lt;/code&gt;·&lt;code&gt;:path&lt;/code&gt;·&lt;code&gt;:scheme&lt;/code&gt; 같은 HTTP/2 의사헤더가 섞여 있었다. 이걸 그대로 &lt;code&gt;-H&lt;/code&gt;로 내보내니 curl이 거부한 것이다. 그래서 헤더를 정규화하는 단계에서 &lt;code&gt;:&lt;/code&gt;로 시작하는 헤더를 전부 걸러냈더니, 화면 표시도 변환도 export도 한꺼번에 깨끗해졌다.&lt;/p&gt;
&lt;p&gt;한 가지가 더 있었다. 요청에는 &lt;code&gt;accept-encoding: gzip&lt;/code&gt;이 들어 있는데 cURL에 &lt;code&gt;--compressed&lt;/code&gt;가 없으면, 서버가 압축해서 보낸 응답을 curl이 풀지 못해 화면이 깨진다. 그래서 변환할 때 &lt;code&gt;accept-encoding&lt;/code&gt; 헤더는 빼고 대신 &lt;code&gt;--compressed&lt;/code&gt;를 붙이게 했다. 사소하지만 &amp;ldquo;그냥 안 되던&amp;rdquo; 진짜 이유였다.&lt;/p&gt;
&lt;h2 id="테스트는-순수-함수에-기댔다"&gt;&lt;a href="#%ed%85%8c%ec%8a%a4%ed%8a%b8%eb%8a%94-%ec%88%9c%ec%88%98-%ed%95%a8%ec%88%98%ec%97%90-%ea%b8%b0%eb%8c%94%eb%8b%a4" class="header-anchor"&gt;&lt;/a&gt;테스트는 순수 함수에 기댔다
&lt;/h2&gt;&lt;p&gt;마스킹·변환·필터·파싱·해시처럼 판단이 들어가는 로직은 전부 브라우저 API에 의존하지 않는 순수 함수로 떼어냈다(&lt;code&gt;src/core&lt;/code&gt;). 덕분에 cURL 이스케이프, Luhn 카드 감지, base64url 왕복, JWT 디코드, 퍼즈 범위 확장 같은 걸 136개 테스트로 묶어 둘 수 있었다. UI는 chrome.devtools를 목으로 바꿔 jsdom에서 컴포넌트 테스트로 돌렸는데, 가상 스크롤이 jsdom에서 행을 0개로 그려 버리는 함정이 있어 offsetHeight를 폴리필해 줘야 했던 건 덤이었다.&lt;/p&gt;
&lt;h2 id="같은-엔진-위에-얹은-워게임-도구"&gt;&lt;a href="#%ea%b0%99%ec%9d%80-%ec%97%94%ec%a7%84-%ec%9c%84%ec%97%90-%ec%96%b9%ec%9d%80-%ec%9b%8c%ea%b2%8c%ec%9e%84-%eb%8f%84%ea%b5%ac" class="header-anchor"&gt;&lt;/a&gt;같은 엔진 위에 얹은 워게임 도구
&lt;/h2&gt;&lt;p&gt;재전송 엔진이 생기니, 그 위에 연습용(인가된 워게임/CTF) 도구를 얹는 건 자연스러웠다. &lt;code&gt;${}&lt;/code&gt; 마커 자리에 &lt;code&gt;1..100&lt;/code&gt; 같은 페이로드를 순차로 밀어 넣어 응답의 길이·상태가 튀는 행을 자동으로 강조하는 Intruder형 퍼저, 토큰을 까보는 인코더/디코더와 해시, payload만 고치면 토큰을 다시 조립해 주는 JWT 에디터까지 — 전부 권한 0 재전송 위에서 돈다. 남용을 막으려고 단일 타깃과 딜레이를 두고, 인가된 환경 전용임을 분명히 적어 뒀다.&lt;/p&gt;
&lt;h2 id="돌아보면"&gt;&lt;a href="#%eb%8f%8c%ec%95%84%eb%b3%b4%eb%a9%b4" class="header-anchor"&gt;&lt;/a&gt;돌아보면
&lt;/h2&gt;&lt;p&gt;결국 3부의 어려움은 대부분 &amp;ldquo;권한을 안 받는다&amp;quot;는 1부의 한 줄에서 흘러나왔다. 그런데 그 제약을 우회하는 과정 자체 — 페이지 안에서 eval로 쏘고, 의사헤더를 걷어내고, 응답을 폴링으로 받는 — 가 결국 이 도구를 남들과 다르게 만든 부분이기도 했다. 제약을 정체성으로 받아들이면, 우회가 차별점이 된다.&lt;/p&gt;</description></item><item><title>#2 - 가린 cURL이 그대로 실행되게, 토큰을 변수 자리로</title><link>https://monkshark.github.io/p/api-inspector-product/</link><pubDate>Tue, 09 Jun 2026 00:00:00 +0000</pubDate><guid>https://monkshark.github.io/p/api-inspector-product/</guid><description>&lt;p&gt;기능을 더하는 것보다 빼는 게 더 어려웠다. 2부는 만든 것보다, 의심하고 줄이고 들어낸 이야기에 가깝다.&lt;/p&gt;
&lt;h2 id="무엇을-가릴-것인가"&gt;&lt;a href="#%eb%ac%b4%ec%97%87%ec%9d%84-%ea%b0%80%eb%a6%b4-%ea%b2%83%ec%9d%b8%ea%b0%80" class="header-anchor"&gt;&lt;/a&gt;무엇을 가릴 것인가
&lt;/h2&gt;&lt;p&gt;핵심 가치가 &amp;ldquo;안전한 공유&amp;quot;였으니, 화면에 보여줄 때도 변환할 때도 내보낼 때도 자동으로 가리게 했다. 가리는 대상은 둘이다. 하나는 자격증명으로 &lt;code&gt;Authorization&lt;/code&gt;, &lt;code&gt;Cookie&lt;/code&gt;, &lt;code&gt;*-token&lt;/code&gt;, 그리고 쿼리의 &lt;code&gt;token&lt;/code&gt;·&lt;code&gt;key&lt;/code&gt;·&lt;code&gt;password&lt;/code&gt;를 잡는다. 다른 하나는 본문에 섞여 있는 PII로, 신용카드 번호(Luhn으로 검증해 오탐을 줄이고 끝 4자리만 남긴다)·이메일·JWT·주민등록번호를 가린다. DevTools가 절대 대신 해주지 않는 영역이고, &amp;ldquo;토큰 유출 사고를 코드로 막는다&amp;quot;는 분명한 실무 가치가 있었다.&lt;/p&gt;
&lt;h2 id="근데-가리면-재현을-못-하잖아"&gt;&lt;a href="#%ea%b7%bc%eb%8d%b0-%ea%b0%80%eb%a6%ac%eb%a9%b4-%ec%9e%ac%ed%98%84%ec%9d%84-%eb%aa%bb-%ed%95%98%ec%9e%96%ec%95%84" class="header-anchor"&gt;&lt;/a&gt;&amp;ldquo;근데 가리면 재현을 못 하잖아&amp;rdquo;
&lt;/h2&gt;&lt;p&gt;스스로 던진 이 질문이 제일 중요한 기능을 끌어냈다. 마스킹한 cURL은 안전하지만 &lt;code&gt;Bearer ***MASKED***&lt;/code&gt;라 그대로 실행하면 401이다. 그래서 플레이스홀더 모드를 만들었다. 토큰 값은 지우되, 그 자리에 &lt;code&gt;$AUTH_TOKEN&lt;/code&gt;(cURL)이나 &lt;code&gt;{{AUTH_TOKEN}}&lt;/code&gt;(Postman) 같은 변수 자리를 남기는 것이다.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;curl &lt;span class="s1"&gt;&amp;#39;...&amp;#39;&lt;/span&gt; -H &lt;span class="s2"&gt;&amp;#34;Authorization: Bearer &lt;/span&gt;&lt;span class="nv"&gt;$AUTH_TOKEN&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;받는 사람은 자기 값만 채우면 바로 실행된다. 안전하면서도 재현이 되는, 둘 다 잡은 셈이다.&lt;/p&gt;
&lt;h2 id="토글을-줄여-사고를-줄였다"&gt;&lt;a href="#%ed%86%a0%ea%b8%80%ec%9d%84-%ec%a4%84%ec%97%ac-%ec%82%ac%ea%b3%a0%eb%a5%bc-%ec%a4%84%ec%98%80%eb%8b%a4" class="header-anchor"&gt;&lt;/a&gt;토글을 줄여 사고를 줄였다
&lt;/h2&gt;&lt;p&gt;처음엔 mask on/off와 placeholder on/off, 두 토글이 따로 있었다. 곧 사용자에게 네 가지 조합은 너무 많다는 걸 깨달았다. 특히 자격증명을 &lt;code&gt;***&lt;/code&gt;로만 내보내는 어중간한 조합은 거의 쓰이지 않았다. 그래서 변환·공유 모드를 원본과 안전(placeholder) 둘로 압축하고, &lt;code&gt;***&lt;/code&gt; 마스킹은 화면 표시와 본문 PII 전용으로만 남겼다. 토글 하나가 사라진 게 아니라, &amp;ldquo;공유용이냐 아니냐&amp;quot;라는 한 축으로 머릿속이 정리됐다.&lt;/p&gt;
&lt;h2 id="devtools랑-뭐가-다른지-정직하게-물었다"&gt;&lt;a href="#devtools%eb%9e%91-%eb%ad%90%ea%b0%80-%eb%8b%a4%eb%a5%b8%ec%a7%80-%ec%a0%95%ec%a7%81%ed%95%98%ea%b2%8c-%eb%ac%bc%ec%97%88%eb%8b%a4" class="header-anchor"&gt;&lt;/a&gt;DevTools랑 뭐가 다른지 정직하게 물었다
&lt;/h2&gt;&lt;p&gt;캡처·필터·cURL·HAR 같은 건 DevTools가 더 잘하거나 적어도 동급이다. 그건 인정했다. 대신 마스킹, 구조화된 export(Postman·문서), diff, 재전송 — 이쪽이 진짜 다른 점이라는 걸 분명히 하고 거기에 집중했다.&lt;/p&gt;
&lt;h2 id="만들었다가-죽인-기능-indexeddb-히스토리"&gt;&lt;a href="#%eb%a7%8c%eb%93%a4%ec%97%88%eb%8b%a4%ea%b0%80-%ec%a3%bd%ec%9d%b8-%ea%b8%b0%eb%8a%a5-indexeddb-%ed%9e%88%ec%8a%a4%ed%86%a0%eb%a6%ac" class="header-anchor"&gt;&lt;/a&gt;만들었다가 죽인 기능: IndexedDB 히스토리
&lt;/h2&gt;&lt;p&gt;&amp;ldquo;DevTools를 다시 열어도 이전 세션이 남아 있게&amp;rdquo; IndexedDB 영속을 넣었었다. 그런데 나중에 &amp;ldquo;처음엔 빈 화면으로 시작하고, import는 기존을 비우고 새로 채운다&amp;quot;로 UX를 정하자, 자동 영속이 갑자기 아무도 다시 읽지 않는 죽은 코드가 됐다. 그래서 idb 의존성까지 통째로 들어냈다. 저장과 복원은 세션 파일을 export하고 import하는 쪽으로 대체했는데, 오히려 명시적이고 파일로 백업·이동까지 된다. 코드는 줄고 동작은 더 정직해졌다.&lt;/p&gt;
&lt;h2 id="나머지-결정들"&gt;&lt;a href="#%eb%82%98%eb%a8%b8%ec%a7%80-%ea%b2%b0%ec%a0%95%eb%93%a4" class="header-anchor"&gt;&lt;/a&gt;나머지 결정들
&lt;/h2&gt;&lt;p&gt;export만 되고 import는 안 되던 비대칭도 없앴다. Postman Collection·HAR·세션 JSON 모두 왕복하게 했고, import는 파일 형식을 알아서 가려낸다. 마크다운 문서만 단방향인데, 요약본이라 본질적으로 되돌릴 수 없어서다.&lt;/p&gt;
&lt;p&gt;DevTools 패널은 사이트가 열려 있어야 뜨는데, 남이 준 HAR을 그냥 열어 보는 데까지 DevTools를 강요하는 건 어색했다. 그래서 툴바 아이콘을 누르면 새 탭으로 뜨는 뷰어를 따로 뒀다. 실시간 캡처는 패널이, 파일 분석은 뷰어가 맡고 둘은 같은 코드를 공유한다.&lt;/p&gt;
&lt;h2 id="돌아보면"&gt;&lt;a href="#%eb%8f%8c%ec%95%84%eb%b3%b4%eb%a9%b4" class="header-anchor"&gt;&lt;/a&gt;돌아보면
&lt;/h2&gt;&lt;p&gt;워게임용 도구(인코더·해시·퍼저)까지 손이 뻗쳤을 때, 멈출 자리를 정했다. &amp;ldquo;한 가지를 잘하는 도구&amp;quot;에서 멀어지면 차별성보다 유지보수만 늘어난다. 넣을 수 있다고 다 넣지 않는 것도 설계라고 생각했다. 결국 2부 내내 한 일은 더하기보다 빼기였다 — 헷갈리는 토글을 줄이고, 죽은 코드를 들어내고, 늘리고 싶은 욕심에서 멈췄다.&lt;/p&gt;</description></item><item><title>#1 - Copy as cURL이 토큰을 그대로 흘린다</title><link>https://monkshark.github.io/p/api-inspector-problem/</link><pubDate>Mon, 08 Jun 2026 00:00:00 +0000</pubDate><guid>https://monkshark.github.io/p/api-inspector-problem/</guid><description>&lt;p&gt;API Inspector는 브라우저 DevTools 안에 탭 하나로 들어가, 페이지가 주고받는 API 요청을 잡아 보여주고, 검색·마스킹·변환하고, 필요하면 그대로 다시 쏘는 확장이다. 한 줄로 줄이면 DevTools의 &amp;ldquo;Copy as cURL&amp;quot;을 검색·마스킹·변환·재현까지 끌어올린 도구다.&lt;/p&gt;
&lt;h2 id="network-탭은-보여주기만-잘한다"&gt;&lt;a href="#network-%ed%83%ad%ec%9d%80-%eb%b3%b4%ec%97%ac%ec%a3%bc%ea%b8%b0%eb%a7%8c-%ec%9e%98%ed%95%9c%eb%8b%a4" class="header-anchor"&gt;&lt;/a&gt;Network 탭은 보여주기만 잘한다
&lt;/h2&gt;&lt;p&gt;브라우저 기본 Network 탭은 요청을 잘 보여준다. 그런데 막상 개발하면서 하는 일은 보는 데서 끝나지 않는다. 정리하고, 검색하고, 다른 포맷으로 바꾸고, 남에게 넘기고, 그대로 재현하고, 문서로 남긴다.&lt;/p&gt;
&lt;p&gt;그리고 결정적으로, &amp;ldquo;Copy as cURL&amp;quot;은 &lt;code&gt;Authorization: Bearer ...&lt;/code&gt; 토큰을 한 글자도 빼지 않고 그대로 복사한다. 그걸 슬랙이나 지라에 붙이는 순간 자격증명이 영영 새어 나간다. 그래서 시작할 때 목표를 한 문장으로 박아 뒀다 — API 요청을 안전하게 공유하고, 받는 사람이 그대로 재현하게 한다.&lt;/p&gt;
&lt;h2 id="이름까지-철학에-맞췄다"&gt;&lt;a href="#%ec%9d%b4%eb%a6%84%ea%b9%8c%ec%a7%80-%ec%b2%a0%ed%95%99%ec%97%90-%eb%a7%9e%ec%b7%84%eb%8b%a4" class="header-anchor"&gt;&lt;/a&gt;이름까지 철학에 맞췄다
&lt;/h2&gt;&lt;p&gt;처음 이름은 API Sniffer였다. 직관적이긴 한데, sniffer(도청)라는 말이 &amp;ldquo;네트워크를 가로채지 않는다&amp;quot;는 이 도구의 핵심과 정면으로 부딪혔다. 그래서 API Inspector로 바꿨다. 이름 하나가 제품이 내세우는 약속을 배신하면 안 된다고 봤다.&lt;/p&gt;
&lt;h2 id="첫날-박은-한-줄-최소권한"&gt;&lt;a href="#%ec%b2%ab%eb%82%a0-%eb%b0%95%ec%9d%80-%ed%95%9c-%ec%a4%84-%ec%b5%9c%ec%86%8c%ea%b6%8c%ed%95%9c" class="header-anchor"&gt;&lt;/a&gt;첫날 박은 한 줄: 최소권한
&lt;/h2&gt;&lt;p&gt;가장 중요한 결정은 첫날 내렸다. webRequest도, host 권한도 쓰지 않는다. chrome.devtools API만 쓰고, storage 하나만 요청한다.&lt;/p&gt;
&lt;p&gt;이건 그냥 제약이 아니라 정체성에 가까웠다. 네트워크를 가로채는 확장은 사용자 신뢰를 깎고 웹스토어 심사도 까다롭다. 반대로 &amp;ldquo;DevTools API로만 동작한다 = 가로채지 않는다&amp;quot;는 그 자체가 팔 거리가 된다. 그리고 이 한 줄이 나중에 재전송 기능을 어떻게 만들지까지 전부 결정짓게 되는데, 그 고생은 3부에 적었다.&lt;/p&gt;
&lt;h2 id="돌아보면"&gt;&lt;a href="#%eb%8f%8c%ec%95%84%eb%b3%b4%eb%a9%b4" class="header-anchor"&gt;&lt;/a&gt;돌아보면
&lt;/h2&gt;&lt;p&gt;결국 방향은 &amp;ldquo;더 나은 Network 탭&amp;quot;이 아니었다. 안전하게 공유하고 그대로 재현하는 도구로 좁혔고, 이름까지 거기에 맞췄으며, 최소권한을 정체성으로 삼았다. 이 세 가지가 이후 모든 선택의 기준이 됐다.&lt;/p&gt;</description></item></channel></rss>