chore(harness): 비밀 파일 읽기 차단 가드 + 규약 문서화
- .claude/hooks/block-secrets.mjs: PreToolUse 훅으로 비밀값 파일 접근 하드 차단 (Read/Edit/Write + Bash cat/less 우회까지). example 파일은 허용. - .claude/settings.json: permissions.deny + 자주 쓰는 dev 명령 allow + 훅 연결 - CLAUDE.md: init 표준 헤더, 하네스 가드/아키텍처 섹션 추가 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
60
.claude/hooks/block-secrets.mjs
Executable file
60
.claude/hooks/block-secrets.mjs
Executable file
@@ -0,0 +1,60 @@
|
||||
#!/usr/bin/env node
|
||||
// PreToolUse 가드 — 비밀값 파일 접근을 하드 차단한다.
|
||||
//
|
||||
// 차단 대상: .project-env, .env, .env.*, ~/.env (LITELLM_KEY 등).
|
||||
// - 허용: .project-env.example (참조용 예시는 OK)
|
||||
// 적용 도구: Read/Edit/Write/NotebookEdit 의 경로, 그리고 Bash 명령 문자열(cat/less/grep 등 우회 포함).
|
||||
//
|
||||
// settings.json 의 permissions.deny 는 Read 도구만 막지만, 이 훅은 Bash 우회까지 막는다.
|
||||
// 반환: permissionDecision=deny 면 도구 호출이 실행 전에 거부된다.
|
||||
|
||||
import { readFileSync } from "node:fs";
|
||||
|
||||
let input = {};
|
||||
try {
|
||||
input = JSON.parse(readFileSync(0, "utf8") || "{}");
|
||||
} catch {
|
||||
process.exit(0); // 입력 파싱 실패 시 통과(다른 가드에 위임).
|
||||
}
|
||||
|
||||
const tool = input.tool_name || "";
|
||||
const ti = input.tool_input || {};
|
||||
|
||||
// .project-env(단, .example 제외) 또는 .env / .env.* / ~/.env 를 가리키는 토큰.
|
||||
const SECRET_RE =
|
||||
/(?:^|[\s'"=(/])\.project-env(?!\.example)|(?:^|[\s'"=(/])\.env(?:\.[\w.-]+)?(?![\w])/;
|
||||
|
||||
let target = "";
|
||||
switch (tool) {
|
||||
case "Read":
|
||||
case "Edit":
|
||||
case "Write":
|
||||
target = ti.file_path || "";
|
||||
break;
|
||||
case "NotebookEdit":
|
||||
target = ti.notebook_path || "";
|
||||
break;
|
||||
case "Bash":
|
||||
target = ti.command || "";
|
||||
break;
|
||||
default:
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
if (SECRET_RE.test(target)) {
|
||||
process.stdout.write(
|
||||
JSON.stringify({
|
||||
hookSpecificOutput: {
|
||||
hookEventName: "PreToolUse",
|
||||
permissionDecision: "deny",
|
||||
permissionDecisionReason:
|
||||
"비밀값 파일(.project-env / .env / ~/.env)은 읽거나 수정할 수 없습니다. " +
|
||||
"DB·MinIO·LiteLLM 자격증명은 런타임에 환경변수로만 주입됩니다($DATABASE_URL 등). " +
|
||||
"키 구조가 궁금하면 .project-env.example 을 보세요.",
|
||||
},
|
||||
})
|
||||
);
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
process.exit(0);
|
||||
35
.claude/settings.json
Normal file
35
.claude/settings.json
Normal file
@@ -0,0 +1,35 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/claude-code-settings.json",
|
||||
"permissions": {
|
||||
"deny": [
|
||||
"Read(.project-env)",
|
||||
"Read(.env)",
|
||||
"Read(.env.*)",
|
||||
"Read(//home/coder/.env)",
|
||||
"Read(~/.env)"
|
||||
],
|
||||
"allow": [
|
||||
"Bash(npm install)",
|
||||
"Bash(npm run dev:*)",
|
||||
"Bash(npm run start:*)",
|
||||
"Bash(npm run db:check:*)",
|
||||
"Bash(npm run minio:check:*)",
|
||||
"Bash(npm run files:check:*)",
|
||||
"Bash(podman build:*)",
|
||||
"Bash(podman compose:*)"
|
||||
]
|
||||
},
|
||||
"hooks": {
|
||||
"PreToolUse": [
|
||||
{
|
||||
"matcher": "Read|Edit|Write|NotebookEdit|Bash",
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "node \"$CLAUDE_PROJECT_DIR/.claude/hooks/block-secrets.mjs\""
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
59
CLAUDE.md
Normal file
59
CLAUDE.md
Normal file
@@ -0,0 +1,59 @@
|
||||
# CLAUDE.md
|
||||
|
||||
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||||
|
||||
> 이 프로젝트에서 Claude Code가 따라야 할 규약
|
||||
|
||||
너는 사내 개발 워크스페이스에서 동작하는 코딩 에이전트다. 이 프로젝트는 **Node(ESM) 앱**이고,
|
||||
**로컬 개발/테스트는 podman**, **배포는 Coolify(Gitea push → Dockerfile 자동빌드)** 로 한다.
|
||||
|
||||
## 절대 규칙
|
||||
- **비밀값을 코드/커밋에 넣지 마라.** DB·MinIO 자격증명은 항상 환경변수에서만 읽는다.
|
||||
값은 프로젝트의 `.project-env` 에 있고(프로젝트 폴더 cd 시 셸이 자동 export), 이 파일은 **절대 커밋 금지**(`.gitignore`에 있음). 참고용 예시는 `.project-env.example`.
|
||||
- 컨테이너는 `docker` 명령으로 실행해도 되지만 **실제 런타임은 rootless podman**이다(`docker`는 podman 별칭).
|
||||
- 운영 배포를 직접 하지 마라. 배포는 **git push → Coolify**가 담당한다. 너는 코드/Dockerfile만 책임진다.
|
||||
|
||||
## 하네스 가드 (자동 적용 — `.claude/`)
|
||||
- `.claude/settings.json` + `.claude/hooks/block-secrets.mjs` 가 **PreToolUse 훅**으로 비밀값 파일 접근을
|
||||
하드 차단한다: `.project-env`, `.env`, `.env.*`, `~/.env`. Read/Edit/Write 뿐 아니라 **Bash 우회**
|
||||
(`cat`/`less`/`grep` 등 명령 문자열)도 막힌다. 키 구조 참고가 필요하면 `.project-env.example` 만 보면 된다.
|
||||
- 값을 직접 들여다보려 하지 말고, 항상 런타임 환경변수(`$DATABASE_URL`, `$S3_*`)와 `src/config.js` 를 통해서만 쓴다.
|
||||
- 자주 쓰는 명령(`npm install`, `npm run dev|start|db:check|minio:check`, `podman build|compose`)은 미리 허용돼 있다.
|
||||
|
||||
## 아키텍처 한눈에
|
||||
- 외부 연결정보는 **`src/config.js` 한 곳**에서만 env를 읽는다 → `src/db.js`(pg Pool, `pingDb`),
|
||||
`src/s3.js`(S3Client, `pingS3`)가 이를 소비. `src/server.js`(Express)가 `/healthz` `/db` `/s3` 로 노출.
|
||||
- `scripts/check-db.js` / `check-minio.js` 는 같은 `pingDb`/`pingS3` 를 CLI로 호출하는 얇은 래퍼다.
|
||||
- DB는 직원 전용 schema가 `search_path` 에 고정돼 격리된다. S3(MinIO)는 `forcePathStyle: true` 필수.
|
||||
|
||||
## 외부 연결 (프로젝트 `.project-env` 에서 env로 주입됨)
|
||||
- **Postgres**: `DATABASE_URL` (직원 전용 schema로 격리, `search_path` 고정). 코드는 `src/db.js`를 통해 접근.
|
||||
- **MinIO(S3)**: API endpoint `https://minio.bokdev.in` (콘솔 `minioc`가 아님), 버킷 `coolify-user-data`.
|
||||
키는 `.project-env`의 `S3_ACCESS_KEY_ID`/`S3_SECRET_ACCESS_KEY`. 코드는 `src/s3.js`를 통해 접근.
|
||||
- **LiteLLM**: AI 호출은 사내 gateway(`https://litellm.bok.or.kr`) 경유. virtual key는 워크스페이스 전역인 `~/.env`(`LITELLM_KEY`)에서 관리 — `update-litellm-key` 명령으로 입력. (프로젝트 설정 아님)
|
||||
|
||||
## 로컬 개발 워크플로 (podman)
|
||||
```bash
|
||||
# 이 폴더는 new-project 로 만들어졌고 .project-env(DB/S3)가 이미 채워져 있다.
|
||||
# 폴더에 들어와 있으면 셸이 .project-env 를 자동 export 한 상태다($DATABASE_URL 등 사용 가능).
|
||||
npm install
|
||||
npm run dev # node --watch 로 핫리로드, http://localhost:3000
|
||||
|
||||
# 컨테이너로 동일하게 돌려보기
|
||||
podman build -t sample .
|
||||
podman run --rm -p 3000:3000 --env-file .project-env sample
|
||||
# 또는
|
||||
podman compose up --build
|
||||
```
|
||||
연결 확인: `npm run db:check`, `npm run minio:check`, 또는 `/db` `/s3` 엔드포인트.
|
||||
|
||||
## 배포 워크플로 (Coolify)
|
||||
1. Gitea repo에 push (`main` 등 배포 브랜치).
|
||||
2. Coolify가 이 **Dockerfile**로 자동 빌드/배포한다(로컬 podman 빌드와 동일 Dockerfile).
|
||||
3. Coolify 쪽 Environment Variables 에 `DATABASE_URL`, `S3_*` 를 채운다(로컬 `.project-env`와 같은 키).
|
||||
4. 포트는 컨테이너 `3000`. Coolify에서 도메인/포트 매핑.
|
||||
|
||||
## 코드 규약
|
||||
- ESM(`import`/`export`), Node 22+. 외부 연결정보는 반드시 `src/config.js` 한 곳을 거친다.
|
||||
- 새 외부 의존성을 추가하면 `package.json`에 반영하고 `npm install`로 lock 갱신.
|
||||
- 변경 후에는 `npm run db:check` / `npm run minio:check`로 연결이 살아있는지 스스로 확인하라.
|
||||
Reference in New Issue
Block a user