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

에이전트는 네 작업 디렉터리를 물려받아

~11 min · security, subprocess, agents, environment

Level 0미분류
0 XP0/36 lessons0/12 achievements
0/100 XP to next level100 XP to go0% complete

아무것도 거길 가리키지 않았어

샘플링 세션이 자길 부른 바로 그 서비스의 소스 코드를 읽고 있는 게 발견됐어. 프롬프트에 경로도 없고, 저장소를 지명하는 설정도 없고, 프로젝트를 둘러보라고 만든 도구도 없었어. 그냥 부모 프로세스가 돌던 디렉터리에서 시작했어. 그게 서비스 자기 체크아웃이었고. 거기 파일이 있는 걸 알아채고 쓴 거야.

이건 오작동이 아냐. 모든 서브프로세스가 부모 작업 디렉터리를 물려받아. 프로세스가 원래 그래. 바뀐 건 자식의 종류야. 평범한 서브프로세스는 시킨 걸 하고, 파일 접근이 있는 에이전트는 주변을 쓸 수 있는 맥락으로 취급하고 자기 주도로 탐색해.

물려받은 환경은 더 넓은 범주야

작업 디렉터리에서 그걸 보고 나면 물려받은 환경 나머지도 달라 보여. 자식은 부모 변수를 받아. 무관한 서비스의 자격 증명까지 포함해서. 그리고 사용자 신원도, 그 신원이 가진 파일 접근권까지 통째로. 어느 것도 의도적으로 부여된 게 아니고, 전부 그게 기본값이라서 도착해.

평범한 서브프로세스한텐 보통 괜찮아. 인자가 말하는 것만 하니까. 에이전트한텐 환경이 그냥 쓸 수 있는 게 아니라 재료야. 맞는 심적 모형은 "내가 돌리는 프로그램"이 아니라 "내가 어딘가에 놓아두는 호기심 많은 독자"고, 질문은 이렇게 돼. 내가 놓는 자리에서 손 닿는 데 뭐가 있지?

절반은 고쳤고, 나머지 절반은 안 고쳤어

위치 쪽은 한 줄이야. 서브프로세스를 전용 빈 디렉터리로 보내는 것. 실제로 그렇게 나갔고, 물려받은 위치를 통한 우발적 노출이라는 부류 전체가 사라졌어. 키워드 인자 하나 값이고, 어떤 프롬프트 엔지니어링도 못 닿는 실패 양상을 없앴지. 프롬프트는 애초에 문제가 아니었으니까. 에이전트는 저장소를 읽으라는 말을 들은 게 아니라 저장소 안에 놓인 거였어.

환경 쪽도 한 줄인데, 이 시스템엔 안 적혀 있어. 호출에 env 가 없어서 자식이 엔진이 든 변수를 전부 그대로 물려받아. 내가 냈으면 좋았을 버전을 슬쩍 보여주는 대신 이걸 대놓고 적어두는 게 맞아. 요약할 수 있는 에이전트는 자기 환경에서 주운 자격 증명을 되뱉을 수 있는 에이전트고, 여기서 아직 안 터진 이유는 아무도 그걸 시키지 않아서야. 그건 통제가 아니라 운이고.

진짜 피해가 날 자리

이번 경우 결과는 낭비된 토큰이랑 좀 헷갈린 샘플이었어. 이게 신뢰 못 할 입력 트랙에 속하는 이유는 뭐랑 조합되느냐야. 남의 글을 처리하는 에이전트, 파일 읽기 도구 보유, 그리고 소스랑 설정으로 가득한 디렉터리 안에 앉아 있기. 하나하나는 따로 놓고 변호 가능해. 합치면 남의 글에서 네 코드베이스 설명까지 가는 경로고, 세 번째는 순전히 우연히 도착했지.

에이전트는 그냥 실행되는 게 아니라 어딘가에 놓이는 거야. 그 위치에서 손 닿는 건 전부 잠재적 맥락이니까 위치를 의도적으로 골라. 기본값은 네 서비스가 어쩌다 사는 자리고, 그건 네가 절대 안 골랐을 유일한 자리야.

Code

에이전트를 의도적으로 놓기 — 그리고 끝내 안 적힌 절반·python
# BEFORE: inherits the parent's cwd. Nothing pointed the agent at the
# repository -- it simply STARTED there, found files, and read them.
subprocess.run([GROK_BIN, "-p", prompt, "--yolo"], capture_output=True)


# AFTER, as actually shipped: a dedicated neutral directory, created
# once and reused. Not a temporary one -- a fixed empty dir under the
# engine's own data root. That is enough, because what matters is not
# that the directory is fresh but that nothing interesting is
# reachable from it.
def _run_grok(prompt: str) -> dict:
    workdir = DB_DIR / "grok-workdir"
    workdir.mkdir(parents=True, exist_ok=True)
    proc = subprocess.run(
        [GROK_BIN, "-p", prompt,
         "--disallowed-tools", DISALLOWED_TOOLS,
         "--yolo", "--output-format", "json",
         "--max-turns", str(SOCIAL_MAX_TURNS)],
        cwd=str(workdir),          # <- the LOCATION half, closed
        capture_output=True,
        text=True,
        timeout=SOCIAL_SAMPLE_TIMEOUT_SECONDS,
    )

# Note what is NOT in that call: there is no `env=`. The child
# therefore still inherits every variable this process holds. The
# location half of the problem is closed; the environment half is
# open. Both are one keyword argument. Only one of them got written.

External links

Exercise

네 코드가 파일을 읽을 수 있는 서브프로세스를 낳는 자리를 전부 찾아서, 어떤 작업 디렉터리랑 환경을 받는지 확인해. 각각에 대해 그 디렉터리에서 손 닿는 걸 나열해봐. 소스, 설정, 자격 증명, 다른 프로젝트. 마지막으로 그중 뭘 일부러 줬을지 정해.
Hint
답은 거의 항상 '하나도'야. 아무도 작업 디렉터리를 일부러 안 주거든. 물려받는 거지. 손 닿는 것과 의도한 것 사이의 간극이 발견 전부고, 서비스 디렉터리에서 셸을 부르는 거의 모든 코드베이스에 있어.

Progress

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

댓글 0

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

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