@gaonjs/seal
v0.3.0
Published
Gaon 선택형 페이로드 봉인 플러그인 (GSP · 난독화·replay 방어·구간암호화). ⚠️ 서버 검증을 대체하지 않는다 — agents/security.md §0 참조.
Readme
@gaonjs/seal
Gaon 선택형 페이로드 봉인 플러그인 (GSP — Gaon Secure Protocol · 결정 121).
앱 app.config.ts 에 seal: true 한 줄이면 그 앱의 wire(요청/응답 JSON + 최초 문서 data-page)와
WebSocket 채널 프레임이 봉인된다. 컨트롤러·useForm·api()·채널 코드는 바뀌지 않는다 — 봉인/개봉은
서버 Fastify 훅과 클라이언트 인터셉터가 전송 경계에서 투명하게 처리한다.
⚠️ 정직한 포지셔닝 — 먼저 읽어라
seal 은 "완전한 보안" 이 아니고, 서버 검증을 대체하지 않는다. 키 유도 공식·시드·미끼 마스터 시크릿이
클라이언트(wasm)에 실린다 — 작정한 공격자는 봉인 규약을 복원해 조작할 수 있다. 그러므로 기존 방어층
(스키마 검증 · 대량 할당 방어 · requireAuth+소유권 404 · CSRF · rate limit · Kysely 바인딩 · Vue 이스케이프)을
seal 을 켠 뒤에도 그대로 유지해야 한다.
seal 이 주는 것은 딱 3가지다:
- 난독화 계층 — 소스 보기·개발자도구·자동 스캐너를 막아 공격 비용을 올린다(방어 아님).
- replay 방어 — 탈취 트래픽 재전송을 nonce + timestamp drift 로 차단한다(HTTPS 도 못 하는 부분).
- 구간암호화 컴플라이언스 — 금융권류 wire 암호화 요구를 만족시킨다.
이 기능의 유일한 진짜 위험은 "seal 켰으니 검증을 느슨하게 해도 된다" 는 가짜 안심이다. 자세한 경계표는 사용자 프로젝트의
agents/security.md§0.
설치 · 사용
npm i @gaonjs/sealseal 을 켜는 데는 두 지점이 필요하다 — app.config 토글 + main.ts 클라이언트 배선:
// ① apps/web/app.config.ts
export default defineAppConfig({
seal: true, // 또는 { except: ['/webhooks/*'] }
})// ② apps/web/main.ts — seal 클라이언트를 정적 import 해 createGaonApp 에 넘긴다.
import { createGaonApp } from 'gaonjs/vue'
import * as sealClient from '@gaonjs/seal/client'
void createGaonApp({ pages, layouts, sealClient })- ②가 필요한 이유:
@gaonjs/vue는 선택 플러그인 seal 을 몰라야 하고, vite 는 변수 동적 import 를 번들하지 못한다 — 봉인 클라이언트(+wasm)는 seal 을 설치한 사용자 프로젝트의 main.ts 가 정적 import 해 주입해야 한다. 빠뜨리면 봉인 문서를 브라우저가 못 열어 화면이 blank 로 뜬다(gaon check의seal-security가 잡고, 런타임도 수리 안내로 실패한다). - CSP: 코어가 seal 앱에 한해
script-src에'wasm-unsafe-eval'을 자동 보정한다(wasm instantiate 허용). - 정적 자산·헬스체크·multipart 업로드 body 는 자동 제외된다(기계 판별).
- 봉인 앱의 JSON 경로에 봉인 시그널 없이 온 요청은 403(fail-closed) — 외부가 봉인을 모르는 경로는
except. masterSecret설정 표면은 없다(미끼 literal · ADR-056). 설정할 이유가 없는 값은 표면을 두지 않는다.
알고리즘 (byte-exact 이식)
AES-256-GCM + nibble-swap XOR(0x5A) + base64. 키 유도 = SHA256(hex(HMAC-SHA256(secret, "domain:path:uaSlice:ts"))),
per-frame keying(시드에 userId 미포함). Rust 정본(wasm 클라이언트) ↔ JS mirror(서버) 가 known-vector CI 로
양방향 byte 호환을 강제한다. 클라이언트 crypto 는 wasm 이라 소스/개발자도구에서 읽기 어렵다.
crate 를 고치면 pnpm --filter @gaonjs/seal build:wasm 로 wasm 을 재빌드한다(정본 Rust → wasm 산출).
서브패스
@gaonjs/seal— 서버측 API(Fastify 훅·crypto mirror·replay 가드·WS terminator).@gaonjs/web이 앱 토글로 자동 배선.@gaonjs/seal/client— 브라우저 인터셉터(wasm · data-page 개봉·Inertia XHR·api()봉인).@gaonjs/vue가 자동 로드.
일반적으로 이 서브패스들을 직접 import 할 일은 없다 — seal: true 토글이면 프레임웍이 배선한다.
