Capabilities와 보안

이 페이지

샌드박스된 플러그인은 기본적으로 격리됩니다. 자체 KV와 storage를 읽고 쓰는 것 이상의 작업을 수행하려면, 플러그인은 manifest에서 capability를 선언해야 합니다. 샌드박스 브리지는 이러한 선언을 기반으로 호스트가 제공하는 모든 API를 제어합니다. content:read를 선언하지 않은 플러그인은 ctx.content를 얻지 못하고, network:request를 선언하지 않은 플러그인은 ctx.http를 얻지 못합니다.

이 페이지는 각 capability가 무엇을 부여하는지, 샌드박스가 어떻게 이를 강제하는지, 그리고 무엇이 강제될 수 없는지 다룹니다.

Capabilities 선언

Capabilities는 emdash-plugin.jsonc에 있으며, slug 및 나머지 신뢰 계약과 함께 있습니다:

{
	"slug": "plugin-hello",
	// ...identity + profile...

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

플러그인이 실제로 필요로 하는 것만 선언하세요. Capability 선언은 또한 마켓플레이스가 동의 대화 상자에서 사이트 운영자에게 보여주는 것이기도 합니다. 추가 capabilities는 설치 시 마찰이 되며 감사에서 보안 플래그가 됩니다.

Capability 참조

Capability접근 권한 부여
content:readctx.content.get(), ctx.content.list()
content:writectx.content.create(), ctx.content.update(), ctx.content.delete() (content:read 포함)
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를 포함합니다. 둘 다 나열할 필요가 없습니다.
  • network:request:unrestricted는 사용자 구성 URL을 위해 존재합니다. 운영자가 대상 URL을 입력하는 웹훅 플러그인은 manifest에 없는 호스트에 도달해야 합니다. 항상 알려진 API를 호출하는 플러그인은 network:request + allowedHosts를 사용해야 합니다.
  • email:send는 capability뿐만 아니라 구성에 의해 제어됩니다. 플러그인은 email:send를 선언할 수 있지만, 다른 플러그인이 email:deliver 전송을 등록한 경우에만 ctx.email이 채워집니다.

네트워크 호스트 허용 목록

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

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

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

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

샌드박스가 강제하는 것

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

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

  2. Storage 및 KV 범위 지정. 모든 storage 및 KV 작업은 플러그인의 slug로 범위가 지정됩니다. 플러그인은 다른 플러그인의 KV 또는 storage 컬렉션을 읽을 수 없으며, manifest에 선언한 storage 컬렉션에만 액세스할 수 있습니다.

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

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

  5. 리소스 제한. 러너는 호출당 CPU, 하위 요청, wall-clock 및 메모리 제한을 강제할 수 있습니다. 정확한 제한은 사용 중인 러너에 따라 다릅니다. Cloudflare 러너는 플랫폼의 Worker Loader 제한(호출당 50ms CPU, 10개 하위 요청, 30초 wall-clock, ~128MB 메모리)을 사용합니다. 러너의 제한을 초과하는 훅은 중단됩니다. EmDash 훅 타임아웃(훅 구성의 timeout)은 그 위에 더 엄격한 상한선을 적용합니다.

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

capability 시스템이 다루지 않고 다룰 수 없는 몇 가지 사항:

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

Capability 동의

운영자가 마켓플레이스에서 샌드박스된 플러그인을 설치하면 EmDash는 선언된 capabilities를 나열하는 동의 대화 상자를 표시합니다. capabilities를 추가하는 업데이트(예: 이전에는 콘텐츠만 읽었던 플러그인이 이제 네트워크 요청을 하려는 경우)는 capability 차이로 표시되며 새 버전이 적용되기 전에 새로운 승인이 필요합니다.

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

번들 시간 검증

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

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

전체 검사 목록은 Bundling and publishing를 참조하세요.