기능과 보안

이 페이지

샌드박스 플러그인은 기본적으로 격리되어 있습니다. 자체 KV 및 스토리지 읽기/쓰기 이상의 작업을 수행하려면 플러그인이 매니페스트에서 기능을 선언해야 합니다. 샌드박스 브리지는 이러한 선언에 따라 호스트가 제공하는 각 API를 제어합니다 — content:read를 선언하지 않은 플러그인은 ctx.content를 얻지 못하고, network:request를 선언하지 않은 플러그인은 ctx.http를 얻지 못합니다.

이 페이지는 각 기능이 무엇을 허용하는지, 샌드박스가 이를 어떻게 강제하는지, 그리고 강제할 수 없는 것이 무엇인지를 다룹니다.

기능 선언

기능은 slug 및 나머지 신뢰 계약과 함께 emdash-plugin.jsonc에 있습니다:

{
	"slug": "plugin-hello",
	// ...아이덴티티 + 프로필...

	"capabilities": ["content:read", "network:request"],
	"allowedHosts": ["api.example.com"]
}

플러그인이 실제로 필요로 하는 것만 선언하세요. 기능 선언은 Marketplace가 동의 다이얼로그에서 운영자에게 보여주는 것이기도 합니다 — 추가 기능은 설치 시 마찰이 되고 감사 시 보안 신호가 됩니다.

기능 레퍼런스

기능접근 허용 대상
content:readctx.content.get(), ctx.content.list()
content:writectx.content.create(), ctx.content.update(), ctx.content.delete() (content:read 포함)
taxonomies:readctx.taxonomies.getAll(), ctx.taxonomies.getTerms(), ctx.taxonomies.getEntryTerms()
media:readctx.media.get(), ctx.media.list()
media:writectx.media.getUploadUrl(), ctx.media.upload(), ctx.media.delete() (media:read 포함)
network:requestctx.http.fetch()allowedHosts로 제한
network:request:unrestrictedctx.http.fetch() 호스트 제한 없음 (사용자 구성 URL 전용)
users:readctx.users.get(), ctx.users.getByEmail(), ctx.users.list()
email:sendctx.email.send() (구성된 이메일 제공자 플러그인 필요)
hooks.email-transport:register독점적 email:deliver 훅(전송 제공자) 등록 허용
hooks.email-events:registeremail:beforeSend / email:afterSend 훅 등록 허용
hooks.page-fragments:registerpage:fragments 훅 등록 허용 (네이티브 플러그인만)

알아야 할 사항:

  • 함축. content:write는 자동으로 content:read를 포함합니다. media:writemedia:read를 포함합니다. network:request:unrestrictednetwork:request를 포함합니다. 둘 다 나열할 필요가 없습니다.
  • 택소노미는 별도의 읽기 전용 표면입니다. taxonomies:readctx.taxonomies를 통해 택소노미 정의, 해당 용어, 항목에 할당된 용어에 대한 접근을 허용합니다. content:read와 독립적입니다 — 플러그인이 콘텐츠 그 분류를 읽는 경우 둘 다 선언하세요. 플러그인에서 택소노미에 대한 쓰기 접근은 없습니다.
  • network:request:unrestricted는 사용자 구성 URL을 위해 존재합니다. 운영자가 대상 URL을 입력하는 웹훅 플러그인은 매니페스트에 없는 호스트에 도달해야 합니다. 항상 알려진 API를 호출하는 플러그인은 network:request + allowedHosts를 사용해야 합니다.
  • email:send는 기능만이 아닌 구성으로 제어됩니다. 플러그인은 email:send를 선언할 수 있지만, ctx.email은 다른 플러그인이 email:deliver 전송을 등록한 경우에만 채워집니다.

네트워크 호스트 허용 목록

network:request가 있는 플러그인은 allowedHosts에 나열된 호스트에만 fetch할 수 있습니다. 하위 도메인에 대해 와일드카드가 지원됩니다:

"capabilities": ["network:request"],
"allowedHosts": [
	"api.example.com",     // 정확한 호스트
	"*.cdn.example.com"    // cdn.example.com의 모든 하위 도메인
]

브리지는 요청을 전달하기 전에 요청 URL의 호스트를 허용 목록과 대조합니다. 선언되지 않은 호스트에 대한 요청은 샌드박스를 벗어나지 않고 플러그인 내에서 예외를 발생시킵니다.

network:request:unrestricted는 허용 목록 검사를 완전히 건너뜁니다. 운영자가 런타임에 대상 URL을 구성하는 플러그인(웹훅 발신자, 범용 HTTP 포워더)을 위한 것입니다. 대상이 플러그인 설계의 일부인 플러그인에는 사용하지 마세요 — 대신 명시적 호스트로 network:request를 선언하여 동의 다이얼로그가 운영자에게 플러그인이 어디를 호출할지 정확히 알려주도록 하세요.

샌드박스가 강제하는 것

샌드박스 러너가 활성화되면, 런타임은 다음을 강제합니다:

  1. 기능 게이팅. PluginContext 팩토리는 해당 기능이 선언된 경우에만 ctx.content, ctx.taxonomies, ctx.media, ctx.http, ctx.users, ctx.email을 채웁니다. 선언되지 않은 기능의 메서드를 호출하는 것은 불가능합니다 — 거기에 객체가 없습니다.

  2. 스토리지 및 KV 스코핑. 모든 스토리지 및 KV 작업은 플러그인의 slug로 범위가 지정됩니다. 플러그인은 다른 플러그인의 KV나 스토리지 컬렉션을 읽을 수 없으며, 매니페스트에서 선언한 스토리지 컬렉션에만 접근할 수 있습니다.

  3. 네트워크 격리. 직접 fetch()와 기타 네트워크 프리미티브는 러너에 의해 차단됩니다. 네트워크로의 유일한 경로는 브리지의 호스트 검증을 거치는 ctx.http.fetch()입니다.

  4. 호스트 바인딩 없음. 샌드박스 플러그인은 환경 변수, 파일 시스템, 플랫폼 바인딩을 볼 수 없습니다 — 호스트 워커가 이를 가지고 있더라도. 플러그인 런타임은 브리지와 선언된 기능만 있는 깨끗한 격리체입니다.

  5. 리소스 제한. 러너는 호출당 CPU, 서브 요청, 월 클록, 메모리 제한을 적용할 수 있습니다. 정확한 제한은 사용 중인 러너에 따라 다릅니다. Cloudflare 러너는 플랫폼의 Worker Loader 제한을 사용합니다(호출당 CPU 50ms, 서브 요청 10개, 월 클록 30초, 메모리 ~128MB). Node.js workerd 러너(@emdash-cms/sandbox-workerd)는 Promise.race를 통해 월 클록 시간을 적용합니다. CPU와 메모리 제한은 Cloudflare 플랫폼 기능이며 독립 실행형 workerd에서는 적용되지 않습니다. 러너의 제한을 초과하는 훅은 취소됩니다. EmDash 훅 타임아웃(훅 구성의 timeout)은 더 엄격한 상한을 추가로 적용합니다.

샌드박스가 강제하지 않는 것

기능 시스템이 다루지 않고 다룰 수 없는 일부 사항:

  • 허용된 기능 내의 동작. content:write가 있는 플러그인은 자체 콘텐츠뿐만 아니라 모든 콘텐츠를 편집할 수 있습니다. 기능은 세분화되지 않습니다 — “이 플러그인은 콘텐츠를 쓸 수 있다”고 말하지, “이 플러그인은 자신이 만든 콘텐츠만 쓸 수 있다”고 말하지 않습니다. 감사 시 검토가 플러그인이 권한 내에서 실제로 무엇을 하는지에 대한 유일한 검사입니다.
  • Node.js에서의 운영자 신뢰. 구성된 샌드박스 러너가 사용 불가하다고 보고하면(Cloudflare Worker Loader 없음, Node 측 러너 미설치 등), sandboxed: [] 플러그인은 시작 시 건너뜁니다. 인프로세스로 실행하기 위해 plugins: []로 이동할 수 있습니다 — 하지만 그러면 V8 격리체, 리소스 제한이 없으며 플러그인이 직접 fetch()를 호출하거나 환경 변수를 읽을 수 있습니다. 이를 네이티브 수준의 신뢰로 취급하세요.
  • 사이드 채널. 타이밍, 로그 출력, 저장된 데이터는 호스트 환경에 대한 합리적인 접근 권한을 가진 모든 사람에게 보입니다. 샌드박스를 실행하는 운영자에 대한 기밀성 경계로 사용하지 마세요.

기능 동의

운영자가 Marketplace에서 샌드박스 플러그인을 설치하면, EmDash는 선언된 기능이 포함된 동의 다이얼로그를 표시합니다. 기능을 추가하는 업데이트 — 예를 들어, 이전에는 콘텐츠만 읽던 플러그인이 이제 네트워크 요청을 하고 싶은 경우 — 는 기능 diff로 표시되며 새 버전이 적용되기 전에 새로운 승인이 필요합니다.

이것이 “나중에 필요할 수도 있는” 추가 기능을 선언하는 것이 중요한 이유입니다. 모든 설치 및 업데이트에서 마찰로 나타나며, 보안 감사는 명백히 필요한 것보다 더 많이 요청하는 플러그인에 플래그를 지정합니다. 플러그인이 사용하는 것을 정확히 나열하고, 플러그인이 실제로 사용하기 시작할 때 실제 버전에서 새 기능을 추가하세요.

빌드 시 검증

emdash-plugin bundleemdash-plugin publish는 추가 검사를 실행합니다:

  • 선언된 각 기능은 인식된 세트에 있어야 합니다(오타는 빌드를 실패하게 합니다).
  • network:request는 비어 있지 않은 allowedHosts가 필요합니다. network:request:unrestricted는 비어 있어야 합니다. 매니페스트 레퍼런스를 참조하세요.
  • 번들된 backend.js는 Node.js 내장 모듈(fs, path, child_process 등)을 가져올 수 없습니다 — 샌드박스 런타임은 이를 제공하지 않습니다.

전체 검사 목록은 번들링 및 게시를 참조하세요.