본문 바로가기
WIKI 기술 지식 베이스

시크릿 볼트

원문 보기 위키 갱신

시크릿 볼트 (Secrets Vault)

AI 코딩 에이전트는 진짜 자격 증명을 자주 필요로 해요 — 내 계정으로 GitHub을 호출할 API 토큰, 과금 프로바이더를 칠 키, 배포용 토큰 같은 것들이요. 암호화된 Turso 데이터베이스 기반 시크릿 볼트를 쓰면 에이전트가 값을 한 번도 보지 않고 시크릿을 사용하게 할 수 있어요.

출처: 문서

본문

AI 코딩 에이전트는 실제 자격 증명을 수시로 필요로 해요 — 내 계정으로 GitHub을 호출할 API 토큰, 과금 프로바이더를 호출할 키, 배포용 토큰 같은 것들이요. 가장 쉬운 길은 시크릿을 채팅에 붙여 넣는 거지만, 그러면 그 값이 모델의 컨텍스트와 대화 기록, 프로바이더 로그에 영원히 남게 돼요. 암호화된 Turso 데이터베이스 기반의 시크릿 볼트를 쓰면 에이전트가 값을 한 번도 보지 않고 시크릿을 사용하게 할 수 있어요: 시크릿은 자식 프로세스에 주입되고, 명령이 실행되고, 에이전트가 보기 전에 출력에서 값이 지워져요.

이 가이드는 에이전트가 사용할 수는 있지만 절대 읽을 수 없는 로컬 시크릿 볼트의 스키마, 암호화 연결, 쿼리를 다뤄요.

스키마 (Schema)

볼트는 두 개의 테이블을 사용해요: 시크릿 자체용 하나, 추가 전용(append-only) 감사 로그 하나예요.

CREATE TABLE IF NOT EXISTS secrets (
  name         TEXT PRIMARY KEY,
  value        TEXT NOT NULL,
  provider     TEXT,
  account      TEXT,
  environment  TEXT,
  access       TEXT,
  tags         TEXT NOT NULL DEFAULT '',
  description  TEXT,
  created_at   TEXT NOT NULL,
  last_used_at TEXT
);

CREATE TABLE IF NOT EXISTS audit_log (
  id         INTEGER PRIMARY KEY AUTOINCREMENT,
  ts         TEXT NOT NULL,
  secrets    TEXT NOT NULL,
  command    TEXT NOT NULL,
  cwd        TEXT NOT NULL,
  exit_code  INTEGER,
  outcome    TEXT NOT NULL
);

구성 요소가 어떻게 맞물리는지

  • **secrets**는 각 자격 증명을 name으로 저장해요. value 컬럼에 시크릿 그 자체가 들어 있는데, 이 값은 자식 프로세스에 주입할 때만 읽히고 에이전트에게는 절대 반환되지 않아요. 메타데이터 컬럼(provider, account, environment, access, tags)은 시크릿을 드러내지 않으면서 설명해 주기 때문에, 에이전트가 "프로덕션 데이터베이스 토큰"을 속성으로 판단할 수 있어요.
  • **audit_log**는 모든 사용을 기록하는 추가 전용 레코드예요: 어떤 시크릿이 주입됐는지, 명령은 무엇이었는지, 작업 디렉터리, 종료 코드, 결과(ran 또는 denied).

연결 (Connecting)

볼트는 암호화된 데이터베이스 파일 하나예요. 연결 시점에 키를 넘기면 Turso가 저장 상태의 모든 페이지를 암호화하고, multiprocess_wal 덕분에 여러 프로세스(에이전트 세션 몇 개와 CLI)가 같은 파일을 안전하게 열 수 있어요:

import { connect } from "@tursodatabase/database";

const db = await connect("vault.db", {
  encryption: { cipher: "aes256gcm", hexkey },
  experimental: ["multiprocess_wal"],
});
await db.exec(SCHEMA); // The CREATE TABLE statements above

hexkey는 64자리 16진수 문자열이에요. 어디서 가져올지는 여러분의 선택이에요. 패스프레이즈에서 scrypt로 유도할 수도 있고(절대 저장하지 않도록), OS 키체인에서 풀어 쓸 수도 있고, 클라우드 KMS에서 가져올 수도 있어요. Turso는 connect() 시점에만 키를 필요로 하고, 저장소 레이어는 출처와 무관하게 동일해요.

시크릿 저장하기

name으로 업서트하면 값을 교체(rotate)할 때 기존 메타데이터가 유지돼요:

INSERT INTO secrets (name, value, provider, account, environment, access, tags, description, created_at)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
ON CONFLICT(name) DO UPDATE SET
  value       = excluded.value,
  provider    = COALESCE(excluded.provider, secrets.provider),
  account     = COALESCE(excluded.account, secrets.account),
  environment = COALESCE(excluded.environment, secrets.environment),
  access      = COALESCE(excluded.access, secrets.access),
  description = COALESCE(excluded.description, secrets.description);

값을 노출하지 않고 목록 보기

에이전트가 실행해도 되는 쿼리는 단순히 value 컬럼을 선택하지 않아요:

SELECT name, provider, account, environment, access, tags, description, created_at, last_used_at
FROM secrets ORDER BY name;

이렇게 하면 시크릿 이름과 속성 — 에이전트가 올바른 자격 증명을 고르기에 충분한 정보 — 만 반환되고, 값은 데이터베이스 밖으로 절대 나가지 않아요.

시크릿 사용하기

실제로 시크릿을 사용하려면 값을 읽고, 환경 변수로 자식 프로세스에 주입하고, 명령을 실행하고, 반환하기 전에 출력에서 값을 지워야 해요:

SELECT value FROM secrets WHERE name = ?;

주입과 지우기(scrubbing)는 데이터베이스가 아니라 애플리케이션 코드에서 일어나요:

const { value } = await db.prepare(
  "SELECT value FROM secrets WHERE name = ?"
).get([name]);

const result = await runCommand(command, {
  env: { ...process.env, [name]: value },
});

// Replace every occurrence of the value before the agent sees the output
const safe = result.stdout.replaceAll(value, "***");

출력에서 값을 지우는 것만으로 샌드박스가 되는 건 아니에요. 시크릿을 읽을 수 있는 명령은 항상 의도적으로 누설할 수 있어요(파일에 쓰기, 네트워크로 전송 등). 볼트가 보장하는 것은 더 좁지만 여전히 유용해요: 모델은 절대 값을 받지 않고, 부주의한 echo $TOKEN은 마스킹돼요. 시크릿은 암호화된 파일과 그 값을 사용한 수명이 짧은 프로세스에만 존재해요.

모든 사용 감사하기

접근 기록을 행으로 남기고, 시크릿의 last_used_at을 갱신해요:

INSERT INTO audit_log (ts, secrets, command, cwd, exit_code, outcome)
VALUES (?, ?, ?, ?, ?, ?);

UPDATE secrets SET last_used_at = ? WHERE name = ?;

그러면 "프로덕션 키를 누가, 언제, 무엇을 위해 썼는지"는 그냥 평범한 쿼리가 돼요:

SELECT ts, secrets, command, cwd, exit_code, outcome
FROM audit_log ORDER BY id DESC LIMIT ?;

핵심 설계 포인트

  • 저장 시점 암호화가 기본으로 제공돼요. Turso가 데이터베이스 전체 암호화(AES-256-GCM, 또는 더 빠른 AEGIS 계열 암호)를 알아서 처리해요. 볼트는 암호 알고리즘을 다루거나 페이지 암호화를 관리할 필요가 없어요 — 키를 유도해서 connect()에 넘기기만 하면 돼요.
  • 조율용 데몬이 필요 없어요. multiprocess_wal 덕분에 각 에이전트 세션과 CLI 실행이 같은 암호화 파일을 직접 열 수 있어요. 데이터베이스가 곧 공유 상태가 되고, 계속 살아 있어야 하는 장기 실행 서버가 필요 없어요.
  • 값은 목록에 절대 나타나지 않아요. 목록 조회는 value 컬럼을 제외한 SELECT예요. 에이전트에게는 이름과 속성만 보여요.
  • 안전하게 실패(fail closed)해요. 알 수 없는 시크릿 이름이면 명령이 실행되기 전에 중단돼요. 자격 증명이 없는 채로 실행되지 않아요.
  • 감사는 그냥 테이블이에요. 모든 사용은 INSERT 한 번이고, 보고는 내보내거나 알림을 걸 수 있는 SELECT면 끝이에요.

예시 (Example)

keymaxxer는 이 패턴을 AI 코딩 에이전트용 암호화 시크릿 볼트로 구현한 MCP 서버예요.

더 알아보기 (Learn more)