기억하세요 git blame? 언젠가 누군가가 기존 코드베이스에서 이 코드를 실행하게 될 것이고, 그러면 여러분이 몇 년 전에 내린 결정들이 드러나게 될 것입니다. 그 사람은 당혹스러워할 것입니다. 어쩌면 프로젝트가 다른 사람에게 넘겨졌을 수도 있고, 여러분이 회사를 떠났을 수도 있습니다. 어쩌면, 어쩌면 그 프로젝트가 커뮤니티에 넘겨져 그들이 개발하게 되었을 수도 있습니다. 하지만 문제는, 당신이 왜 그런 결정을 내렸는지, 또 어떤 제약 조건에 처해 있었는지 아무도 모른다는 점입니다. 여기서 제기되는 질문은 간단합니다. 왜 다른 디펜드 대신 이 디펜드를 선택했나요?
오늘 의존성에 대해 내리는 결정은 향후 이 코드베이스를 물려받을 사람에게 보내는 메시지나 다름없습니다. 하지만 그 메시지가 실제로 전달되는 경우는 거의 없습니다. 대개는 그저 package.json 다음과 같은 버전 범위를 가진 ^2.4.1 그리고 그것이 어떻게 거기에 있게 되었는지에 대한 설명은 전혀 없습니다. 바로 그 빈틈 때문에 많은 오픈소스 의존성 관리 시스템이 눈에 띄지 않게 무너지고 맙니다.
단순한 스냅샷이 아닙니다
패키지를 추가한다고 해서 정적인 아티팩트가 고정되는 것은 아닙니다. 여러분은 그 발전 과정을 계승하는 것입니다. 최신 최고의 라이브러리를 가져올 때면, 뒤에서는 수많은 요소와 사람들이 움직이고 있으며, 많은 패키지는 여러분이 들어본 적도 없는 사람들이 관리하는 전이적 의존성을 동반합니다. 설치 명령어만으로는 이러한 사실을 전혀 알 수 없습니다. 유지보수자는 자리를 떠나고, 코드도 변합니다. 패키지가 몇 달 동안 소식이 끊기더라도, CVE가 드러나고 이를 수정해 줄 사람이 아무도 없을 때까지는 아무도 눈치채지 못하는 경우가 많습니다.
락파일(lockfile)은 무엇이 설치될지 제어하는 데 도움이 됩니다. 이 파일은 버전 정보와, 레지스트리 패키지의 경우 무결성 해시 값을 포함한 해결된 트리를 기록하지만, 해당 항목들이 왜 포함되었는지에 대해서는 아무런 설명도 제공하지 않습니다. 이 ‘왜’에 대한 설명이 빠진 점은 나중에, 대개 최악의 순간에 드러나게 됩니다. 누군가 보안 패치를 배포하려다, 업데이트해야 할 종속성이 수년 동안 릴리스되지 않았고 여전히 오래전에 지원 종료(EOL)된 Node.js 버전을 대상으로 하고 있다는 사실을 발견하는 경우가 바로 그 예입니다. 좋지 않은 상황입니다.
이 코드베이스는 언젠가 다른 사람에게 넘겨주게 될 것입니다.
문맥 없이 상속됨
여러분의 코드베이스를 물려받는 사람은 여러분의 사고 과정을 그대로 이어받지는 못할 것입니다. 그들에게는 맥락이 거의 없는 단편적인 정보만 주어질 뿐입니다. 당시의 상황, 마감일, 그리고 여러분이 조사한 대안들을 고려했을 때, 여러분이 선택한 의존성은 아마도 올바른 결정이었을 것입니다. 그 당시에는 모든 것이 타당해 보였을 테니까요. 하지만 인계 과정을 무사히 넘기는 것은 또 다른 문제입니다. 그 결정의 근거를 아무도 문서화하지 않았다면, 다음 담당자는 신중한 결정과 우연한 선택을 구분할 수 없을 것입니다.
이전 버전에 고정된 종속성을 상상해 보세요. 해당 커밋 메시지에는 다음과 같이 적혀 있습니다. pin, do not bump. API의 중대한 변경인가? 라이선스 문제인가? 부하 시 발생하는 메모리 누수인가? 그 이유가 여전히 유효한지 아무도 알 수 없기 때문에, 손을 대는 것이 더 위험해 보이기 때문에 해당 핀은 그대로 남아 있다. 맥락을 모르면, 합리적인 결정조차 방치처럼 보이기 시작한다.
따라서 다음 사람은 당신의 결정을 맹목적으로 신뢰하거나, 처음부터 다시 검토해야 합니다. 두 경우 모두 비용이 많이 듭니다. 전자의 경우, 어느새 잘못된 결정이 되어버린 것을 그대로 이어갈 위험이 있습니다. 후자의 경우, 이미 도달한 결론을 다시 도출하느라 시간을 낭비하게 됩니다.
바로 이 지점에서 오픈소스 인계가 다소 애매해질 수 있습니다. 포크는 코드, 커밋 내역, 그리고 리포지토리에 포함된 문서들을 그대로 이어받습니다. 하지만 많은 의사 결정의 근거가 담겨 있던 노트에 적힌 메모나 슬랙 대화, 티켓 댓글 등은 포함하지 않습니다. 인계받는 사람은 그 맥락을 힘들게 재구성해야 하거나, 수많은 PR 속에서 그 맥락을 잃어버리게 됩니다.
업스트림에서도 작업 인계가 이루어집니다. 2018년에, event-stream, 인기 있는 npm 패키지인 이 프로젝트는 도움을 주겠다고 나선 자원봉사자에게 게시 권한을 위임했습니다. 9월 9일, npm의 사고 보고서에 따르면, 새로운 관리자가 다음과 같이 추가했습니다. flatmap-stream 다음 범위 내의 종속성으로 ^0.1.0. 10월 5일, [email protected] Copay 비트코인 지갑의 릴리스 빌드에서만 활성화되는 난독화된 코드가 포함되어 있었으며, 이 코드는 해당 빌드에서 개인 키를 탈취했습니다. 락파일(lockfile)을 통해 고정하지 않고 해당 범위를 새로 해결하는 모든 설치의 경우 0.1.0, 악성 버전을 확보했습니다. 이 파일은 11월 20일 한 개발자가 이를 신고할 때까지 그대로 남아 있었습니다.
부패는 조용하지만, 기록은 그렇지 않다
의존성 선택의 근거는 시간이 지나도 모두 똑같이 유지되는 것은 아닙니다. 어떤 것은 10년 동안이나 타당성을 유지하지만, 어떤 것은 그렇지 않습니다. 어떤 것은 거의 즉시 무너져 내리기도 합니다. 이러한 부패 현상은 대개 누군가가 직접 찾아보기 전까지는 눈에 띄지 않는데, 보통 보안 스캔이나 제품의 수명 종료(EOL), 또는 감사 과정에서 드러나게 됩니다. 소프트웨어 유지보수와 오픈소스 상속은 낡은 집을 사는 것과 매우 비슷합니다. 기초가 흔들리기 전까지는 아무도 그 기초를 살펴보지 않습니다.
npm outdated 설치된 버전을 사용자가 지정한 범위 및 최신 릴리스와 비교하지만, 다음을 추가하지 않는 한 직접적인 종속성에 대해서만 비교합니다. --all. npm view <package> time 각 버전이 언제 공개되었는지 보여줍니다. 이 중 어느 것도 ‘건강도 점수’는 아닙니다. 잘 다듬어진 라이브러리라도 수년 동안 릴리스가 없을 수 있고, 활동이 활발한 저장소라도 관리가 제대로 되지 않을 수 있기 때문입니다.
오픈소스 의존성 관리는 대개 패키지를 선택하고, 업데이트하고, 보안을 유지하는 과정으로 설명됩니다. 그 배경이 되는 논리를 명확히 하는 것 역시 이 목록에 포함되어야 합니다. 코드베이스를 원활하게 인계하는 팀들은 다음과 같은 기록을 남깁니다. 왜 특정 의존성을 선택했는지, 왜 특정 버전을 고정했는지, 그리고 해당 의존성이 지원 종료(EOL)에 도달했을 때의 대응 계획은 무엇인지에 대한 내용입니다.
자신이 통제할 수 있는 부분
모든 것을 통제할 수는 없습니다. 특히 6년 후 누가 패키지를 유지 관리할지는 더더욱 알 수 없습니다. 의존하고 있는 프로젝트가 중단되거나, 방향이 바뀌거나, 포크될지 여부도 통제할 수 없습니다. 하지만 통제할 수 있는 것은 프로세스를 얼마나 잘 문서화하느냐입니다. 결정 사항과 그 근거를 기록해 두면, 나중에 코드를 이어받게 될 사람이 매우 고마워할 것입니다.
세 가지 도구는 각각 서로 다른 질문에 답합니다. ADR(아키텍처 결정 기록)은 ‘왜’에 대한 답을 제공하며, 대개 docs/adr/ 상황, 고려된 옵션, 결정 사항 및 그 결과를 기록하는 것입니다. SBOM(소프트웨어 자재 명세서)은 ‘무엇’에 대한 답을 제공합니다. 이는 특정 빌드에 포함된 구성 요소에 대한 기계 가독형 목록으로, 일반적으로 SPDX 또는 CycloneDX 형식으로 제공되며, 생성기가 이를 포착할 경우 전이적 종속성도 포함됩니다. 출처 정보는 ‘어디서’와 ‘어떻게’에 대한 답을 제공합니다. 다음을 포함하여 공개된 패키지는 npm 출처 지원되는 클라우드 CI/CD 제공업체에서 가져온 코드에는 해당 소스 저장소 및 빌드와 연결된 인증 정보가 포함됩니다. 이는 코드의 출처를 보여줄 뿐, 코드가 안전하다는 것을 의미하지는 않습니다.
문서는 개발 의도를 설명할 뿐입니다. 패키지 자체의 신뢰성을 보증할 수는 없습니다. 공개 레지스트리는 서명, 출처 정보 지원, 악성 패키지 제거 등의 안전 장치를 제공하지만, 여전히 많은 패키지에 출처 정보가 부족하며, 이러한 통제 수단 중 어느 것도 현재 배포자가 도입 당시 신뢰했던 바로 그 배포자인지 여부를 확인해 주지 않습니다.
SecureChain은 다른 방식을 취합니다. 이 솔루션은 보안이 강화되고 격리된 환경에서 검증된 소스 코드를 바탕으로 오픈소스 패키지를 재빌드하고, 악성코드를 검사한 뒤, 엄선된 저장소를 통해 제공합니다. 패키지에는 재빌드 과정에 대한 SLSA 레벨 3 출처 정보와, 알려진 취약점 중 해당 패키지에 영향을 미치는 것, 영향을 미치지 않는 것, 그리고 수정된 사항에 대한 VEX(Vulnerability Exploitability eXchange) 데이터가 함께 제공됩니다. 패키지에 따라 구성 요소에 대한 SBOM도 포함됩니다.
결국 여러분의 코드베이스 중 일부는 유지보수 주기가 종료되는 시점에 도달하게 될 것이며, 이때 유지보수 담당자들은 더 이상 보안 패치를 제공하지 않게 됩니다. 그리고 이런 일은 여러분이 팀을 떠난 후에 일어날 가능성이 높습니다. ‘무한 수명 주기 지원(ELS) ’은 지원 대상인 수명 주기 종료 버전에 보안 패치를 역포트하므로, 이미 운영 중인 지원 대상 버전은 계속해서 패치를 받을 수 있습니다. 귀하의 코드베이스를 물려받는 팀은 패치가 적용되지 않은 상태로 운영할 것인지, 아니면 압박 속에서 긴급하게 코드를 재작성할 것인지 사이에서 선택을 강요받지 않게 됩니다. 이를 통해 팀은 당황한 상태에서 결정을 내리는 대신, 신중하게 마이그레이션에 대한 결정을 내릴 시간을 벌 수 있습니다.
최종 생각
추가하는 모든 의존성은 향후 프로젝트를 유지보수하게 될 사람에게 지게 되는 지속적인 책임입니다. 코드는 팀, 역할, 심지어 회사보다 더 오래 남습니다. 의존성을 선택한 이유를 문서화하고, 배포한 내용에 대한 검증 가능한 기록을 남기며, 상류 프로젝트의 지원이 종료되는 날을 대비해 계획을 세움으로써, 언젠가 넘겨주게 될 코드베이스가 함정이 아닌 진정한 자산이 될 수 있는 기회를 마련해 줄 수 있습니다.
