---
type: explanation
title: Caputchin이 게임을 샌드박싱하는 방법
summary: "Caputchin 게임 둘레의 모든 격리 조치: 불투명 오리진 iframe, 그 단일 샌드박스 토큰, 엄격한 인라인 Content-Security-Policy, 무결성 해싱, 그리고 그 모든 것이 당신 자신의 페이지에 설정한 CSP와 어떻게 공존하는지."
---

Caputchin 게임은 당신의 방문자 브라우저에서, 당신의 페이지에서 도는 제3자 코드입니다. 그것이 마켓플레이스에서 오든 당신이 직접 호스팅하든, Caputchin은 그것을 **기본으로 신뢰되지 않는** 것으로 다루고 여러 독립 격리 계층으로 감싸니, 버그가 있거나 적대적인 게임도 렌더링하고, 플레이하고, 결과를 보고할 수 있지만, 그 밖에는 아무것도 닿을 수 없습니다: 당신의 페이지도, 당신의 방문자 데이터도, 네트워크도.

이 페이지는 모든 조치, 각각이 왜 존재하는지, 그리고 그 전체가 당신 자신의 사이트에 더하는 CSP와 어떻게 상호작용하는지 설명합니다.

## 한 줄로 된 위협 모델

게임은 제3자가 공급한 임의의 JavaScript(그리고 어쩌면 WebAssembly)를 돌립니다. 따라서 격리 목표는: **게임은 연산하고 그릴 수 있고, 결과를 위젯에 돌려줄 수 있지만, 자기 프레임 바깥의 어떤 것도 읽거나 영향을 줄 수 없다.** 아래 모든 계층이 그 하나의 목표를 섬기며, 그것들은 심층 방어입니다, 어떤 단일 계층도 완벽하다고 신뢰되지 않습니다.

```mermaid
flowchart TD
  P["당신의 페이지 (당신의 오리진)"] --> W["Caputchin 위젯 (당신의 오리진)"]
  W -->|iframe을 지음| F["샌드박스된 iframe (null 오리진)"]
  F --> G["게임: JS / WASM"]
  G -->|postMessage 결과만| W
  subgraph 프레임 안
    G
    L1["sandbox=allow-scripts만"]
    L2["인라인 Content-Security-Policy"]
    L3["Subresource Integrity (마켓플레이스)"]
  end
```

## 계층 1: 불투명 오리진 샌드박스 iframe

모든 게임은 위젯이 일부러 최소한의 `sandbox` 속성으로 짓는 `<iframe>` 안에서 돕니다. 부여되는 **유일한** 토큰은 `allow-scripts`인데, 게임이 JavaScript이고 돌아야 하기 때문입니다. 그 밖의 모든 것은 보류되며, 가장 중요한 누락은 `allow-same-origin`입니다.

`allow-same-origin` 없이 브라우저는 프레임에 고유한 **null 오리진**을 줍니다. 그 하나의 사실이 하중을 견디는 경계입니다:

- 게임은 당신 페이지의 DOM, 쿠키, `localStorage`, 또는 Caputchin 세션을 읽을 수 없습니다, 그것은 당신의 것과 낯선 일회용 오리진에 봉인되어 있습니다.
- 프레임은 또한 당신의 최상위 창을 결코 탐색하거나, 팝업을 열거나, 폼을 제출하거나, 네이티브 다이얼로그를 띄울 수 없으므로(그 모든 역량은 Caputchin이 부여하지 않는 샌드박스 토큰입니다), 게임에서 당신의 페이지로 도로 나가는 경로가 없습니다.

**`allow-same-origin` 없는 `allow-scripts`**의 조합이 요점 전체입니다: 브라우저는 게임의 코드를 실행하지만 프레임을 당신의 것을 아무것도 건드릴 수 없는 낯선 오리진으로 다룹니다. Caputchin은 게임 프레임에 결코 `allow-same-origin`을 더하지 않습니다. (그 결과로, 게임이 위젯에 post하는 모든 메시지는 오리진 `"null"`에서 도착하며, 위젯의 채널 확인이 그것을 기대합니다, null이 아닌 오리진은 그 자체로 잘못 구성된 샌드박스를 신호할 것입니다.)

## 계층 2: 프레임의 문서는 가져와지는 게 아니라 인라인으로 지어짐

위젯은 iframe을 원격 페이지로 향하게 하지 않습니다. 그것은 프레임의 전체 HTML 문서를 **인라인으로**(`srcdoc`를 통해) 구성해 null 오리진 프레임에 건넵니다. 그 문서는 작습니다: 엄격한 CSP 메타 태그(다음 계층), 작은 Caputchin 런타임 부트스트랩, 그리고 게임 번들을 로드하는 하나의 `<script>` 태그.

이것은 두 전달 경로 모두에 같은 기제이며, 번들 URL만 다릅니다:

| 게임 소스 | 인라인 문서의 번들 URL |
|---|---|
| 마켓플레이스 (플랫폼 해소) | Caputchin이 마운트 시 해소한, 고정되고 무결성 해시된 번들 URL. |
| 자체 호스팅 (당신의 `game-src`) | 당신이 공급한 URL. |

문서가 게임이 아니라 위젯에 의해 작성되므로, 게임은 CSP도, 런타임도, 프레임의 구조도 결코 제어하지 않으며, 로드된 뒤 자기 번들이 하는 것만 제어합니다.

## 계층 3: 마켓플레이스 게임을 위한 Subresource Integrity

Caputchin이 마켓플레이스 게임을 낼 때 그것은 번들을 불변 버전으로 고정하고 그것의 암호 해시(SHA-384)를 기록합니다. 번들을 로드하는 `<script>` 태그는 그 해시를 `crossorigin="anonymous"`와 함께 `integrity` 속성으로 지니니, 단 한 바이트라도 고정된 것과 다르면 **브라우저 자체**가 번들 실행을 거부합니다. 손상된 CDN은 다른 코드를 대체할 수 없습니다; 로드가 닫힌 채 실패합니다.

자체 호스팅 게임에는 플랫폼이 주장하는 해시가 없으니(당신이 당신 자신의 바이트의 신뢰 뿌리입니다), `integrity` 속성은 그 경로에서 그저 생략됩니다. 마켓플레이스 게임의 *결과*가 서버에서 독립적으로 다시 도출되는 방식, 무결성과는 별개의 보장은 [리플레이 계약](/docs/marketplace-game-development/replay-contract)을 보세요.

## 계층 4: 엄격한 인라인 Content-Security-Policy

인라인 문서는 빡빡한 `Content-Security-Policy`를 지닙니다. 프레임이 이미 null 오리진이고 샌드박스됐어도, CSP는 로드된 코드가 무엇을 할 수 있는지 더 옥죕니다, 그것은 기본으로 모든 것을 거부하고 최소한만 다시 부여합니다:

| 지시어 | 값 | 왜 |
|---|---|---|
| `default-src` | `'none'` | 아래에서 명시적으로 허용되지 않은 모든 것을 거부. |
| `script-src` | Caputchin 자체 인라인 런타임의 해시 + 게임 번들의 오리진 + `'wasm-unsafe-eval'` | Caputchin의 정확한 부트스트랩(`'unsafe-inline'`이 **아니라** `sha256-` 해시로 고정)과 게임 자체의 번들만 실행. `'wasm-unsafe-eval'`은 WASM 엔진이 전체 `'unsafe-eval'`을 부여하지 **않고** 컴파일하게 함. |
| `connect-src` | `'none'` | **빼내기 방지 지시어.** 게임은 어디로도 `fetch`, `XHR`, `WebSocket` 열기, 또는 비콘을 할 수 없음. 네트워크 출구 전혀 없음. |
| `img-src` | `data:` (더해 모든 스킨 자산 오리진, 아래) | 스프라이트는 인라인, 또는 허용된 자산 호스트에서. |
| `media-src` | `data:` (더해 모든 스킨 자산 오리진, 아래) | 오디오/비디오는 인라인, 또는 허용된 자산 호스트에서. |
| `font-src` | `data:` | 인라인 폰트만. |
| `style-src` | `'unsafe-inline'` | 게임은 인라인 스타일을 설정; 외부 스타일시트 없음. (스타일은 스크립트나 네트워크처럼 데이터를 빼낼 수 없음.) |

순효과: 게임은 돌고, 렌더링하고, SDK 브리지를 통해 결과를 보고하며, 그 밖에는 아무것도, 특히 네트워크에는 닿을 수 없습니다. 이 CSP는 Caputchin이 설정하며 당신이 구성하는 것이 아닙니다; 게임이 정확히 얼마나 갇혀 있는지 당신이 이해하도록 여기 나열되어 있습니다.

### 고객 설정이 프레임 CSP를 넓히는 한 곳

스킨은 이미지나 오디오 필드를 당신 자신의 CDN의 절대 URL로 가리킬 수 있습니다([스킨](/docs/game-customization/skin)을 보세요). 그 자산이 잠긴 프레임 안에서 로드되려면, Caputchin은 프레임의 `img-src` / `media-src`에 **그 정확한 자산 오리진만** 더하고, 그 밖에는 어디에도 더하지 않습니다. `script-src`와 `connect-src`는 결코 넓혀지지 않으니, 허용된 자산 오리진이라도 코드를 로드하거나 네트워크 채널을 여는 데 쓸 수 없습니다. 이것이 구성된 값이 프레임의 정책에 영향을 주는 단 하나의, 좁은 방법이며, 그것도 여전히 자산뿐입니다.

## 계층 5: 단방향 결과 채널

게임은 당신의 페이지로 호출 가능한 표면이 없습니다. 나가는 유일한 방법은 SDK 브리지입니다: 게임은 당신의 오리진에 사는 위젯으로 자기 불투명 결과(그 트레이스)를 `postMessage`하는 단일 메서드를 호출합니다. 위젯이 Caputchin의 API와 이야기하는 것입니다; 게임은 결코 그러지 않습니다(그럴 수 없습니다, `connect-src`가 `'none'`입니다). 그러니 신뢰 경로는 게임 → 위젯 → 당신의 백엔드이고, 각 도약은 결과를 앞으로만 넘기지, 결코 게임에 뒤로의 도달을 부여하지 않습니다.

## 당신 자신의 페이지에 설정하는 CSP [#the-csp-you-set-on-your-own-page]

위 계층은 *당신과 당신의 방문자를 게임으로부터* 보호합니다. 당신 자신의 페이지의 Content-Security-Policy는 다른, 보완하는 통제입니다: 그것은 *당신의 페이지를 모든 것으로부터* 보호하고, Caputchin은 엄격한 것 아래에서 돌도록 설계되었습니다. Caputchin을 임베드하려고 CSP를 느슨하게 할 필요는 없습니다; 위젯이 정당하게 쓰는 **몇 개의 오리진을 허용**하면 됩니다.

최소한, 위젯에 필요한 것:

| 지시어 | 허용 | 왜 |
|---|---|---|
| `script-src` | 당신이 위젯 스크립트를 로드하는 오리진, 더해 `'unsafe-eval'` | `<caputchin-widget>` / `<caputchin-game>`을 정의하는 `<script>`, 당신이 고른 [CDN](/docs/integration-guides/cdn)(jsDelivr, 당신 자신의 호스트, 또는 `caputchin.com`). `'unsafe-eval'`은 [계측](/docs/understanding-caputchin/instrumentation) 챌린지가 돌게 함; 계측이 켜진 동안 필수이며, 키의 [보안](/docs/site-keys/security) 페이지에서 계측을 꺼서 떨굴 수 있음. |
| `connect-src` | `https://verify.caputchin.com` | 위젯은 검증을 세우고 확인하려고 Caputchin 검증 서비스를 호출. 정확히 이 오리진을 허용. 위젯을 다른 API 호스트로 향하게 하면, 대신 그것을 허용. |
| `frame-src` | `https://caputchin.com` | 게임 iframe을 다스림. 프레임이 인라인 `srcdoc` 문서를 쓰므로, 브라우저가 당신의 `frame-src`를 그것에 적용하니, 여기 Caputchin 오리진을 허용. |
| `worker-src` | `blob:` | proof-of-work 풀이기는 전적으로 `blob:` URL에서 만들어진 Web Worker에서 돌고, 메인 스레드 대체가 없으니, 이것은 필수. `worker-src`를 생략하면, 브라우저는 `child-src` 다음 `default-src`로 물러나고, `default-src 'self'`만으로는 `blob:`을 허용하지 않음. |

선택적으로 `script-src`에 `'wasm-unsafe-eval'`을 더하세요: 그것은 proof-of-work 풀이기가 빠른 WebAssembly로 돌게 합니다. 필수는 아니며, 그것 없이 풀이기는 더 느린 순수 JavaScript 구현(여전히 Worker 안에서)으로 물러납니다.

당신의 페이지 CSP에서 게임 자체의 번들 오리진(jsDelivr, 게임의 CDN, 당신의 `game-src` 호스트)을 허용할 필요는 **없습니다**: 그 번들은 위에서 설명한 프레임 자체의 CSP 아래, *샌드박스된 프레임 안에서* 로드되지, 당신 페이지의 정책 아래가 아닙니다. 당신의 페이지는 Caputchin을 프레임할 뿐입니다; Caputchin이 안에서 도는 것을 가둡니다.

Caputchin 요소가 당신의 CSP 아래에서 조용히 로드에 실패하면, 당신의 브라우저 콘솔이 차단된 지시어의 이름을 댑니다; 그것이 대는 오리진이나 키워드를 그 지시어에 더하세요. 엄격하게 시작하고 콘솔이 청하는 것만 정확히 여세요. 당신은 `'unsafe-inline'`이 결코 필요 없습니다. 워커를 위해 `blob:`(`worker-src`)이, 그리고 계측이 켜진 동안 `script-src`에 `'unsafe-eval'`이 필요합니다; `'unsafe-eval'` 요건은 키의 [보안](/docs/site-keys/security) 페이지에서 계측을 끄면 사라집니다. `'wasm-unsafe-eval'`은 선택입니다(그것은 proof-of-work 풀이기를 빠르게 하며, 그것 없이는 같은 Worker 안에서 JavaScript로 더 느리게 돕니다).

## 왜 이렇게 많은 계층인가

각 계층은 그 자체로 의미 있는 경계일 것입니다; 함께 그것들은 하나의 실패가 침해가 아니라는 뜻입니다. null 오리진만으로도 이미 게임을 당신의 데이터로부터 벽으로 막고; CSP만으로도 이미 네트워크 출구를 죽이고; 무결성만으로도 이미 정확한 바이트를 고정합니다. Caputchin은 그 모두를 돌리는데, 신뢰되지 않는 제3자 코드가 바로 심층 방어가 제값을 하는 곳이기 때문입니다.

## 함께 보기

- [게임이 봇에 저항하는 방법](/docs/understanding-caputchin/gaming-anti-cheat): 게임 무결성의 서버 권위 측면.
- [리플레이 계약](/docs/marketplace-game-development/replay-contract): 게임의 결과가 서버 측에서 독립적으로 다시 도출되는 방식.
- [자체 호스팅 게임 빌드하기](/docs/custom-game-development/self-hosted-game): 이 샌드박스가 작성자에게 부과하는 번들 제약.
- [CDN에서 위젯 로드하기](/docs/integration-guides/cdn): 당신의 페이지 CSP가 허용해야 하는 스크립트 오리진 고르기.
