스폰서LobeHubLobeHub자세히 알아보기
dshfind

5강: 보안 경계: 무엇을 건드릴 수 있고, 무엇을 건드릴 수 없는가

한 문장 요약: DSH의 보안 철학은 「기본적으로 신뢰하지 않음」입니다 —— 플러그인은 무엇을 사용할지 먼저 선언하고, 명령은 샌드박스 안에서 래핑되며, 파일 변경은 정책 이벤트를 통과하고, 시크릿은 항상 참조 형태로만 존재합니다 —— 「무엇을 건드릴 수 있는가」는 구호가 아니라, 계층별로 검증할 수 있는 네 개의 방어선입니다.

1. 사용자 스토리: 낯선 플러그인이 감히 함부로 행동하지 못하는 이유는?

커뮤니티에서 작은 플러그인 하나를 발견했습니다: 「원클릭 Markdown 서식 미화」. 이것은 당신의 컴퓨터에서 실행되며, 파일을 읽고 쓸 수 있고, 명령도 실행할 수 있습니다. 설치하기 전에 소박한 질문이 떠오릅니다: 이 플러그인이 조용히 내 파일을 삭제하거나, 터미널에 있는 시크릿을 몰래 어떤 서버로 보내지 않을 것이라고 무엇을 근거로 믿을 수 있을까요?

전통적인 답은 「신뢰에 의존」입니다: 작성자의 평판을 보고, 소스 코드를 한 번 읽고, 악행을 저지르지 않기를 바라는 것이죠. DSH의 답은 다릅니다 —— 신뢰가 아니라 구조에 의존합니다. 플러그인, 모델, 명령은 기본적으로 「아무것도 건드릴 수 없으며」 무언가를 건드리려 할 때마다 게이트를 통과해야 합니다. 네 개의 방어선이 겹겹이 쌓여 있습니다:

방어선무엇을 관리하는가누가 집행하는가
① 의존성 선언플러그인이 어떤 서비스와 의존성을 얻을 수 있는가로더 + 컨텍스트 프록시(Proxy)
② 프로세스 샌드박스명령이 호스트 파일 시스템의 어느 부분을 읽고 쓸 수 있는가ctx.sandbox
③ 파일 정책 이벤트쓰기와 편집은 먼저 읽어야 하고, 버전이 일치해야 함fs 정책 이벤트 게이트
④ 가드와 승인범위를 벗어난 작업을 허용할지, 어느 정도 폭으로승인 가드 / guard 플러그인

아래 네 섹션에서 하나씩 분해해 보겠습니다. 먼저 「신뢰 문제」에 가장 가까운 첫 번째 방어선부터 살펴 봅시다.

2. 첫 번째 방어선: 선언이 곧 능력, 미선언은 거부

2장 13강에서 배운 접근 제어를 기억하나요? Cordis 논문에는 핵심 메커니즘이 있습니다: 컴포넌트는 자신이 「선언한」 의존성에만 접근할 수 있고, 선언하지 않은 것에 접근하면 오류가 발생합니다 —— JavaScript의 컨텍스트 프록시(Proxy)가 접근할 때마다 키가 선언 목록에 있는지 확인하고, 없으면 거부합니다. 이것이 「능력 기반 보안」(capability-based security)입니다: 권한은 참조를 보유하는 데서 오지, 「이 환경에 있는 사람」이라는 데서 오지 않습니다.

DSH는 이 원칙을 그대로 플러그인 체계에 도입했습니다:

  • 플러그인은 로드 시점에 어떤 의존성을 주입받을지 정적으로 선언합니다 —— inject 선언이 곧 능력 요청입니다;
  • 선언하지 않은 접근은 런타임에 프록시가 거부합니다: 코드에 적혀 있어도 얻을 수 없습니다;
  • 선언이 정적이기 때문에 로더는 로드 시점에 심사하고 승인할 수 있으며, 접근이 발생한 후에야 항목을 하나씩 발견할 필요가 없습니다.

따라서 「낯선 플러그인」이 손에 쥐는 것은 자신이 선언한 몇 가지뿐입니다. 범위를 벗어나고 싶다고요? 첫 번째 게이트가 그것을 막습니다.

3. 두 번째 방어선: 프로세스 샌드박스와 파일 정책 이벤트

언어 수준의 검사로는 악성 코드를 막을 수 없습니다 —— 코드가 호스트 런타임에 닿을 수 있는 한, 하위 객체를 직접 조작할 수 있습니다. 그래서 DSH는 「명령」에 손을 대습니다: 프로세스를 샌드박스 안에서 실행하게 하는 것입니다.

3.1 샌드박스가 하는 일: argv 래핑, 프로세스 제한

프로세스 샌드박스의 핵심은 「argv 래핑」이라는 동작이며, 저장소의 설명은 매우 정확합니다:

ctx.sandbox.confine(argv, policy)는 spawn에 사용할 argv를 반환하며, 호출자의 원래 argv를 대체해야 합니다. 반환값은 래핑되어 프로세스와 그것이 파생하는 모든 프로세스가 제한 하에서 실행됩니다…… 사용 가능한 백엔드가 없으면 예외를 던지며, argv를 그대로 전달하여 제한 없이 실행되게 하는 일은 결코 없습니다.

—— 출처: packages/sandbox/sandbox/README.zh.md

쉽게 풀어 말하면:

  • 실행하려는 명령은 시작되기 전에 먼저 「우리」 한 겹에 싸입니다;
  • 이 명령뿐만 아니라, 그것이 파생하는 모든 자식 프로세스도 같은 우리 안에 있습니다 —— 명령이 하위 명령을 띄워도 벗어날 수 없습니다;
  • 이 머신에 사용 가능한 샌드박스 백엔드가 하나도 없다면, DSH는 실행을 거부하며, 결코 무방비로 실행하지 않습니다.

백엔드는 플랫폼 네이티브입니다: Linux에서는 bubblewrap(bwrap) 또는 Landlock, macOS에서는 Seatbelt(sandbox-exec)를 사용합니다. 이들은 호스트와 파일 시스템 및 커널을 공유하지만, 파일 효과는 정책에 의해 엄격히 제약됩니다.

3.2 호출별 정책: 같은 샌드박스, 다른 우리

샌드박스는 「이 명령이 어디까지 건드릴 수 있는가」를 어떻게 알까요? 답은 호출별 정책에 있습니다 —— 정책은 샌드박스 제공자에 붙어 있는 고정 설정이 아니라, 매번 호출과 함께 전달됩니다. 세 가지 모드가 있습니다:

모드무엇을 할 수 있는가전형적인 시나리오
read-only읽기 전용; 쓰기는 모두 거부(/dev/null 등 필수 출구만 유지)기본 모드, 장애 안전(fail-safe)
workspace-write세션 워크스페이스 루트 + 플랫폼 임시 영역(예: /tmp)에 쓰기 가능정상적인 작업
danger-full-access제한 없음명시적으로 신뢰하는 호출

두 가지 요점:

  • 기본값은 read-only, 장애 안전: 파일을 쓰려면 먼저 이 호출이 workspace-write 자격이 있음을 증명해야 합니다;
  • 정책은 호출과 함께 이동하고, 제공자에 붙어 있지 않습니다: bash는 read-only로 실행될 수 있고, 제한된 하위 에이전트는 동시에 자신의 상태 디렉터리를 쓰기 가능하게 유지할 수 있습니다; 승인된 권한 상승 재시도는 더 넓은 정책으로 발행하는 새 호출일 뿐입니다.

이 세 가지 모드는 파일 작업만 제약한다는 점에 주의하세요 —— 네트워크와 프로세스 가시성은 이 어휘에 없습니다(5절에서 그것들이 누구 소관인지 다룹니다).

3.3 파일 시스템 정책 이벤트: 쓰기 전에 읽기, 버전으로 덮어쓰기 방지

의존성 선언은 「플러그인이 함부로 가져가는 것」을 막았고, 프로세스 샌드박스는 「명령이 함부로 파일을 쓰는 것」을 막았습니다. 그렇다면 모델이 파일 도구를 통해 읽고 쓰는 파일은요? 한 겹 더 있습니다: 파일 시스템 정책입니다.

먼저 「샌드박스화된 파일 시스템」 백엔드(fs-sandbox)를 보겠습니다. 이것은 쓰기에만 호출별 모드 펜스를 씌우며, 소박한 원칙이 하나 있습니다: 읽기는 항상 그대로 통과한다 —— 모든 모드가 읽기를 허용한다. 구체적으로:

  • read-only에서는 모든 변경이 구조화된 거부가 됩니다(오류 코드 FS_SANDBOX_DENIED, 현재 모드를 함께 전달);
  • workspace-write에서는 대상을 정규화한 결과가 쓰기 가능한 루트(워크스페이스 루트 + 플랫폼 임시 영역) 안에 있을 때만 변경이 허용됩니다;
  • danger-full-access에서는 펜스 없이 바로 위임합니다.

(출처: packages/fs/fs-sandbox/README.zh.md)

한 단계 위에는 파일 시스템 스택의 정책 계층(fs-observation-policy 플러그인)이 있습니다. 이것은 어떤 서비스 메서드도 제공하지 않고, fs/* 이벤트 게이트를 통해서만 참여하며, 「편집 위생」을 전담합니다:

  • 편집 전에 반드시 읽었어야 함: 어떤 파일을 읽지도 않고 edit 하려 하면 바로 거부됩니다 —— 「edit requires reading the file first」;
  • 버전 보호: 쓰기와 편집은 관측된 버전을 기반으로 CAS(compare-and-swap)를 수행하며, 파일이 다른 사람에 의해 변경되었다면 버전이 오래되었다는 오류(FS_STALE_VERSION)를 보고하고 다시 읽고 재시도하도록 안내합니다;
  • 이점: 모델이 오래된 뷰를 바탕으로 파일을 맹목적으로 수정하는 일이 없고, 동시에 쓰는 작성자가 조용히 덮어씌워지는 일도 없습니다.

(출처: packages/fs/fs-observation-policy/README.zh.md)

3.4 가드와 승인: 한 번 저지된 범위 외 쓰기

마지막 게이트는 「권한 상승」 경로에 있습니다. 모델은 거부를 만난 후 단 한 번 더 넓은 권한으로 재시도를 발행할 수 있습니다 —— 하지만 이 재시도는 가드와 승인을 통과해야 합니다. 가드(guard) 플러그인은 에이전트 루프의 루프 위생(반복되는 도구 호출에 대한 권고성 알림, 단일 호출의 시간 예산)을 감시하는 역할을 하고, 더 넓은 모드 전환 자체는 명시적 이벤트입니다: 세션이 모드를 전환한다는 것은 sandbox/mode 이벤트를 하나 추가하는 것이고, 승인된 권한 상승 재시도는 더 넓은 정책으로 발행하는 새 호출일 뿐입니다.

전체 체인을 따라가 보면 완전한 실제 사례가 됩니다(정책 설계는 저장소의 fs-sandbox와 sandbox-policy에서 가져옴):

모델이 app/config.json에 쓰기를 원함
  ↓ 현재 정책은 read-only
  ↓ 쓰기 거부: FS_SANDBOX_DENIED, [sandbox: file access denied under read-only mode]로 렌더링됨
  ↓ 도구 계층이 단 한 번의 권한 상승 재시도 안내를 제공
  ↓ 가드와 승인: 이 호출은 자격이 있는가?
  ↓ 승인됨 → workspace-write로 새 호출 발행, 대상이 워크스페이스 안 → 쓰기 성공

모든 단계에 근거가 있습니다: 정책 해석 결과, 거부 사유, 승인 결정이 모두 세션 로그에 남습니다.

沙箱(sandbox)智能体+ 它调用的工具允许 ✓工作区文件 · 受管进程逐调用策略sandboxPolicy / 审批系统外部 ×沙箱外摸不到 ×

沙箱 + 策略 + 审批:智能体只碰它被允许碰的东西

4. 세 번째 방어선: 설정과 자격 증명

앞의 두 방어선은 「코드가 무엇을 건드릴 수 있는가」를 관리합니다. 세 번째는 「설정 안에 무엇이 있는가」를 관리합니다 —— 특히 시크릿을요.

4.1 설정: 플러그인별 네임스페이스

DSH의 사용자 설정은 등록된 네임스페이스를 통해 해석됩니다: 각 플러그인은 settings에 자신의 네임스페이스를 등록하고, 서로 간섭하지 않습니다. settings 패밀리는 「네임스페이스 등록, 계층적 해석, 커밋을 정의하는」 서비스와 「설정을 로컬 파일에 저장하고 외부 편집을 감시하는」 제공자로 구성됩니다 —— 한 플러그인의 설정 항목이 다른 플러그인의 설정 항목을 사칭하는 일은 결코 없습니다.

(출처: packages/settings/README.zh.md)

4.2 자격 증명: 이름 있는 시크릿 참조, 인라인은 절대 금지

설정에서 가장 위험한 것은 시크릿입니다. DSH의 규칙은 단 한 문장으로, 자격 증명 서비스 정의의 README 맨 앞에 적혀 있습니다:

설정은 기밀에 대한 참조만을 담고, 기밀 그 자체는 결코 담지 않습니다. settings 섹션이나 cordis.yml 항목에는 apiKeyEnv: DEEPSEEK_API_KEY라고 쓰며, 참조 뒤에 있는 값은 자격 증명 제공자가 소유합니다.

—— 출처: packages/credentials/credentials/README.zh.md

즉: 설정에 sk-xxx 같은 평문이 나타나는 일은 결코 없습니다. 당신이 쓰는 것은 이름 있는 참조(예: apiKeyEnv: DEEPSEEK_API_KEY)이고, 실제 값은 자격 증명 제공자에게 보관됩니다(로컬에서는 $DSH_HOME/.credentials.yaml이며, 0700 디렉터리 안에 0600 권한으로 저장). 참조는 매 작업이 시작될 때 해석되고, 작업을 넘나들며 캐시되는 일은 결코 없습니다 —— 변경된 자격 증명은 플러그인을 재시작할 필요 없이 다음 요청부터 적용됩니다.

이것은 세 가지 소박한 이점을 가져옵니다:

  • 설정 문서를 안심하고 동기화하고, 안심하고 설정 UI에 렌더링할 수 있습니다 —— 안에 기밀이 전혀 없으니까요;
  • 키를 교체할 때 설정 파일을 전혀 건드리지 않습니다 —— 바꾸는 것은 자격 증명 제공자이지, 설정이 아니니까요;
  • 빈 저장값은 존재하지 않는 것과 같습니다 —— 공백이 설정된 시크릿을 가장하는 일은 결코 없습니다.

5. 샌드박스 너머: 신뢰할 수 없는 코드에는 격리된 런타임이 필요하다

앞의 세 방어선은 모두 한 가지를 전제합니다: 코드가 호스트와 파일 시스템 및 커널을 공유하므로 「정책」으로 다스릴 수 있다는 것. 하지만 코드가 근본적으로 신뢰할 수 없다면 어떻게 할까요? 2장 13강에서 논문 5.2절의 결론을 배웠습니다: 신뢰할 수 없는 코드는 격리 경계 밖으로 던져야 합니다 —— 언어 수준의 검사로는 악성 코드를 막을 수 없으며, 진정한 격리에는 언어 수준을 넘어선 실행 경계가 필요합니다: 소프트웨어 장애 격리, 격리된 언어 런타임, 샌드박스 프로세스, 가상화 컨테이너.

DSH의 대응 설계도 마찬가지로 깔끔합니다. 샌드박스 서비스의 README에는 명확히 적혀 있습니다:

호스트와 파일 시스템 및 커널을 공유하는 제한만을 지원합니다. 컨테이너, microVM, 원격 실행기는 이 seam의 백엔드가 아닙니다: 이들은 환경 일관된 그룹으로서 능력 seam 전체의 Service provider(ctx.shell, ctx.fs)를 교체합니다.

—— 출처: packages/sandbox/sandbox/README.zh.md

풀어 말하면: bwrap, Landlock, Seatbelt 같은 메커니즘은 호스트와 파일 시스템 및 커널을 「공유한다」는 전제 아래의 정책 제한입니다; 배포 측이 요구하는 것이 완전한 격리(컨테이너, microVM, 원격 실행)라면, 그것은 샌드박스에 백엔드를 하나 추가하는 문제가 아니라 실행 능력 전체(bash, fs)를 다른 구현으로 교체하는 문제입니다. 파일 시스템 백엔드의 README도 같은 입장입니다 —— 「정책 펜스이지, 커널 경계가 아니다」: 제약을 제공하지만 보안 경계는 아니며, 신뢰할 수 없는 코드의 커널 수준 격리는 여전히 실행 능력의 책임입니다.

그래서 보안 토폴로지 다이어그램을 볼 때는 먼저 이렇게 물어보세요: 이 코드는 「제한된 이웃」인가, 아니면 「다른 세계에 갇힌 죄수」인가? 전자는 샌드박스 정책에 의지하고, 후자는 실행 세계를 교체하는 데 의지합니다 —— DSH는 두 길을 모두 마련해 두었습니다.

6. 핵심 정리

  1. 기본적으로 신뢰하지 않음: 능력은 선언과 참조에서 오지, 「환경 안에 있다」는 데서 오지 않습니다.
  2. 선언이 곧 능력 요청: 플러그인은 선언한 의존성에만 접근할 수 있고, 미선언 접근은 Proxy가 거부하며, 로드 시점에 심사하고 승인할 수 있습니다.
  3. 샌드박스는 argv를 래핑: 프로세스와 그 후손이 모두 제한 하에서 실행됩니다; 사용 가능한 백엔드가 없으면 실행을 거부하며, 결코 무방비로 실행하지 않습니다.
  4. 호출별 정책: read-only(기본값, 장애 안전)/workspace-write/danger-full-access, 정책은 호출과 함께 이동합니다.
  5. 파일 정책 이벤트: 쓰기 전에 반드시 읽기, 버전 CAS로 덮어쓰기 방지; 거부는 구조화된 오류이지 조용한 실패가 아닙니다.
  6. 시크릿은 인라인하지 않음: settings는 플러그인별로 네임스페이스화; credentials는 이름 있는 참조이고, 작업별로 해석되며, 값은 제공자가 소유합니다.
  7. 샌드박스는 만능이 아님: 신뢰할 수 없는 코드에는 실행 세계의 교체(컨테이너/microVM/원격 실행)가 필요하며, 정책 추가만으로는 부족합니다.

🚀 다음 강의에서는 「지각과 컨텍스트」로 들어갑니다: 모델이 코드베이스, 웹 페이지, 실행 중인 환경을 어떻게 「보는가」 —— 그리고 이러한 지각 채널도 마찬가지로 정책과 승인의 제약을 받는다는 것을 다룹니다.

자가 테스트 · 보안 경계

답을 모두 선택한 후 「답안 제출」을 클릭하면 정답 여부와 해설을 확인할 수 있습니다.

1. 「의존성 선언이 곧 능력 요청」에 대해 올바른 설명은 무엇인가요?
2. 프로세스 샌드박스의 「argv 래핑」이 가장 핵심적으로 보장하는 보안은 무엇인가요?
3. read-only 모드에서 모델이 파일에 쓰려고 하면 어떻게 되나요?
4. 자격 증명(credentials)에 대해 올바른 설명은 무엇인가요?