Precedent: 어떤 Solidity 보안 조언이 폐기되었는지 아는 에이전트
요약
Solidity 보안 조언은 컴파일러 및 EVM 변경으로 인해 구식이 되기 쉬우며, 기존 LLM으로는 버전 충돌을 파악하기 어렵습니다. 'Precedent' 에이전트는 모든 보안 주장에 날짜와 컴파일러 버전을 포함하여 판례법처럼 관리합니다. 이는 새로운 지침이 이전의 지침을 무효화하는 과정을 추적하며, 구조화된 ID를 반환하여 위조 인용을 방지하고 정확도를 높입니다.
핵심 포인트
- 보안 조언은 버전 의존성이 높아 구식이 되기 쉬움
- Precedent 에이전트는 보안 주장을 판례법처럼 관리함
- 날짜와 컴파일러 버전을 포함하여 주장과 무효화 관계를 추적
- 구조화된 ID 반환으로 위조 인용(citation)을 원천 차단
_이 글은 Sanity Challenge, Path One: 실제 콘텐츠를 쿼리하는 에이전트를 배포하기에 대한 제출물입니다.
제가 만든 것
Solidity의 보안 조언은 구식이 되기 쉬우며, 이 분야에서 구식 조언은 비용이 많이 듭니다. 문서(Docs), EIP(Ethereum Improvement Proposal), 감사 보고서(audit reports), 오래된 블로그 게시물 등이 서로 모순되며, 이러한 불일치 중 상당 부분은 단순히 시간이 지났기 때문입니다. 즉, 컴파일러와 EVM(Ethereum Virtual Machine)이 조언의 기반 아래에서 변경되었기 때문입니다. 키워드 검색이나 일반적인 LLM(Large Language Model)은 두 가지 모두 텍스트만 보기 때문에, 특정 버전에 대해 한 텍스트가 다른 텍스트를 대체했다는 사실을 알지 못하고 구식 답변을 높은 확신도로 반환합니다.
Precedent는 보안 지침을 판례법(case law)처럼 다룹니다. 모든 주장에는 날짜와 컴파일러 버전 범위가 포함되며, 더 새로운 판결이 이전의 판결을 무효화합니다.
- [[Question 1: 버전이 오래된 트랩 패턴 (a stale-trap pattern with a version)]]
- [[Question 2: 진정으로 논쟁 중인 패턴 (a genuinely contested pattern)]]
- [[Question 3: 드리프트가 없어야 하는 안정적인 제어 (the stable control, which should show no drift)]]
코드
[[GITHUB REPO URL]]
작동 방식
Question + compiler version
│
▼
...
- 패턴 및 버전 식별: 질문과 버전 선택기에서 패턴과 버전을 식별합니다. 버전이 제공되지 않으면, 에이전트는 데이터셋의 최신 버전을 가정하고 그렇게 언급합니다.
- 출처 읽기: 지식 기반(Knowledge Base) 엔드포인트를 통해 출처를 읽어보며 어떤 내용들이 있는지, 그리고 어디서 충돌하는지 확인합니다.
- 주장 조회: GROQ 엔드포인트를 통해 주장을 조회합니다. 이 주장은 버전 범위가 해당 버전을 포함하며, 어떤 주장이 어떤 주장을 대체(supersede)하는지를 알려줍니다.
- 해결: 제어 주장(Controlling claims)은 다른 모든 범위 내 주장(in-scope claim)에 의해 대체되지 않는 범위 내의 주장입니다. 만약 이들의 입장이 일치하면 상태는 '확정됨(settled)'입니다. 만약 다르다면, 이는 '논쟁 중(contested)'이며 승자는 지정하지 않습니다. 존재하지 않으면 '범위 외(out of scope)'입니다.
- 서술이 아닌 ID 반환: 에이전트는 주장 ID를 포함하는 구조화된 판결을 방출합니다. UI는 Sanity에서 텍스트, 날짜 및 URL을 로드합니다.
5단계는 의도적입니다. 모델은 출처, URL 또는 날짜를 절대 작성하지 않으므로, 위조 인용(citation)을 할 수 없습니다. API 경로는 또한 모든 ID가 존재하는지 재확인하고 상태를 자체적으로 재계산하며, 불일치가 발생하면 사용자에게 잘못된 답변이 아닌 오류 메시지를 보여줍니다.
Sanity 사용 방법
에이전트는 두 개의 Context MCP 엔드포인트(Context MCP endpoints)를 통해 Sanity를 읽는데, 그 이유는 이들이 서로 다른 역할을 수행하기 때문입니다. 지식 기반으로 백업된 엔드포인트는 지식 기반 모드(Knowledge Base mode)를 제공하고, 데이터셋으로 백업된 엔드포인트는 GROQ 모드를 제공하므로, 저는 각각 하나씩 사용합니다.
Endpoint A: 지식 기반 (Knowledge Base)
저는 Sanity Context에 다음 출처 문서들을 지정했습니다: [\N] 출처 문서들: [\예시로 Solidity 문서 및 변경 로그, EIPs, OpenZeppelin 문서 및 릴리스 노트, N개의 공개 감사 보고서]를 포함합니다. Sanity는 이들을 탐색 가능한 지식 기반(Knowledge Base)으로 정제하며, 각 항목은 출처와 연결된 상태를 유지하고, 상충되는 주장들은 그 출처와 함께 나란히 표시됩니다. [\직접 본 내용 추가: 몇 개의 항목을 생성했는지, 자체적으로 발견한 모순의 예시, 그리고 충돌에 대해 내린 결정 중 어떤 것이든]
저는 의도적으로 베타 버전의 약 150개 문서 예산 범위 내에서 머물렀습니다. 작고 엄선된 핵심 출처가 크고 잡음이 많은 출처보다 낫기 때문입니다.
Endpoint B: 데이터셋, GROQ 모드
지식 기반(Knowledge Base) 모드는 "이 주장은 오직 0.8.x에만 적용되며 그것으로 대체되었다"와 같은 내용을 표현할 수 없으므로, 저는 이를 구조화된 콘텐츠로 모델링했습니다:
| 유형 | 포함 내용 |
|---|---|
pattern | 이름 지정 패턴, 간략한 요약 및 별칭 |
| ... | |
컴파일러 버전은 숫자로 저장됩니다 (예: 0.8.28은 8028이 됨). 그래야 GROQ가 이를 비교할 수 있기 때문입니다. 오직 제가 핵심 출처와 직접 확인한 주장들만 verified로 표시되며, 이들만 사용됩니다. [\N]개의 패턴에 걸쳐 [\5]개 주장. |
판단은 저장되는 것이 아니라 계산됩니다. 이 쿼리는 범위 내의 모든 주장을 그리고 어떤 다른 주장들이 이를 대체하는지를 반환합니다:
*[_type == "claim" && confidence == "verified" && pattern._ref == $patternId
&& (!defined(fromVersion) || fromVersion <= $v)
&& (!defined(toVersion) || toVersion >= $v)]{
...
제어하는 주장들(controlling claims)은 supersededBy에 범위 내 주장이 없는 것들입니다. 판단이 도출되기 때문에, 하나의 주장을 수정하거나 더 새로운 것을 추가하면 재정비 없이도 영향을 받는 모든 답변이 즉시 변경됩니다.
여기서 구조가 중요한 이유
키워드 검색은 특정 패턴을 언급하는 구절들을 찾아냅니다. 하지만 하나의 구절이 다른 구절로 대체되었다는 사실을 알 방법이 없습니다. supersedes 체인과 버전 범위가 콘텐츠 모델이며, 에이전트의 정확성은 이들에 달려 있습니다. 지식 기반(Knowledge Base)은 출처들이 무엇을 말하는지 보여주고; 데이터셋은 어떤 진술이 통제권을 갖는지 알려줍니다.
에이전트가 사용한 도구들
[[List the actual tool names each endpoint exposed, discovered at runtime, and what the agent used each one for. Example format: tool_name (endpoint A): used to ... ]]
Context 엔드포인트는 읽기 전용(read-only)이므로 에이전트는 내용을 변경할 수 없습니다. 주장(Claims)은 스크립트를 통해 시딩되고 Studio에서 편집됩니다.
실제로 대안들을 능가하는가?
저는 명백한 답이 구식이 된 'Stale-Trap' 질문 [[N]]개를 작성했습니다: [[N]]개의 구식 함정, [[N]]개의 진정으로 논쟁되는 사례, [[N]]개의 안정적인 통제(stable controls), 그리고 [[N]]개의 범위를 벗어난 사례입니다. 저는 검증된 주장들로부터 수동으로 정답을 라벨링한 후, 동일한 모델, 동일한 출력 스키마, 질문당 3회로 세 가지 시스템을 실행했습니다:
- A: 원본 출처에 대한 키워드 검색 (모델에 제공되는 상위 구절들, 도구 사용 안 함)
- B: 지식 기반만 이용 (주장 계층이 없는 Endpoint A)
- C: Precedent (두 엔드포인트 모두 이용)
모든 시스템이 동일한 구조화된 판결을 반환하기 때문에, 채점은 모델이 다른 모델을 판단하는 것이 아니라 결정론적(deterministic)입니다. 즉, 어떤 답변이 대체된 주장과 일치하면 _구식(stale)_이고, 인용문이 통제권을 갖는 주장을 포함하면 정확한(correct) 것입니다.
| 시스템 | 구식 답변 비율 (낮을수록 좋음) | 통제하는 출처를 인용함 | 되어야 하는데
– 모델에서 ID만 가져오기. 조작된 인용을 방지합니다.
– '논쟁 중(Contested)'은 일급 결과입니다. 실제로 충돌하는 출처들 사이에서 승자를 가려내는 보안 도구는, 그렇지 않다고 말하는 것보다 못합니다.
– 검증된 주장만 허용됩니다. 검토되지 않은 주장은 판결 경로에 도달할 수 없습니다.
– 모델 메모리로 폴백(fallback)하지 않기. 엔드포인트가 실패하면 사용자에게 오류가 표시되어야 하며, 모델의 학습 데이터에서 추측한 답변을 제공해서는 안 됩니다.
– 작고 깊게. 제대로 수행된 [[]5]] 패턴이 느슨하게 수행된 50개보다 낫습니다.
– 제어 사례(A control case). tx.origin은 드리프트가 없으므로, 정확한 에이전트는 안정적인 답변을 보고해야 합니다.
가드레일 (Guardrails)
Precedent는 절대 익스플로잇 코드를 작성하거나 사용자 코드를 감사하지 않습니다. 모든 판결에는 '게시된 출처를 요약했습니다. 감사가 아닙니다.'라는 문구가 표시됩니다. 주장은 제가 원문을 인용하여 의역한 것이며, 가능한 경우 허가된 라이선스의 출처를 사용했습니다.
도전 과제 및 배운 점 (Challenges and What I Learned)
[[]실제 구축 과정에서 얻은 3~4가지 솔직한 항목을 작성하세요. 질문: 무엇이 가장 먼저 고장났나요? 지식 기반(Knowledge Base)이 예상보다 더 잘했거나 못했던 부분은 어디인가요? 모델이 상속(supersession)을 잘못 읽은 곳은 어디이며, 어떻게 수정했나요? 주장을 수동으로 검증하는 데 얼마나 걸렸으며, 무엇을 발견했나요?][]
한계점 (Limits, Honestly)
– [[]N]] 질문은 작은 표본이므로, 백분율은 결정적이라기보다는 참고용으로 간주하십시오.
– [[]5]] 패턴만 다루고 있습니다.
– 주장은 의역되고 수동으로 검증되었으므로 오류가 있을 수 있습니다. 판결에 의존하기 전에 연결된 출처를 확인하세요.
– [[]지식 기반은 베타 버전입니다. 예상대로 작동하지 않은 것이 있다면 언급해 주세요.]
다음 계획 (What's Next)
– 더 많은 패턴과 EVM의 더 많은 시대
– 코드 스니펫을 붙여넣고 어떤 패턴에 닿는지 감지하기
– 비교 모드: 두 컴파일러 버전에서 동일한 패턴
– [[]진정으로 계획하고 있는 다른 것들]
구축된 기술 스택: Next.js, TypeScript, Vercel AI SDK, Sanity Studio 및 Sanity Context를 사용했으며, [[]모델 이름 및 제공업체]]로 구현되었고, [[]Vercel]]에 배포되었습니다. 코딩 환경은 Google Antigravity입니다. (사실일 경우에만 유지)
Sanity 프로젝트 상세 정보
프로젝트 ID: [[YOUR SANITY PROJECT ID]] (데이터셋: [[DATASET NAME]])
[[]또는 공개 데이터셋 URL을 사용하고, 해당 데이터셋이 공개인지 확인하세요]]
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기