오늘 Datasette 프로젝트 블로그에 새 플러그인 datasette-apps를 출시 공지 글과 함께 공개했습니다. 공식 출시 글에는 이 플러그인이 무엇인지를 담았다면, 이 글에서는 한 발 더 나아가 왜 만들었는지를 이야기해보려 합니다.
Datasette Apps는 Datasette 애플리케이션에 호스팅되는 <iframe> 샌드박스 안에서 실행되는 독립형 HTML+JavaScript 애플리케이션입니다. JavaScript를 사용해 Datasette의 데이터에 읽기 전용 SQL 쿼리를 실행할 수 있으며, 저장된 쿼리(stored queries)를 설정하면 쓰기 쿼리도 실행할 수 있습니다.
아주 간단한 예시와 좀 더 복잡한 커스텀 타임라인 예시를 준비했습니다. 후자는 다음과 같이 생겼습니다:

앱은 JavaScript를 실행하고 HTML과 CSS를 렌더링할 수 있습니다. 다만 접근 권한에는 엄격한 제한이 있습니다. 앱이 실행되는 <iframe sandbox="allow-scripts allow-forms">는 쿠키나 localStorage에 접근하지 못하도록 막고, 이 연구를 바탕으로 CSP 헤더도 주입합니다. 이 헤더 덕분에 외부 호스트로의 HTTP 요청이 차단되어, 악의적이거나 버그가 있는 앱이 개인 데이터를 외부로 유출하는 것을 방지합니다.
Datasette Apps는 원래 Datasette Agent에 Claude Artifacts와 같은 기능을 추가하려는 시도에서 출발했습니다. 그런데 작업하면서 이 샌드박스 패턴이 단순히 커스텀 앱을 인터페이스에 추가하는 것 이상의 가능성을 지니고 있다는 걸 깨달았고, 결국 Datasette 생태계 내의 독립적인 최상위 개념으로 격상시키게 되었습니다.
그뿐만 아니라, 수년간 이어온 vibe-coded HTML 툴 실험을 제 메인 프로젝트의 핵심 기능으로 끌어올릴 수 있는 재미있는 방법이기도 합니다!
Datasette Apps는 agent.datasette.io 데모 인스턴스에서 GitHub로 로그인해 직접 체험해볼 수 있습니다.
Datasette는 첫 릴리스부터 JSON API를 통해 커스텀 HTML 앱을 만들 수 있는 유연한 백엔드를 제공해왔습니다.
제가 Datasette로 처음 만든 프로젝트 중 하나는 Eventbrite 재직 당시 만든 사내 문서 검색 엔진이었습니다. 여러 시스템의 문서를 크론(cron)으로 SQLite에 가져온 뒤, Datasette 인스턴스와 커스텀 HTML+JavaScript 검색 인터페이스를 통해 서빙하는 방식이었고, 검색 인터페이스는 Datasette API를 직접 쿼리하는 구조였습니다.
클라이언트 측 JavaScript로 SQL 쿼리를 직접 구성하는 방식은 처음엔 일종의 엔지니어링 농담처럼 시작했지만, 실제로 앱을 빠르게 개선해나가는 데 놀랍도록 효과적인 방법임이 밝혀졌습니다!
이 프로젝트의 경험에 HTML 툴 컬렉션을 만들면서 쌓은 노하우와 Claude Artifacts 실험까지 더하고 나니, Datasette 스타일의 백엔드와 독립형 HTML 프런트엔드의 조합이 얼마나 강력한지 확신하게 되었습니다.
Claude Artifacts에 영속적인 관계형 데이터베이스 접근 권한이 생긴다면 얼마나 강력해질지 상상해보세요. 바로 그것을 Datasette Apps로 구현하는 겁니다!
개발 과정에서 발견한 아이디어와 패턴 중 앞으로도 충분히 가치 있을 것들을 소개합니다.
<iframe sandbox="allow-scripts" srcdoc="..."> + <meta http-equiv="Content-Security-Policy" content="default-src 'none'; script-src 'unsafe-inline'; style-src 'unsafe-inline'; img-src data: blob:;">
이 조합이야말로 Datasette Apps를 가능하게 하는 핵심 마법입니다. 인증된 Datasette 인스턴스에는 온갖 민감한 개인 데이터가 들어있을 수 있는데, 그런 중요한 도메인 위에서 신뢰할 수 없는 HTML과 JavaScript를 실행해야 했습니다. sandbox= 속성을 사용하면 해당 코드를 부모 애플리케이션과 완전히 격리된 환경에서 실행할 수 있어, DOM 읽기, 쿠키 접근, localStorage에서의 비밀 정보 탈취가 모두 차단됩니다. 그러나 fetch() 등을 이용해 다른 도메인에서 콘텐츠를 불러오거나 데이터를 유출하는 것은 여전히 가능합니다. 하지만 알고 보면, HTML 페이지 앞부분에 <meta http-equiv="Content-Security-Policy"> 헤더를 추가하는 것만으로 외부 도메인 접근을 추가로 잠글 수 있습니다. 악의적인 JavaScript가 이 헤더를 수정하거나 제거할 수 있지 않을까 걱정했지만, 그건 불가능한 것으로 밝혀졌습니다. 한 번 설정된 CSP 정책은 해당 프레임의 콘텐츠에 대해 변경할 수 없습니다.
postMessage()와 MessageChannel()으로 구현한 제한된 API
이렇게 iframe을 완전히 잠가버리고 나니, 이번엔 반대로 허용 목록에 등록된 작업, 즉 특정 데이터베이스에 대한 읽기 전용 SQL 쿼리부터 시작해서 다시 열어주는 것이 과제였습니다.
첫 번째 버전은 postMessage()을 사용해 구현했습니다. 자식 iframe이 부모 창에 메시지를 보낼 수 있는 방식으로, SQL 쿼리 실행을 요청하는 간단한 프로토콜을 만들었습니다. 부모는 쿼리를 실행하기 전에 해당 데이터베이스가 허용 목록에 있는지 검증합니다.
LLM 중 하나, 아마 GPT-5.5였던 것 같은데, postMessage()만 단독으로 사용하면 iframe이 신뢰할 수 없는 도메인에서 추가 코드를 불러올 경우 악용될 수 있다고 지적했습니다. Datasette Apps에는 해당되지 않는 경우라고 생각했지만, 심층 방어(defense in depth) 원칙을 믿는 만큼 GPT-5.5의 도움을 받아 MessageChannel() 기반 전송 방식으로 전환했습니다.
MessageChannel()은 페이지가 다른 곳으로 이동하면 채널이 자동으로 닫히는 장점이 있어, 신뢰할 수 없는 외부 페이지에서 전송된 명령이 실행될 가능성을 원천 차단합니다.
타임라인 데모에서 usercontent 문자열을 검색하면 user-images.githubusercontent.com 도메인의 이미지가 포함된 검색 결과가 나옵니다. 이 도메인은 CSP 허용 목록에 없기 때문에 오류가 발생합니다.
이 오류들은 캡처되어 부모 프레임으로 전달되고, 유용한 오류 로그 형태로 표시됩니다. 눈에 보이지 않던 문제를 수면 위로 드러냄으로써 앱 개발을 더 생산적으로 만들려는 의도입니다.
나아가 실험적 구현을 통해, 오류가 발생한 항목을 클릭 한 번으로 CSP 허용 목록에 추가할 수 있는 메커니즘도 만들어봤습니다. 다만 아직 datasette-apps에는 통합하지 않았습니다.
SQL 쿼리도 마찬가지로 시각적으로 로깅됩니다. 타임라인 페이지 하단으로 스크롤하면 실제로 확인할 수 있습니다.
앱이 조건부로 데이터베이스에 쓸 수 있게 하고 싶었지만, SQL 읽기보다 훨씬 더 위험한 문제였습니다!
해결책으로 Datasette의 저장된 쿼리 기능을 활용했습니다. 기존의 "canned queries"에서 이름을 바꾸고 최근 Datasette 1.0a31에서 대폭 개선된 이 기능은, 사실 Datasette Apps에서 영감을 받아 작업된 것입니다.
사용자는 insert나 update를 수행하는 저장된 쓰기 쿼리를 만들고, 해당 쿼리를 특정 앱에서만 사용할 수 있도록 허용 목록에 추가할 수 있습니다. 앱 내부 코드에서의 사용 방법은 다음과 같습니다:
const result = await datasette.storedQuery("todos", "add_todo", {
title: "Buy milk",
due_date: "2026-06-20",
priority: "high",
completed: false
});저 자신도 이 기능이 열어주는 가능성을 이제 막 탐색하기 시작한 단계이지만, 궁극적인 목표는 Datasette Apps로 안전하게 읽기-쓰기가 모두 가능한 완전한 애플리케이션을 지원하는 것입니다.
Datasette Apps 플러그인 자체는 LLM에 전혀 의존하지 않지만, 이 독립형 앱들은 현대적인 LLM이 작성하기에 최적화된 형태를 갖추고 있습니다.
앱 생성 폼 하단에는 복사 가능한 프롬프트가 포함되어 있습니다. 이 프롬프트에는 선택한 데이터베이스의 스키마를 포함해, 모델이 새 앱을 만드는 데 필요한 모든 정보가 담겨 있습니다.

"복사" 버튼을 클릭해 ChatGPT, Claude, Gemini 등에 붙여넣고 원하는 내용을 설명하면, 모델이 앱 구현에 필요한 코드를 뽑아낼 가능성이 높습니다.
Datasette Agent가 설치되어 있다면, AI 어시스턴트가 Claude Artifacts 방식으로 새 앱을 생성하거나 기존 앱을 편집하는 도구까지 갖추게 됩니다.

Datasette Apps는 4월에 datasette-agent-artifacts라는 이름으로 시작했다가, 이후 편집 도구만 남기고 datasette-agent-edit으로 이름을 바꾼 플러그인입니다. Datasette Agent의 초기 플러그인 중 하나로 만들어, 플러그인 훅의 구조를 잡는 데 활용했습니다. 첫 프로토타입은 주로 Claude Code에서 Claude Opus 4.6으로 구현했습니다.
Datasette Apps로 방향을 전환하면서는 Codex Desktop과 GPT-5.5 xhigh를 활용해 계획부터 세웠습니다. datasette-agent-artifacts와 그 외 프로토타입들을 충분히 입력하며 심도 있는 대화를 나눈 끝에 나온 결과물이었습니다.
이후 작업 대부분은 Codex와 함께했지만, Claude Fable 5에 접근할 수 있었던 짧은 기간 동안 보안 평가를 맡겼고(이 능력은 얼마 지나지 않아 미국 정부에 의해 금지되었습니다), 실제 문제를 하나 발견해냈습니다.
사용자가 자신의 앱에 CSP 허용 호스트를 직접 추가할 수 있게 해두었는데, Fable이 다음과 같은 공격 시나리오를 지적했습니다:
create-app 권한만 있는 낮은 권한의 사용자가, CSP를 통해 자신이 허용 목록에 추가한 호스트로 SQLite의 모든 테이블을 조회해 데이터를 전송하는 앱을 만듭니다.이는 명백히 허용할 수 없는 문제였습니다. 신뢰할 수 있는 스태프 전용으로 설계된 새 apps-set-csp 권한을 가진 사용자만 도메인을 허용 목록에 추가할 수 있도록 제한해 수정했습니다. 사이트 관리자는 Datasette 설정에서 allowed_csp_origins 목록을 직접 지정할 수 있으며, 일반 사용자는 그 목록 안에서 선택할 수 있습니다. 예를 들어 cdnjs.cloudflare.com을 허용해두면, 사용자들이 cdnjs CDN에서 추가 JavaScript 라이브러리를 불러오는 앱을 만들 수 있게 됩니다.
Datasette Apps, 특히 보안과 관련된 부분은 매우 면밀하게 검토했습니다. 핵심 샌드박스와 CSP 설정은 AI 보조로 진행한 여러 차례의 프로토타입 제작과 테스트를 거쳐 완성되었습니다.
이번 초기 릴리스가 정말 마음에 듭니다.
Datasette는 읽기 전용 데이터를 서빙하는 애플리케이션에서 출발해, 이제는 수집된 데이터를 다양하게 활용할 수 있는 훨씬 풍부한 도구 생태계로 성장하고 있습니다.
Datasette의 뿌리는 데이터 저널리즘에 있습니다. 저는 항상 기자가 방대한 데이터 덩어리를 손에 쥐었을 때 그 다음 단계가 무엇인지에 관심을 가져왔습니다. Datasette는 그 데이터를 탐색하고 공개하는 역할을 합니다. Datasette Agent는 AI 보조로 데이터를 심층적으로 파고들 수 있게 해주고, 이제 Datasette Apps는 데이터 안에 숨겨진 이야기를 끄집어내는 커스텀 인터페이스와 시각화를 구축하는 것까지 그 영역을 넓혀줍니다.
이 피드에는 블로그의 롱폼 아티클만 표시됩니다. 모든 포스트를 받아보려면 /atom/everything/을 구독하거나, 다른 구독 옵션을 확인해보세요.