본문으로 건너뛰기

신뢰 모델, 보안, 오류

직접 쓰지 않은 플러그인을 설치하기 전에, 그리고 다른 사람이 설치할 플러그인을 쓰기 전에 읽으십시오. 권한은 설치 시점에 승인받는 의도 선언과 API 관문이고, 단단한 샌드박스가 아닙니다. 진짜 격리를 제공하는 것은 Shadow DOM 경계(위 참조)뿐이고, 그것은 CSS만 격리하며 JavaScript는 격리하지 않습니다. 플러그인은 에디터와 같은 JS 컨텍스트에서 실행되므로, editorfiles 권한을 가진 플러그인은 원리적으로 그 권한의 API 표면이 허용하는 모든 것을 할 수 있고, 악의적이거나 버그가 있는 플러그인은 부여된 API 안에서 여전히 잘못 동작할 수 있습니다.

network는 설계상 유출 제한이 없습니다. context.network.fetch()는 루프백 주소, 사설/RFC1918 IP 범위, 클라우드 인스턴스 메타데이터 엔드포인트에 닿을 수 있습니다 — 어느 것도 막지 않습니다. 이것은 의도한 선택이고(누락이 아닙니다) 플러그인에서 로컬 Ollama 서버나 로컬 개발 서버와 이야기하는 것을 유용하게 만드는 것이 바로 그것입니다. 구체적으로:

  • http://https:// URL scheme만 허용되고, 그 밖의 것은 요청이 나가기 전에 거부됩니다.
  • 응답 본문은 항상 UTF-8 문자열로 디코드됩니다 — 바이너리·바이트 모드가 없습니다. 바이너리 리소스를 가져오면 조용히 손상됩니다.
  • 중복된 응답 헤더는 마지막 값이 이기는 단일 값으로 접힙니다(다중 값 헤더를 지원하지 않습니다).
  • 모든 요청에 30초 타임아웃10 MiB 응답 크기 상한이 있습니다(스트리밍하면서 점진적으로 강제하므로, 끝없는 응답은 전체를 버퍼링한 뒤가 아니라 상한을 넘는 순간 거부됩니다).

storage는 볼트별이 아니라 앱 전역이고, 디스크의 ~/.baram/plugin-data/<pluginId>/<key>에 평문으로 저장됩니다 — 플러그인의 저장소는 “현재 볼트”로 한정되지 않고 여는 모든 볼트에서 공유됩니다. 키는 안전한 경로 조각 하나로 제한되므로(/ 없음, .. 없음) 키가 그 플러그인 저장소 디렉터리를 벗어날 수는 없지만, 이것은 심볼릭 링크에 대해 단단하지 않습니다 — 정규화해서 실제 경로를 검증하는 것이 아니라 순전히 파일명 모양 검사입니다.

ai는 사용자가 직접 설정한 프로바이더·모델·API 키/쿼터를 씁니다 — 플러그인이 자기 키를 가져올 수 없고, 모든 complete·stream 호출이 사용자가 설정 → AI에 정해 둔 것으로 과금됩니다. 개인정보 보호 모드는 completestream만 막습니다 — 보호 모드(또는 파일별 프론트매터 privacy: true)가 켜져 있으면, 설정된 프로바이더가 로컬인 경우(지금은 Ollama뿐)를 빼고 그 두 호출이 거절됩니다. listModels()는 개인정보 보호 모드가 막지 않습니다. 보호 모드가 complete·stream을 막고 있는 동안에도 모델을 열거하려 클라우드 프로바이더 API를 호출할 수 있으므로, 보호 모드가 켜져 있다는 이유로 listModels()가 프라이버시에 안전하다고 가정하지 마십시오.

체크섬은 레지스트리에만 적용됩니다. 레지스트리에서 설치한 플러그인은 설치 전에 SHA-256 체크섬으로 검증되지만, 개발 폴더 불러오기(개발자 구역)는 이것을 완전히 건너뜁니다 — 로컬 폴더를 불러오는 것은 그 코드를 신뢰한다는 명시적이고 의도적인 행위이고, 그 사이에 암호학적 검사가 없습니다. 개발 폴더 불러오기는 또한 개발 빌드 전용입니다 — 체크섬과 레지스트리 목록과 동의 기록을 한꺼번에 우회하므로, 패키징된 빌드는 그것을 거부합니다.

설치는 무엇을 승인했는지 기록합니다. 설치 대화상자가 요구된 권한을 나열하고, trust: "trusted"인 경우에는 권한 목록이 그 플러그인을 한정하지 않는다고 분명히 말합니다 — trusted 플러그인은 Baram 안에서 돌면서 무엇을 선언했든 모든 것을 갖기 때문에, 그것에는 명시적인 확인이 필요합니다. 승인된 (trust, capabilities)는 플러그인과 함께 저장되고, 그것을 넘어서는 업데이트는 다시 묻습니다sandboxed로 설치된 플러그인이 조용히 trusted가 될 수 없고, 새 권한은 새로운 것으로 표시됩니다. 요구하는 업데이트는 묻지 않고 설치되며 기록을 좁힙니다.

샌드박스는 앱과, 그리고 서로와 origin을 공유합니다. Tauri v2에는 창별 origin이 없으므로 plugin-* 웹뷰에 자기 origin을 줄 수 없습니다. 결과가 셋이고, 모두 버그가 아니라 한계입니다.

  • 앱은 localStorage에 아무것도 두지 않습니다 — 모든 것을 Rust의 설정 파일로 영속화합니다 — 그러지 않으면 플러그인이 권한 없이도 그것을 읽을 수 있기 때문입니다.
  • 설치된 두 플러그인은 BroadcastChannel로 서로에게 닿을 수 있으므로, 둘 다 악의적이라면 network가 없는 플러그인이 network를 받은 플러그인을 프록시로 쓸 수 있습니다. 권한 부여는 플러그인 하나를 한정하고, 협력하는 한 쌍을 한정하지는 않습니다. SharedWorker도 같은 종류의 통로였고 막혀 있습니다 — 샌드박스가 worker-src를 아예 거부하므로, 샌드박스 플러그인에서는 Worker 자체를 쓸 수 없습니다.
  • indexedDB와 Cache API는 storage 권한 없이 닿을 수 있습니다. 그것들은 권한 체계가 통제하지 않는 영속성을 플러그인에 주지만, 앱의 것을 주지는 않습니다 — 앱이 둘 중 어디에도 아무것도 저장하지 않기 때문입니다. ctx.storage를 쓰십시오. 사용자가 볼 수 있고 제거할 때 실제로 지워지는 플러그인 저장소는 그것뿐입니다.

결론: 신뢰하는 플러그인만 설치하십시오. 특히 ai·network·files·storage를 선언한 것이라면 그렇습니다. 플러그인 업데이트에서 새로 추가된 권한은 업데이트가 적용되기 전에 다시 승인받아야 합니다.

  • activate(): 5초 타임아웃.
  • deactivate()와 그 밖의 생명주기 훅: 1초 타임아웃.
  • 플러그인이 시간을 넘기면 오류 상태로 표시되고 수동으로 다시 켤 수 있습니다.
  • 플러그인 오류가 메인 앱을 죽이는 일은 없습니다.
  • 플러그인 UI에서 렌더된 React 컴포넌트는 에러 경계로 감싸집니다.
  • 실패한 플러그인은 마켓플레이스·개발 UI에서 오류 상태로 표시됩니다.
  • 자세한 플러그인 오류 로그는 브라우저 콘솔을 확인하십시오.