위키 동기화 2026-06-19: 보안 트러블슈팅 노트·회의록·lessons·Digests + ASTRA 성장 산출물

- 00_Raw: ASTRA 보안 가이드 3종(SSRF/셸 명령/파일 경로 경계), 회의록 p/q/r 추가
- Topics: Digests 5종, lessons 4종, 메모리 에피소드/장기기억 갱신
- .astra: growth(decay/regression/weakness)·eval(corrections/report) 학습 산출물 갱신

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-19 18:29:23 +09:00
parent e2c5471046
commit 8957890d13
46 changed files with 1494 additions and 102 deletions
@@ -0,0 +1,118 @@
---
id: astra-ssrf-url-fetch-guard-20260619
title: "ASTRA SSRF 방어 — 신뢰 불가 URL fetch 경계(assertPublicUrl)"
category: "Security"
status: "applied"
verification_status: "validated"
canonical_id: ""
aliases: ["SSRF 방어", "assertPublicUrl", "ssrfGuard", "URL fetch 경계", "사설 IP 차단", "169.254.169.254 메타데이터 차단", "DNS 리바인딩", "fetch_url 보안", "buildUrlContext 가드"]
duplicate_of: ""
source_trust_level: "S"
confidence_score: 0.95
created_at: 2026-06-19
updated_at: 2026-06-19
review_reason: ""
merge_history: []
tags: [security, astra, ssrf, network, fetch, web, troubleshooting]
raw_sources: ["E:/Wiki/astraai/src/features/web/ssrfGuard.ts", "E:/Wiki/astraai/src/features/web/webFetch.ts", "E:/Wiki/astraai/src/lib/contextBuilders/urlContext.ts", "E:/Wiki/astraai/src/agent/actions/webFetch.ts", "E:/Wiki/astraai/tests/ssrfGuard.test.ts"]
applied_in: ["E:/Wiki/astraai @ branch (uncommitted, 2026-06-19)"]
github_commit: ""
---
# [[ASTRA SSRF 방어 URL fetch 경계]]
## 🎯 한 줄 통찰 (One-line insight)
**모델/웹 콘텐츠가 준 URL을 호스트 검증 없이 fetch 하던 SSRF 경로를, "공인 IP 가 아니면 거부"하는 allowlist 성격의 `assertPublicUrl` 가드로 닫고 리다이렉트 각 홉까지 재검증한다.**
## 🧠 핵심 개념 (Core concepts)
- **SSRF (Server-Side Request Forgery)**: 신뢰 불가 입력으로 서버가 내부 자원에 요청하게 만드는 공격. 여기선 "에이전트 프로세스"가 내부망/메타데이터/로컬 브릿지에 도달.
- **차단 대상 IP**: 루프백(127/8·::1), 사설(10/8·172.16/12·192.168/16·fc00::/7), 링크로컬(169.254/16=클라우드 메타데이터·fe80::/10), CGNAT/멀티캐스트/예약, IPv4-mapped IPv6.
- **단일 chokepoint**: `buildUrlContext` 입구에서 한 번 검증 → 브릿지 추출·직접 fetch 양 경로 모두 보호.
- **리다이렉트 재검증**: `redirect:'manual'` + 각 홉 `assertPublicUrl` 재호출로 공인→사설 우회 차단.
## 🩺 증상 (Symptom)
일반 챗의 URL 주입과 에이전트 `<fetch_url>`이 모델/페이지가 제시한 임의 URL을 `redirect:'follow'`로 그대로 fetch했다. 악성 페이지가 `http://127.0.0.1:3002`(로컬 브릿지)·`http://169.254.169.254/...`(메타데이터)·NAS/인트라넷 주소를 제시하면 그 응답 본문이 모델 컨텍스트로 주입될 수 있었다.
## 🌐 환경 / 범위 (Environment & scope)
- 프로젝트: ASTRA (`E:/Wiki/astraai`). Node 18+ global `fetch`.
- 경로: `urlContext.buildUrlContext`(일반 챗 + `fetch_url` 액션 공용) → ① 브릿지 추출 → ② `fetchUrlDirect` 폴백.
- 비대상: `bridgeFetch`(의도적 localhost:3002 도구 호출)는 SSRF 가드 미적용 — 별도 함수.
## 🔁 재현 절차 (Reproduction)
1. 챗 또는 모델 응답에 `http://169.254.169.254/latest/meta-data/` 같은 내부 URL 포함.
2. 기존 `fetchUrlDirect`: `http/https`만 확인하고 목적지 IP 미검증 → 그대로 요청, 본문 반환([webFetch.ts](E:/Wiki/astraai/src/features/web/webFetch.ts)).
3. 공인 도메인 → 302 리다이렉트로 사설 IP 유도 시 `follow`가 그대로 추적.
## 🔥 영향 및 심각도 (Impact & severity)
**High.** 내부망 스캐닝·클라우드 메타데이터 자격증명 탈취·로컬 전용 서비스(브릿지) 호출 가능. 입력 출처가 untrusted(웹/모델)라 악용 난도 낮음.
## 🧠 근본 원인 (Root cause)
- `fetchUrlDirect`가 스킴(`http/https`)만 검사하고 **호스트→IP 해석 후 사설/예약 범위 검증이 전무**.
- `buildUrlContext`가 URL을 브릿지·직접 양쪽으로 무검증 전달.
- `redirect:'follow'`로 리다이렉트 목적지 재검증 불가.
## 🔎 조사 과정 (Investigation)
- 보안 감사로 `webFetch.ts:81-103`·`agent/actions/webFetch.ts:20`의 무필터 fetch 경로 식별.
- 외부 표준 리서치: OWASP는 **denylist보다 allowlist**(공인 IP 아니면 거부), DNS 리바인딩 방지엔 **해석 시점 IP 검증/피닝** 권고 [S5].
## 🛠️ 해결 (Resolution / applied fix)
1. 신규 모듈 [ssrfGuard.ts](E:/Wiki/astraai/src/features/web/ssrfGuard.ts): `isBlockedIPv4/IPv6/Ip`, `assertPublicUrl(url)` — IP 리터럴 즉시 검사, DNS 호스트는 **모든 A/AAAA 레코드** 해석 후 하나라도 차단 대상이면 거부. `localhost`류는 해석 전 거부.
2. [urlContext.ts](E:/Wiki/astraai/src/lib/contextBuilders/urlContext.ts) 입구에 `assertPublicUrl` — 실패 시 "🚫 URL 접근 차단" 정직 블록 반환(브릿지·직접 양 경로 차단).
3. [webFetch.ts](E:/Wiki/astraai/src/features/web/webFetch.ts) `fetchUrlDirect`: `redirect:'manual'`로 전환, 최대 5홉 루프에서 각 홉 `assertPublicUrl` 재검증(이중 방어 + 독립 호출자 보호).
## 💻 코드 패턴 (Code patterns)
```ts
// ssrfGuard.ts — "공인 아니면 거부"
export async function assertPublicUrl(rawUrl: string): Promise<void> {
const host = new URL(rawUrl).hostname.replace(/^\[|\]$/g, '');
if (isIP(host)) { if (isBlockedIp(host)) throw new SsrfBlockedError(`차단된 내부 IP: ${host}`); return; }
if (/^localhost$/i.test(host) || host.endsWith('.localhost')) throw new SsrfBlockedError('로컬 호스트');
const addrs = await dnsLookup(host, { all: true }); // 모든 레코드 검사(다중 IP 우회 방지)
for (const { address } of addrs)
if (isBlockedIp(address)) throw new SsrfBlockedError(`내부 IP 해석: ${host}${address}`);
}
```
```ts
// webFetch.ts — 리다이렉트 각 홉 재검증
for (let hop = 0; hop <= 5; hop++) {
try { await assertPublicUrl(current); } catch (e) { return fail(`차단됨(SSRF 방지): ${e.message}`); }
res = await fetch(current, { redirect: 'manual', /* … */ });
if (res.status >= 300 && res.status < 400 && res.headers.get('location')) {
current = new URL(res.headers.get('location'), current).toString(); continue;
}
break;
}
```
## ✅ 검증 (Verification)
- 신규 [tests/ssrfGuard.test.ts](E:/Wiki/astraai/tests/ssrfGuard.test.ts) 9개: IPv4/IPv6 범위, 내부 IP URL 거부, `localhost` 거부, 잘못된 URL 거부.
- `urlContextBuild.test.ts`는 SSRF 가드를 모킹(할루시네이션 로직 검증 전용, DNS 비의존화).
- `tsc` 0 에러, 전체 716 통과.
## ⚖️ 모순 및 업데이트 (Contradictions & updates)
**한계(정직 고지):** 해석 시점 검증이라 fetch 가 재해석하는 사이 레코드가 바뀌는 **DNS 리바인딩 창**은 완전히 닫지 못한다. 완전 차단은 연결 IP 고정(커스텀 undici dispatcher)이 필요 — 정적 사설 IP·리다이렉트 우회는 차단됨. 브릿지(:3002) 자체의 서버사이드 fetch SSRF는 브릿지(별도 프로젝트)의 책임.
## 🛠️ 적용 사례 (Applied in summary)
ASTRA 일반 챗 URL 주입 + 에이전트 `fetch_url` 액션 + `/wikify` 폴백(직접 fetch) 경로에 적용.
## ✅ 검증 상태 및 신뢰도
- **상태:** applied (미커밋)
- **검증 단계:** validated (단위 테스트 + 빌드/타입)
- **출처 신뢰도:** S
- **신뢰 점수:** 0.95
- **중복 검사 결과:** 신규 생성
## 🔗 지식 그래프 (Knowledge Graph)
- **상위/루트:** [[ASTRA]]
- **관련 개념:** [[ASTRA 에이전트 셸 명령 실행 보안 게이트]], [[ASTRA 파일 경로 경계 가드]], [[ASTRA Datacollect Bridge]]
- **참조 맥락:** 에이전트/LLM이 외부 URL을 가져올 때의 네트워크 경계 통제 기준.
## 📚 출처 (Sources)
- [S1] `E:/Wiki/astraai/src/features/web/ssrfGuard.ts` — IP 범위 판정 + `assertPublicUrl`.
- [S2] `E:/Wiki/astraai/src/features/web/webFetch.ts``fetchUrlDirect` 수동 리다이렉트 + 가드.
- [S3] `E:/Wiki/astraai/src/lib/contextBuilders/urlContext.ts` — 입구 chokepoint.
- [S4] `E:/Wiki/astraai/tests/ssrfGuard.test.ts` — 회귀 테스트.
- [S5] OWASP SSRF Prevention / `azu/request-filtering-agent`, Wiz SSRF guide — allowlist·DNS 리바인딩 권고.
## 📝 변경 이력 (Change history)
- 2026-06-19: 보안 감사 + 외부 표준 대조 후 SSRF 가드 신설 및 최초 문서화(Claude Opus 4.8).