본문으로 건너뛰기

AI 데이터베이스 보호 (4중 안전망)

Multi-SaaS Kit 은 AI 협업 워크플로우 를 핵심 가치로 하는 플랫폼입니다. AI 에 의한 우발적인 데이터 손실을 막기 위해 4중 안전망 + 1 옵션 을 기본 탑재합니다.

왜 필요한가?

AI 코딩 도구 (Claude Code, Codex, Cursor, …) 가 보편화되면서 다음과 같은 위험이 실재화되었습니다:

위험 시나리오결과
AI 가 php artisan migrate:fresh 자율 실행전 테이블 drop → 운영 데이터 전체 손실
AI 가 Model::truncate() 호출단일 테이블 전체 삭제
AI 가 psql -c "TRUNCATE …" Bash 실행application 우회 → 직접 SQL 파괴
AI 가 WHERE 빠진 DELETE FROM users의도와 다른 전체 삭제

일반 백업만으로는 부족합니다 — 손실 발생부터 인지까지 시간 차가 있고, 부분 손실 후 데이터 부정합 복구는 매우 어렵습니다.

사후 복구보다 사전 차단 이 비용/리스크 모두 우월합니다.


4중 안전망 한눈에 보기

                                AI 의 destructive 작업 시도

┌─────────────────────────┼─────────────────────────┐
│ │ │
Bash 실행 PHP 코드 Artisan 명령
│ │ │
┌───────▼──────┐ ┌────────▼────────┐ ┌──────▼──────┐
│ Phase E │ │ Phase A │ │ Phase 0 │
│ AI Bash hook│ │ Query Listener │ │ Cmd Guard │
│ (Claude Code)│ │ (DB::listen) │ │ (Artisan) │
└───────┬──────┘ └────────┬────────┘ └──────┬──────┘
│ │ │
└─────────────┬───────────┴───────────┬─────────────┘
│ │
차단 + audit 통과
│ │
┌──────▼──────┐ ▼
│ Phase F │ 정상 실행
│ Audit log │
└──────┬──────┘

⚠️ AI→SQL→User 워크플로
(사용자가 admin 도구로 직접 실행)
Phase레이어막는 대상기본 활성
0Artisan 명령 차단migrate:fresh|refresh|reset, db:wipe, schema:dump --prune
A런타임 Query 차단Model::truncate(), raw TRUNCATE/DROP/DELETE FROM(WHERE 없음)
CDB Role 분리 (PostgreSQL)application connection 의 권한 자체를 제한⏸️ 옵션
EAI Bash hook 차단AI 가 Bash 로 실행하는 destructive shell SQL
FAudit log 기록차단/허용 이력 영구 추적

일반 개발 워크플로 (테스트, RefreshDatabase, 정상 마이그레이션) 는 자동 허용 됩니다 — false positive 가 거의 없습니다.


Phase 0 — Artisan 명령 차단

php artisan migrate:fresh 같은 명령을 Laravel 부트 시점에 가로챕니다.

차단 대상

명령위험
migrate:fresh전 테이블 drop 후 재생성
migrate:refresh전체 rollback 후 재실행
migrate:reset전체 rollback
db:wipe전 테이블 drop
schema:dump --prune마이그레이션 파일 일괄 삭제

자동 허용

  • PHPUnit 실행 + APP_ENV=testing + migrate:fresh 만 허용 (Laravel RefreshDatabase 정상 경로)

운영자 1회성 우회 (필요한 경우)

# 명령별로 토큰 발급 — .env 에 상시 저장 금지
MSK_ALLOW_DESTRUCTIVE_ARTISAN="MSK-ALLOW-DB-DESTRUCTIVE:migrate:fresh" \
php artisan migrate:fresh

Phase A — 런타임 Query 차단

PHP 코드에서 발생하는 destructive query 를 DB::listen() 으로 가로채 차단합니다. Phase 0 가 막지 못하는 런타임 우회 경로 를 차단합니다.

차단 패턴

TRUNCATE [TABLE] {name}              -- ⛔ 차단
DROP TABLE {name} -- ⛔ 차단
DROP DATABASE {name} -- ⛔ 차단
DELETE FROM {name} -- ⛔ 차단 (WHERE 없음)

DELETE FROM {name} WHERE-- ✅ 통과
UPDATE {name} SETWHERE-- ✅ 통과
SELECT/ INSERT-- ✅ 통과

차단 시 동작

RuntimeException: ⛔ DB destructive query blocked: TRUNCATE TABLE users
사용자 명시 필요 (MSK_ALLOW_DESTRUCTIVE_QUERY=1) 또는 admin role 에서 실행하세요.

→ 호출 stack 까지 예외 전파, 트랜잭션이 있다면 자동 롤백.

자동 허용

  • APP_ENV=testing + runningUnitTests() (RefreshDatabase 호환)
  • MSK_ALLOW_DESTRUCTIVE_QUERY=1 환경변수 (운영자 1회성 우회)

Phase E — AI Bash hook 차단

AI 코딩 도구 (Claude Code) 가 Bash 로 실행하는 destructive 명령을 실행 직전 에 가로챕니다.

검사 대상 (Bash 만 — Edit/Write 제외)

패턴 예시차단
psql -c "TRUNCATE TABLE users"
mysql -e "DROP TABLE foo"
php artisan migrate:fresh
docker exec mydb psql -c "DROP DATABASE x"
php artisan tinker --execute "User::truncate()"
grep -r 'truncate' src/✅ (정상 검색)
git log --oneline | grep truncate✅ (정상 검색)

왜 Bash 만? Edit/Write 의 텍스트에 truncate, delete from 단어가 등장하는 정상 케이스(주석, 문서, 검색 결과)가 너무 많습니다. SQL 실행 컨텍스트(Bash) 만 검사하여 false positive 를 거의 0 으로 만들었습니다.

차단 시 AI 의 동작 (AI→SQL→User 워크플로)

AI 응답:
⛔ 이 작업은 admin 권한이 필요합니다.

다음 SQL 을 사용자가 직접 실행해주세요:

TRUNCATE TABLE foo CASCADE;

실행 위치 (택 1):
- pgadmin (admin role 연결)
- 운영 DBA 에게 요청

실행 후 결과를 알려주시면 후속 작업을 진행하겠습니다.

→ AI 는 SQL 을 작성만 하고, 실제 실행은 사용자가 admin 권한 도구로 직접 수행.


Phase F — Audit Log 기록

차단/허용 이력은 모두 audit_logs 테이블에 영구 기록됩니다.

SELECT action, message, meta->>'sql', created_at
FROM audit_logs
WHERE action LIKE 'db_destructive_%'
ORDER BY created_at DESC
LIMIT 20;
action의미
db_destructive_blocked차단됨 (RuntimeException 발생)
db_destructive_allowedunlock 토큰 / RefreshDatabase 로 통과됨

→ 운영자가 주기적으로 검토하여 의심 패턴을 탐지할 수 있습니다.


Phase C — PostgreSQL Role 분리 (옵션)

가장 강력한 안전망. application connection 자체에 destructive 권한이 없도록 PostgreSQL Role 을 2단으로 분리합니다.

┌────────────────────────┐         ┌────────────────────────┐
│ app_user (서비스용) │ │ admin_user (관리용) │
│ │ │ │
│ ✅ SELECT │ │ ✅ SELECT │
│ ✅ INSERT │ │ ✅ INSERT │
│ ✅ UPDATE WHERE │ │ ✅ UPDATE │
│ ✅ DELETE WHERE │ │ ✅ DELETE │
│ ⛔ TRUNCATE │ │ ✅ TRUNCATE │
│ ⛔ DROP │ │ ✅ DROP │
│ ⛔ migrate / DDL │ │ ✅ migrate / DDL │
│ │ │ │
│ Laravel 앱이 사용 │ │ pgadmin / DBA 가 사용 │
└────────────────────────┘ └────────────────────────┘

장점: AI 가 Phase 0/A/E 를 모두 우회해도 PostgreSQL 레벨에서 거부됩니다.

단점: 인프라 변경이 큼 — 22+ 프로젝트 마이그레이션 필요. 따라서 옵션 으로 제공.

보안 강도 요구가 높은 운영 환경 (금융, 의료, 정부) 에서 권장. 일반 SaaS 는 Phase 0+A+E+F 만으로 충분.

활성화 가이드는 별도 문서 (예정) 에서 다룹니다.


운영자가 알아야 할 것

1. 신규 프로젝트는 자동 적용

make create NAME=myproject 로 생성된 모든 프로젝트는 Phase 0/A/F 가 기본 활성됩니다. 추가 설정 불필요.

2. 기존 프로젝트 적용

DatabaseSafetyServiceProviderbootstrap/providers.php 에 등록하면 됩니다.

return [
// ...
\App\Core\Base\Database\Providers\DatabaseSafetyServiceProvider::class,
];

3. Phase E (AI Bash hook) 활성화

Claude Code 사용 시 .claude/settings.json 의 PreToolUse hook 에 pre-tool-use-db-guard.sh 가 등록되어야 합니다. msk 의 신규 프로젝트는 make create 시 자동 등록.

4. Unlock 토큰 정책

토큰용도권장
MSK_ALLOW_DESTRUCTIVE_ARTISANPhase 0 우회 (migrate:fresh 등)명령별 1회성
MSK_ALLOW_DESTRUCTIVE_QUERYPhase A 우회 (런타임)명령별 1회성
CLAUDE_ALLOW_DB_DESTRUCTIVEPhase E 우회 (AI Bash)사용자 명시 시만

⚠️ .env 에 상시 저장 금지. 명령 직전에만 export, 명령 직후 unset.

5. 정상 워크플로에서 차단되는 경우

매우 드물지만 다음과 같은 경우 false positive 가 발생할 수 있습니다:

  • 데이터 정리 스크립트의 DELETE FROM logs (WHERE 없음, 의도적 전체 삭제) → MSK_ALLOW_DESTRUCTIVE_QUERY=1 1회 실행
  • 수동 마이그레이션 스크립트의 DROP TABLE old_table → admin role 에서 직접 실행 (Phase C 권장)

비교 — 다른 솔루션 대비

솔루션보호 범위msk 의 4중 안전망
Laravel 기본 (prohibitDestructiveCommands())Artisan 명령만Phase 0 + A + E + F (3 레이어 추가)
백업만사후 복구사전 차단 + 백업 양쪽
단순 sudo wrapper권한 게이트만AI→SQL→User 워크플로 + audit
ORM-only 차단raw SQL 우회 가능DB::listen 으로 raw 까지 잡음

검증

영역결과
Phase A QueryGuardListener 단위 테스트13/13 PASS
Phase 0 DestructiveCommandGuard 단위 테스트5/5 PASS
Phase E hook 음성/양성 케이스✅ 모두 검증
전체 회귀 (PG testing)1212 / 28 (변동 0)

관련 자료