요약 (TL;DR)
- AI를 통해 앱 제작이 가능해졌다. 하지만 만들었을 때 사용했던 부품이나 실행 환경을 영원히 기억할 수는 없다.
- PHP의 취약점을 계기로, 자신의 앱을 방치하지 않기 위한 점검(Audit) 시스템을 구축했다.
- 공통 하네스에 장부(台帳), 의존 관계 목록화, 대응 우선순위, 오래된 프로세스 감지, 업데이트 보류 기한 등을 통합했다.
- 하지만 단순히 알림이 오는 것만으로는 이해도가 깊어지지 않는다. '나와 관련 있는가?', '무엇을 확인해야 하는가?'라는 질문에 답할 수 있도록
AI Security Playbook을 만들었다. - 점검 및 자료 수집은 AI의 도움을 받는다. 근거를 확인하고 사람의 눈으로 내용을 검증하여 공개하는 과정은 남겨둔다.
만들고 난 후, 무엇을 썼는지 잊어간다
PHP 취약점이 화제가 되었다.
궁금했던 것은 단순히 취약점의 구조만이 아니었다.
'나도 사용하고 있는가?'
'어떤 앱의 어디에 포함되어 있는가?'
'애초에 무엇을 확인해야 알 수 있을까?'
AI의 도움을 받으면 앱 제작이 가능해졌다.
제작하는 동안에는 AI가 설명해준다. 프레임워크, 라이브러리, 실행 환경, 배포처. 그때는 대화를 읽으면서 이해했다고 착각한다.
하지만 다음 앱을 만들기 시작하면, 이전 앱에서 무엇을 사용했는지 조금씩 잊어간다. 모델명도, 부품의 버전도, 언제 왜 그 구성을 선택했는지까지도.
만들었을 때가 가장 잘 기억난다.
이는 비엔지니어에게만 해당하는 이야기는 아닐 것이다. 몇 달 전 작은 도구의 의존 관계를 모두 기억하는 사람은 많지 않을 것이다.
다만, 비엔지니어인 자신은 잊어버린 후에 어디서부터 조사해야 할지 알 수 없게 된다.
매번 생성 시점의 채팅 기록을 찾아보는 일은 하고 싶지 않았다.
만든 것을 자신의 기억력만으로 유지보수하는 것을 그만두자.
이번에 하려 했던 것이 바로 그것이다.
업데이트했다. 하지만 작동하던 것은 구 버전이었다
Node.js 업데이트에서도 같은 문제가 발견되었다.
이전 글에서는 본인의 Mac에서 Node를 업데이트한 후에도, 수정 전의 Node로 작동하는 프로세스가 19개 남아있었던 것을 썼다.
설치된 버전과 현재 작동 중인 버전은 다르다.
리포지토리에 적힌 버전과 실제 운영 환경(Production)에서 작동하는 버전도 다르다.
| 계층 | 확인할 것 | 이것만으로는 알 수 없는 것 |
|---|
| 개발 단말기 | 도입된 툴/런타임 | 실행 중인 프로세스가 새로운 실체를 사용하는지 |
| ... | | |
| 예를 들어, Dockerfile을 수정해도 이미지를 다시 만들지 않았다면 운영 환경은 변하지 않는다. 만들어도 배포하지 않았다면 변하지 않는다. | | |
이러한 각 단계를 통틀어 '업데이트했다'라고 부르면 간과하기 쉽다.
그래서, 무엇을 확인했는지 분리하여 남기는 시스템이 필요하게 되었다.
하네스에 '제작 후' 점검(Audit)을 넣었다
개발에서는 Claude Code, Codex, Cursor Agent를 사용한다.
공통 규칙이나 작업 시작 전 확인 사항, 리뷰 등은 ai-dev-harness라는 리포지토리에 모아두고 있다. 여기에 제작 후의 점검(Audit)도 추가했다.
하네스라고 해도 모든 것을 판단하는 거대한 시스템은 아니다. 장부, 스크립트, 운영 규칙을 조합한 것이다.
1. 점검 대상을 정한다. 장부에 누락된 것도 찾는다
리포지토리를 '기반(基盤)', '운영 중(稼働中)', '보관(保管)'으로 나누었다.
의존 관계를 주기적으로 점검하는 것은 기반과 운영 중인 것들이다. 보관 중인 것까지 무조건 업데이트한다면, 대응 대상이 끝없이 늘어나게 된다.
하지만 장부 등록도 잊는다.
그래서 GitHub 리포지토리 목록과 대조하여 미등록된 것을 알려주도록 했다.
찾고 싶은 것은 오래된 부품만이 아니다. 점검 대상에 포함되지 않은 것도 마찬가지다.
물론, 리포지토리 목록과 공개 서비스 목록은 같지 않다. 장부 대조만으로 공개 중인 서비스를 완벽하게 파악할 수는 없다.
2. 버전과 알림(Alert)을 대응 순서로 바꾼다
deps-scan은 clone하여 앱을 실행하는 것이 아니라, GitHub나 패키지 레지스트리에서 자료를 모은다.
| 자료 | 취득원 |
|---|
| 취약점 알림(Vulnerability Alert) | Dependabot |
| ... | package.json, Python 의존 정의 |
| 패키지의 최신 버전 | npm, PyPI |
| 런타임 지정 | Dockerfile, .nvmrc, .node-version |
| 업데이트 정체(Stagnation) | Dependabot의 미병합 PR |
| 지원 종료 예정 | 지원 기간 정보 |
SBOM은 소프트웨어 구성 요소 목록(Software Bill of Materials)입니다. 여기서는 GitHub가 제공하는 의존성 정보를 자료로 사용합니다.
여러 리포지토리에서 동일한 패키지를 사용하기 때문에, 최신 버전 여부를 확인하는 과정은 한 번의 점검만으로 생태계(ecosystem)와 패키지 이름을 키(key)로 재사용합니다. 이는 날짜를 넘어서 오래된 최신 버전을 고정하는 캐시가 아닙니다.
수집한 정보는 자신의 운영 목적에 맞게 P1부터 P3까지 정리됩니다.
| 우선순위 | 판단 예시 | 대응 기준 |
|---|
| P1 | critical, 프로덕션 실행 시 의존성이 높은 high, 프로덕션에서 지정하는 런타임의 지원 종료 | 발견한 주 내에 대응 PR 제출 |
| ... | | |
| 구현 일부는 다음과 같습니다. | | |
let level;
if (
g.severity === 'critical' ||
...
이것은 대응 순서를 정하는 규칙일 뿐, 해당 환경에서 공격이 성립할지 여부를 판단하는 코드는 아닙니다.
프로덕션 사용 여부나 의존 용도에 따라 분류한 후, 개별적인 이용 조건을 조사합니다.
3. 알린 후에도 다음에 만질 때 다시 보여주기
알림은 본 후에 잊어버리기 쉽습니다.
그래서 결과를 최신 목록과 날짜가 찍힌 이력에 저장하고, 작업 시작 전 사전 점검(preflight) 시
이 리포지토리의 점검 결과를 표시합니다. P1 항목이 남아 있다면 주의를 줍니다.
작업을 시작할 때마다 모든 정보를 재조사하는 것은 아닙니다. 보여주는 것은 저장된 결과이므로, 조회일도 확인합니다.
알리는 것보다 확인할 기회를 한 번으로 끝내지 않는 것에 중점을 두었습니다.
4. '지금은 올리지 않기'에 검토 날짜를 지정하기
메이저 업데이트(Major Update)를 적용하면 빌드가 실패하는 경우도 있습니다.
업데이트를 보류한다는 판단 자체는 할 수 있습니다. 하지만 그것을 무기한으로 하고 싶지 않았습니다.
그래서, 보류할 때는 다음 항목을 남기는 규칙을 만들었습니다.
| 항목 | 기록할 내용 |
|---|
| 대상 | 패키지명・보류하는 메이저 버전 |
| ... | |
| 근거가 부족하거나 기한이 지난 것은 목록과 알림에 표시합니다. | |
'지금은 업데이트하지 않는다'를 '더 이상 확인하지 않는다'로 만들지 않습니다.
참고로, 취약점 수정은 단순히 무시(ignore)하는 것으로 끝내지 않습니다. 필요한 마이그레이션(migration)을 할지, 공개 중단까지 포함하여 처리 방식을 결정합니다.
오래된 상태로 작동하는 것은 별도로 찾기
의존성 목록만으로는 업데이트 전에 실행했던 프로세스는 볼 수 없습니다.
여기에 stale-procs를 두어, 오래된 실체를 사용하며 계속 움직이는 프로세스를 확인하도록 했습니다. Node, Claude Code, Homebrew 관리 도구 등이 대상이 됩니다.
여기서는 '오래됐다면 전부 중지'로 처리하지 않습니다.
- 자신이나 조상 프로세스는 보호합니다.
- 일부 MCP(Main Control Process)나 개발 서버는 부모가 종료되어 남아있고 일정 시간 이상 경과한 경우 등, 조건을 충족하는 것만 자동으로 중지시킵니다.
- 판단할 수 없는 것은 남겨두고 알립니다.
- 원격 제어(Remote Control)의 부모 처리에도 자식 세션이나 관측 이력에 따른 조건을 둡니다.
왜 일괄적으로 재부팅하지 않는가
프로세스가 조용하다고 해서 사용되지 않는 것은 아닙니다. CPU 관측만으로는 모든 대기 중인 처리를 판단할 수 없습니다.
이 처리는 자신의 개발 기기에서 대상을 한정하여 사용하고 있습니다. 다른 환경에 그대로 적용하는 일괄 종료 절차로는 권장하지 않습니다.
주간 처리 과정에서는 개발 도구의 업데이트, 그 후 오래된 프로세스 확인, 의존성 점검을 연결하고 있습니다. macOS 본체 업데이트는 자동 적용하지 않고, 들어오고 있다는 사실만 알립니다.
'도입된 버전'뿐만 아니라, '아직 작동하고 있는 것'까지 봅니다.
이전의 실패를 다음에도 떠올릴 수 있는 처리 과정으로 바꿨습니다.
의존성 점검에 대한 자세한 이야기는 여기에서 작성했습니다.
하지만, 알림만으로는 나의 이해가 깊어지지 않았다
여기까지 오면서, 잊지 않고 확인하는 시스템은 갖춰졌습니다.
다만, 알림의 의미를 모른 채로만 있으면, 'AI가 위험하다고 했으니 AI에게 고쳐달라'는 것으로 끝나버립니다.
그렇게 해서는 스스로 판단할 수 있는 범위가 늘어나지 않습니다.
무엇이 일어나고 있는지, 어디를 보면 자신과 관련 있다고 알 수 있는지. 그 확인만으로는 무엇까지 알 수 없는 것인지.
자신의 말로 설명할 수 있게 되고 싶었습니다.
그것을 위해 만든 것이 AI Security Playbook입니다.
비엔지니어(Non-engineer)를 위한 것은, 용어를 줄이는 것만이 아니었다
글은 독자가 헷갈릴 만한 순서대로 구성했습니다.
| 독자의 의문점 | 준비할 내용 |
|---|
| 내가 읽을 필요가 있는가? | 대상・상황・얻게 되는 판단 |
| ... | |
| 명령어를 놓기만 해서는, 어디서 실행하는지 알 수 없습니다. | |
숫자가 나와도, 그것이 손안의 PC 버전인지, 공개된 앱의 버전인지 알 수 없습니다.
따라서 실행하는 장소와 결과의 의미까지 세트로 묶는다. 에러가 발생했다고 해서 '사용하지 않는다'고 판단하지 않는다는 내용도 기록한다.
비엔지니어(non-engineer)를 대상으로 한다는 것이 기술적인 조건을 생략한다는 것을 의미하는 것은 아니다.
조건은 남겨두되, 확인 순서를 따라갈 수 있도록 하는 것이 목적이었다.
구조는 다이어그램으로, 차이점은 표로, 확인 항목은 목록(bullet point)으로 만들었다. 검은 고양이 안내원 역할도 배치했다. 무서운 뉴스를 접했던 사람이 조금 안정을 찾고 읽기 시작할 수 있는 공간을 만들고 싶었다.
Tailworm에서는 'SSH 사용' 여부를 분리해 보았습니다
최근에 추가한 글이 이 예가 됩니다.
시작점은 "Tailscale로 SSH 연결을 했다면, 나도 대상인가"라는 의문입니다.
여기서 네트워크로서 Tailscale을 사용하는 것과 Tailscale SSH의 서버 기능을 사용하는 것을 분리했습니다. 확인해야 하는 것은 연결되는 쪽(수신 측)입니다. 연결 원본 앱 이름이나 지정된 단말명만으로는 알 수 없습니다.
기사에는 설정 확인 방법과 결과 예시를 배치했습니다. 다만, 하나의 설정값만으로 다른 기능까지 문제가 없다고 단정하지는 않습니다.
연구자의 지적 사항과 개발사가 확인 및 안내한 범위를 분리합니다.
뉴스 요약을 전달하는 것보다 독자가 자신의 환경을 구분할 수 있게 하는 것을 우선했습니다.
점검은 AI에게. 공개 내용은 마지막에 확인하기
정기 처리에는 두 가지 역할이 있습니다. 자신의 환경을 유지 보수하는 점검과, 공개된 정보를 학습으로 연결시키는 점검입니다.
신규 내용이 생겨도 하나의 기사로 만들지는 않습니다.
- 후속 보도가면 기존 기사에 추가합니다.
- 같은 설명이 있다면, 기존 교재로 연결합니다.
- 내부 대응만이라면 공개 기사로 삼지 않습니다.
- 근거가 부족하면 보류합니다.
일일 보고서(日報)를 저장하는 것을 자동화하는 것과, 내용이 정확하다고 인정하는 것은 다릅니다.
획득한 문서는 미확인 자료로 취급합니다. 문서 내의 지시 사항을 실행하지 않고, 내부 환경 기록을 그대로 공개하지 않습니다.
마지막으로 보는 것은 글의 오탈자만이 아닙니다.
| 확인해야 할 것 | 피해야 할 오류 |
|---|
| 1차 자료와 조건 | 제품명만으로 전 사용자에게 영향을 단정하는 것 |
| ... | |
보이지 않는 부분도 남겨두기
구조를 만들다 보면, "이것으로 괜찮다"고 쓰고 싶어집니다.
하지만 그 부분은 자체 제작한 가드(guard)에서도 실패했습니다.
이번 점검 역시 설정 지정과 실제 운영 환경의 측정을 모두 대조하고 있는 것은 아닙니다. 모델 제공 종료나 품질 변화 역시 의존 패키지와는 별도로 확인이 필요합니다.
획득에 실패했을 때의 처리에도 개선할 여지가 있습니다. 리포지토리(repository) 단위의 에러는 남기면서도, 최신 버전이 불분명해지는 등 일부 실패가 전체 중단을 유발하지 않는 경로도 존재합니다.
에러를 통보하는 메커니즘이 있다는 것과, 획득 누락을 완벽하게 감지할 수 있는 것은 별개입니다.
AI 리뷰도 마찬가지로, 반복한다고 해서 반드시 발견된다고 생각할 수는 없습니다. 그 부분은 다른 기사에서 다루었습니다.
알고 있는 것과 보이지 않는 것을 나누어 남겨두는 것까지가 현재의 대책입니다.
모두 기억하기보다, 잊어도 확인할 수 있게 하기
처음부터 거대한 구조를 만들지 않아도, 하나의 앱으로 확인할 수 있습니다.
- 의존 정의(dependency definition)・lockfile・런타임 지정은 어디에 남아 있는가?
- 실제로 작동하는 환경을 어떻게 확인할 것인가?
- 업데이트 정보를 볼 담당자와 시기는 정해져 있는가?
- 업데이트를 미룬 이유와 재검토 날짜는 남겨져 있는가?
- 대응 후의 버전과 동작을 무엇으로 확인할 것인가?
- 확인할 수 없었던 것을, 미확인 상태로 남길 수 있는가?
"모두 기억하는가"가 아니라, "잊어도 확인할 수 있는가".
AI로 만들 수 있는 것이 늘어난 만큼, 만든 후에 무엇을 확인해야 하는지를 잊지 않도록 하고 싶었습니다.
점검과 정리는 AI의 도움을 받습니다. 스스로 이해하고 다시 검토한 후, 무엇을 전달할지는 마지막에 확인합니다.
이 Playbook은 그 확인을 계속하기 위한 장소로 가꾸고 있습니다.
본문의 구현 및 운영 상황은 2026년 10월 27일 기준입니다. 우선순위나 프로세스 중단 조건은 필자의 환경에 맞춰져 있으며, 모든 환경에 그대로 적용할 것을 권장하는 것은 아닙니다.
Discussion