보안 모델
Wippy의 보안 모델은 여러분의 코드가 접근할 수 있는 것, 접근할 수 없는 것, 그리고 그 경계를 누가 강제하는지를 정의합니다. 대부분의 프레임워크가 하나로 합쳐 버리는 두 계층에서 동작하기 때문에, 구축을 시작하기 전에 읽어 둘 가치가 있습니다. 런타임은 각 프로세스를 격리해 위험한 기능이 아예 존재하지 않도록 만들고, 속성 기반 정책 계층은 프로세스가 어떤 레지스트리 기능을 사용할 수 있는지를 관장합니다. 두 계층을 모두 이해하면 애플리케이션을 구성하는 방식이 달라집니다.
신뢰 모델
Wippy의 격리 계층은 프로세스에 주변 권한(ambient authority)을 전혀 주지 않습니다. 새로 시작된 Lua 또는 WASM 프로세스는 파일 시스템, 네트워크, 호스트 OS, 다른 프로세스의 메모리에 접근할 수 없습니다. 그런 기능이 프로세스 환경에 존재하지 않기 때문입니다. 기능은 오직 레지스트리를 통해서만 도달합니다. 프로세스에 명시적으로 부여된 함수, 도구, 커넥션, 구성이 그것입니다.
그 위에서 레지스트리 기능에 대한 접근은 속성 기반 접근 제어(ABAC)로 관장됩니다. 보호되는 모든 작업은 현재 액터의 보안 스코프, 즉 리소스에 대한 작업을 허용하거나 거부하는 정책 집합에 대해 검사되며, 선택적으로 액터와 리소스 메타데이터를 조건으로 걸 수 있습니다. 이는 선언적입니다. 정책을 애플리케이션 코드가 아니라 구성에서 정의합니다.
프로세스가 액터와 스코프를 모두 갖고 실행될 때 접근은 기본 거부입니다. 어떤 정책이 명시적으로 허용하고 어떤 정책도 거부하지 않을 때만 요청이 허용됩니다. **엄격 모드(strict mode)**는 액터나 스코프가 확립되지 않은 불완전한 경우를 관장합니다. 이 모드는 기본적으로 켜져 있어서 불완전한 컨텍스트는 거부됩니다. 런타임 구성에서 security.strict_mode: false를 설정하면 허용적인 동작을 선택하게 됩니다. 대비해야 할 결과는, 선언된 보안 컨텍스트가 없는 프로세스는 기본 설정에서 모든 검사에 실패한다는 점입니다. 그런 프로세스에는 항목에 security: 블록을 주거나, 컨텍스트를 제공하는 경로로 시작하십시오. 최소 권한 정책과 결합하면 부재에 의한 거부 격리 위에 실패 시 차단(fail-closed) 인가를 얻게 됩니다. 정책 문법, 평가 규칙, security: 블록의 형태는 보안 레퍼런스를 참조하십시오.
프로세스 격리
Wippy의 모든 실행 단위는 자체 임베디드 인터프리터(Lua 또는 WASM)를 갖는 격리된 프로세스에서 실행됩니다.
프로세스가 갖는 것: 자체 메모리 공간(Lua의 경우 기본 오버헤드 약 13 KB). 레지스트리에 대한 범위가 지정된 뷰. 액터 신원과 보안 스코프. 크래시 복구와 재시작 제한을 갖춘 감독 대상 라이프사이클.
프로세스가 갖지 못하는 것: 파일 시스템 접근(레지스트리가 통제하는 파일 시스템 항목을 통한 경우 제외). 네트워크 접근(허용된 HTTP 클라이언트나 도구 모듈을 통한 경우 제외). 다른 프로세스의 메모리 접근. 자신을 호스팅하는 Go 런타임 접근. 환경 변수 접근(허용된 환경 항목을 통한 경우 제외).
격리가 강제되는 방식: 각 Lua 프로세스는 최소한의 표준 라이브러리에서 시작합니다. 파일 I/O, OS 프로세스 접근, 동적 코드 로딩, 네트워킹은 결코 로드되지 않으므로 환경에 존재하지 않으며, 프로세스는 존재하지 않는 것을 복원할 수 없습니다. 모듈 로딩은 제한됩니다. require는 프로세스에 명시적으로 허용된 모듈과 레지스트리 항목만 해석하며, 파일 시스템 검색 경로는 없습니다. WASM 프로세스는 WASI를 통해 동등한 격리를 달성합니다. 해당 항목에 대해 구성된 호스트 함수와 마운트된 파일 시스템 항목만 도달 가능합니다.
이는 런타임 권한(seccomp나 AppArmor 같은)을 통한 샌드박싱이 아닙니다. 부재를 통한 샌드박싱입니다. 위험한 기능은 아예 로드되지 않으므로, 악용하거나 우회하거나 권한을 상승시킬 수 없습니다.
기능 제어
레지스트리는 Wippy의 기능 저장소이며, 보안 정책은 그 인가 계층입니다.
모든 기능은 레지스트리 항목입니다. 함수, 도구, 에이전트 정의, 데이터베이스 커넥션, 환경 참조, 구성 값, 예약 작업은 모두 선언된 kind, 스키마, 메타데이터를 갖는 레지스트리 항목입니다. 항목은 등록될 때 해당 kind 핸들러가 검증합니다.
항목 ID는 네임스페이스로 구분됩니다. ID는 콜론 하나를 사용하는 namespace:name 형태이며, 네임스페이스는 점으로 구분된 세그먼트를 통해 계층적입니다. 예를 들어 tenant_acme.tools:read(네임스페이스 tenant_acme.tools, 이름 read)입니다. 정책은 작업과 리소스를 매칭하며, 리소스 패턴은 네임스페이스 접두사를 대상으로 삼을 수 있어 단일 규칙이 네임스페이스 전체를 포괄할 수 있습니다.
정책이 접근을 결정합니다. 각 기능 접근(레지스트리 조회, 함수 호출, 데이터베이스 핸들, 파일 열기)은 액터의 스코프에 대해 검사됩니다. 정책은 자신이 다루는 작업과 리소스, 허용 또는 거부 효과, 그리고 액터와 리소스 메타데이터에 대한 선택적 조건을 선언합니다. 평가는 시작 시 한 번이 아니라 접근마다 일어납니다. 어떤 정책이든 거부하면 접근이 거부되고, 최소 하나가 허용하며 어느 것도 거부하지 않으면 허용되며, 일치하는 정책이 없으면 접근이 거부됩니다. (컨텍스트에 액터나 스코프가 전혀 없는 경우, 그 불완전한 경우는 정책 평가가 아니라 엄격 모드가 해결합니다.)
컨텍스트는 선언되는 것이지 허공에서 상속되지 않습니다. 함수는 호출자의 액터와 스코프를 상속합니다. 스폰된 프로세스도 이를 상속합니다. 프로세스의 프레임은 스포너의 프레임에서 포크되며, 자신의 항목에 있는 security: 블록이 그 상속된 컨텍스트를 수정합니다 — 블록이 지정한 actor는 상속된 액터를 대체하고, 레지스트리 ID로 나열한 정책과 정책 그룹은 상속된 스코프에 병합됩니다. 해석은 원자적입니다. 지정된 정책이나 그룹 중 하나라도 없으면 부분적인 스코프로 진행하지 않고 스폰이 실패합니다. CLI 명령은 추가로 meta.command.security를 선언할 수 있으며, 이는 운영자가 직접 명령을 시작한 신뢰된 실행 경로에서만 적용됩니다.
도구 인자는 스키마로 형태가 정해집니다. 도구는 입력에 대한 JSON Schema를 선언합니다. 그 스키마가 모델에 주어져 모델이 규격에 맞는 인자를 생성하며, 도구에 대한 접근은 호출이 실행되기 전에 정책 검사를 거칩니다.
데이터 경계
데이터베이스 커넥션은 레지스트리 항목입니다. 프로세스는 자체 연결 문자열을 조립하지 않습니다. 레지스트리 ID로 커넥션을 요청하며, 그 요청은 핸들이 반환되기 전에 정책 검사를 거칩니다. 테넌트 B의 데이터베이스 항목이 정책으로 허용되지 않은 프로세스는 그에 대한 핸들을 얻을 수 없습니다.
LLM API 키는 환경 시스템에 있습니다. Claude, GPT 등 제공자의 키는 환경 시스템에서 읽습니다(예: env.storage.os 항목을 통해 노출된 OS 환경 변수를 env.variable 항목이 참조하며, 이 읽기는 env.get 작업으로 정책 검사를 거칩니다). 제공자가 내부적으로 읽으며, 프로세스 인자로 전달되거나 호출 코드로 반환되지 않습니다.
파일과 blob 스토리지도 같은 모델을 따릅니다. 프로세스는 파일 시스템 또는 클라우드 스토리지 레지스트리 항목을 통해 읽고 쓰며, 각 접근은 정책 검사를 거칩니다. WASM 프로세스는 해당 항목에 명시적으로 마운트된 파일 시스템 항목을 통해서만 파일에 접근합니다.
에이전트 보안
에이전트는 도구를 사용하는 LLM 기반 프로세스입니다. 여러분의 코드가 직접 통제하지 않는 결정을 런타임에 내리므로, 그 경계가 중요합니다. Wippy는 다른 모든 프로세스와 동일한 레지스트리 및 정책 메커니즘으로 이를 처리합니다.
도구 접근. 에이전트는 자신의 정의에 나열된 도구만 호출할 수 있으며, 각 도구 실행은 정책 검사를 거치는 funcs.call을 통과합니다. 거부된 호출은 도구 함수가 실행되기 전에 실패합니다. 고객 데이터를 읽되 삭제하지 않도록 설계된 에이전트는 정의에 삭제 도구가 없거나, 정책에 의해 그 작업이 거부됩니다.
외부 도구와 MCP 도구. Wippy는 외부 도구를 소비할 수 있고 Model Context Protocol을 통해 자신의 도구를 노출할 수 있습니다. 소비된 도구는 네이티브 도구와 동일한 함수 호출 경로와 정책 검사를 거칩니다. Wippy가 외부 MCP 클라이언트에 노출하는 도구는 클라이언트가 수행할 수 있는 작업을 제한하는, 범위가 지정되고 취소 가능한 액세스 토큰으로 통제됩니다.
구조화된 출력. LLM 모듈은 제공자의 네이티브 구조화 출력 지원을 사용해 스키마로 제약된(구조화된) 출력을 요청할 수 있으므로, 에이전트의 출력을 선언된 형태로 유지할 수 있습니다.
관측 가능성. OpenTelemetry가 활성화되면 LLM 제공자 호출과 도구 호출이 추적되고, 토큰 사용량이 usage-tracker 계약을 통해 기록됩니다. 이는 에이전트가 무엇을 호출했고 무엇을 소비했는지에 대한 감사 추적을 제공합니다. 관측 가능성을 참조하십시오.
자기 수정 경계. 한 네임스페이스에서 도구를 생성할 수 있는 에이전트라도 다른 네임스페이스에 있는 자기 정의에 대한 쓰기 접근은 거부될 수 있습니다. 레지스트리 쓰기는 정책 검사를 거치는 작업이므로, 에이전트 자신의 네임스페이스에 대한 거부 정책은 에이전트가 자신을 편집하거나 스스로에게 새로운 접근을 부여하는 것을 막습니다.
멀티테넌트 강제
여러 고객이 단일 Wippy 인스턴스를 공유하는 배포에서 격리는 애플리케이션 코드가 테넌트 ID를 확인하는 방식이 아니라, 어떤 작업이 실행되기 전에 정책 평가로 강제됩니다.
테넌트 격리는 정책으로 강제됩니다. 각 테넌트에 액터와, 그 테넌트의 네임스페이스만 포괄하는 정책을 가진 스코프를 부여하십시오. 엄격 모드가 켜져 있으면 테넌트의 프로세스는 코드가 실행되기 전에 자신의 스코프 밖 리소스에 대한 접근이 거부됩니다. 실질적인 격리는 테넌트별 정책을 작성하는 데 달려 있습니다. 런타임은 이를 강제하지만, 테넌시를 대신 추론해 주지는 않습니다.
테넌트 간 접근은 명시적입니다. 테넌트 간에 공유되는 기능은 각 테넌트의 정책이 허용하는 공유 네임스페이스에 있습니다. 공유는 네임스페이스별 옵트인입니다.
동시성은 호스트에서 제한됩니다. 프로세스 호스트는 워커 풀로 동시성을 제한합니다. 프로세스 그룹(pg.scope)은 격리된 클러스터 전역 멤버십 및 브로드캐스트 네임스페이스를 제공하며 그룹과 멤버 수를 제한할 수 있습니다. 테넌트별 CPU 또는 메모리 상한은 런타임 내장 기능이 아닙니다. 이는 인프라 계층에서 강제하십시오.
멀티테넌트 아키텍처 전용 가이드가 계획되어 있습니다.
범위와 한계
Wippy의 보안 모델은 프로세스 격리, 기능 제어, 데이터 경계를 다룹니다. 다음은 런타임의 범위 밖이며 여러분 인프라의 책임으로 남습니다.
저장 데이터 암호화. 데이터베이스, 디스크, blob 스토리지 암호화는 기반 인프라(PostgreSQL TDE, 디스크 암호화 등)가 처리합니다. Wippy는 스토리지 계층이 암호화를 처리한다고 가정합니다.
네트워크 수준 격리. 프로세스 격리는 애플리케이션 계층에서 일어납니다. Wippy와 그 의존성(데이터베이스, LLM API, 외부 서비스) 사이의 네트워크 분할은 인프라가 처리합니다. VPC, 보안 그룹, 방화벽이 그것입니다.
신원 관리. 인증(사용자가 누구인지 검증하는 것)은 여러분의 인증 계층이 처리합니다. Wippy의 보안 모델은 인증 이후부터 시작합니다. 사용자가 누구인지가 아니라, 인증된 사용자의 프로세스가 무엇을 할 수 있는지를 통제합니다. 액터와 스코프를 담은 토큰은 토큰 스토어를 통해 발급하고 검증할 수 있습니다.
인프라 감사 로그. Wippy의 추적은 함수 호출, 도구 호출, 프로세스 활동 같은 프로세스 수준 작업을 다룹니다. 인프라 수준 접근(서버 SSH, 데이터베이스 관리 작업)은 인프라 도구로 감사해야 합니다.
자주 묻는 질문
한 테넌트의 에이전트가 다른 테넌트의 데이터에 접근할 수 있습니까? 각 테넌트의 리소스가 정책으로 범위가 지정되어 있다면 그렇지 않습니다. 테넌트별 정책과 엄격 모드가 있으면, 런타임은 에이전트의 코드가 실행되기 전에 테넌트 스코프 밖 리소스에 대한 접근을 거부합니다.
에이전트가 자신의 권한을 상승시킬 수 있습니까? 정책이 자기 정의에 대한 쓰기를 허용하는 경우에만 가능합니다. 레지스트리 쓰기는 정책 검사를 거치므로, 에이전트 자신의 네임스페이스에 대한 거부 정책이 자기 수정을 막습니다. 한 네임스페이스에서 도구를 생성할 수 있는 에이전트라도 자신의 스코프가 이미 포괄하지 않는 네임스페이스에 대한 접근을 스스로에게 부여할 수 없습니다.
에이전트가 무엇을 했는지 어떻게 확인합니까? OpenTelemetry가 활성화되면 LLM과 도구 호출이 추적되고, 토큰 사용량이 usage-tracker 계약을 통해 기록됩니다. 관측 가능성을 참조하십시오.
에이전트가 예상치 못한 동작을 하면 어떻게 됩니까? 샌드박스가 이를 가둡니다. 파일 시스템도, 네트워크도, OS도, 부여받은 것 이상의 다른 프로세스 접근도 없습니다. 자신의 정의에 있고 정책이 허용하는 도구만 호출할 수 있으며, 그 호출은 로그로 남습니다.
테넌트 격리는 제 코드가 강제합니까, 런타임이 강제합니까? 런타임입니다. 정책 엔진이 작업이 실행되기 전에 각 접근을 평가합니다. 여러분의 일은 테넌트별 정책을 작성하는 것이고, 런타임이 이를 강제합니다.
외부 MCP 도구는 어떻게 보호됩니까? MCP를 통해 소비되는 도구는 네이티브 도구와 동일한 함수 호출 경로와 정책 검사를 거칩니다. Wippy가 외부 MCP 클라이언트에 노출하는 도구는 범위가 지정되고 취소 가능한 액세스 토큰으로 통제됩니다. MCP 서비스를 연결한다고 해서 보안 모델을 우회하지는 않습니다.
보안 레퍼런스
| 관심사 | Wippy의 접근 방식 |
|---|---|
| 프로세스 격리 | 프로세스마다 별도 인터프리터(Lua 또는 WASM), 공유 메모리 없음 |
| 기본 접근 | 액터와 스코프가 모두 설정된 경우 일치하지 않는 정책은 거부, 기본으로 켜진 엄격 모드는 액터나 스코프가 확립되지 않으면 거부 |
| 컨텍스트 선언 | 항목의 security: 블록(액터, 정책, 그룹), 해석은 원자적이며 실패 시 차단 |
| 공급망 | 설치 시와 부팅 시 다이제스트로 검증되는 모듈 팩, 불일치 시 모듈 거부 |
| 노드 간 신뢰 | 상호 인증되는 노드 간 메시, 노드마다 ed25519 신원, 명시적인 신뢰 피어 맵 |
| 워크플로 전파 | 액터와 스코프가 서명되고 대상이 지정된 헤더로 Temporal에 전달, 검증 실패 시 실행 실패 |
| 기능 제어 | 속성 기반 보안 정책(액터, 스코프, 작업, 리소스)이 관장하는 레지스트리 항목 |
| 데이터 경계 | 커넥션과 스토리지는 레지스트리 항목이며, 각 접근은 항목 ID로 정책 검사 |
| API 키 관리 | 환경 시스템에 저장되고 제공자가 내부적으로 읽으며 프로세스 코드에 노출되지 않음 |
| 에이전트 도구 통제 | 도구는 에이전트 정의로 제한되며, 각 호출은 funcs.call 정책으로 검사 |
| 외부 도구(MCP) | 동일한 함수 호출 경로와 정책 검사, 노출된 도구는 범위가 지정된 토큰으로 통제 |
| 에이전트 감사 추적 | OpenTelemetry 추적(활성화된 경우)과 usage-tracker 기록 |
| 멀티테넌트 격리 | 각 작업 전에 런타임이 평가하는 테넌트별 정책과 스코프 |
| 동시성 제한 | 호스트 워커 풀로 제한, 테넌트별 CPU/메모리 상한은 내장되지 않음 |
| 자기 수정 | 레지스트리 쓰기 작업에 대한 거부 정책이 에이전트의 자기 정의 편집을 방지 |