우리는 매일 코드를 실행한다 (하지만 그렇게 생각하지 않는다)
개발자는 하루에도 수십 번씩 특정 명령을 반복한다. npm install, pip install, gradle build 같은 명령들은 더 이상 의식적인 판단의 대상이 아니다. 마치 저장 버튼을 누르듯이, 혹은 터미널에서 디렉토리를 이동하듯이 자연스럽게 수행된다. 대부분의 개발자에게 이 행위는 단순한 준비 과정일 뿐이며, 실제 개발은 그 이후에 시작된다고 생각한다. 하지만 이 전제가 이미 어긋나 있다. 우리가 개발을 시작하기 전에 수행하는 이 명령들이야말로 가장 위험한 실행 지점이기 때문이다. 우리는 코드를 작성하기 전에 이미 수많은 외부 코드를 실행하고 있다.
과거를 떠올려보면 이 변화는 더 명확해진다. 예전의 개발은 상대적으로 폐쇄적이었다. 내가 작성한 코드, 혹은 팀 내부에서 검증된 코드가 대부분이었고, 외부 코드는 제한적으로만 사용됐다. 라이브러리를 가져다 쓴다고 해도 그 수는 제한적이었고, 코드 전체를 이해하는 것이 불가능한 수준은 아니었다. 하지만 지금은 상황이 완전히 다르다. 현대의 애플리케이션은 수십 개, 많게는 수백 개의 외부 라이브러리 위에 구축된다. 직접 작성한 코드보다 외부 코드가 훨씬 많은 구조가 일반적이다. 그럼에도 우리는 여전히 스스로를 “코드를 작성하는 사람”이라고 정의한다.
이 지점에서 인식의 괴리가 발생한다. 우리는 개발을 하고 있다고 생각하지만, 실제로는 외부 코드를 조합하고 실행하는 시스템을 운영하고 있다. 더 중요한 것은 이 과정이 대부분 자동화되어 있고, 그 내부에서 어떤 일이 일어나는지 깊이 들여다보지 않는다는 점이다. 우리는 설치 명령을 입력하는 순간, 이미 수많은 코드가 우리의 시스템에서 실행된다는 사실을 의식하지 않는다. 이 무의식적인 실행이 바로 현대 개발의 출발점이다. 그리고 이 출발점이 공격자에게는 가장 효율적인 침투 지점이 된다.
이제 질문을 바꿔야 한다. 우리는 언제부터 코드를 작성하기 전에 코드를 실행하는 습관을 가지게 되었을까. 그리고 왜 이 행위를 의심하지 않게 되었을까. 이 질문에 대한 답을 찾기 위해서는, 우리가 사용하는 “설치”라는 단어가 실제로 무엇을 의미하는지부터 다시 들여다볼 필요가 있다.
설치라는 단어가 숨기고 있는 것
“install”이라는 단어는 지나치게 단순하다. 이 단어는 마치 무언가를 다운로드해서 특정 위치에 저장하는 행위를 연상시킨다. 하지만 실제 패키지 매니저가 수행하는 작업은 그보다 훨씬 복잡하고, 더 중요한 의미를 가진다. npm이나 pip, gradle 같은 도구들은 단순히 파일을 가져오는 역할만 하지 않는다. 이들은 의존성을 해석하고, 필요한 패키지를 재귀적으로 다운로드하며, 설치 과정에서 정의된 스크립트를 실행하고, 시스템 환경에 맞게 코드를 구성한다. 이 모든 과정이 “install”이라는 한 단어로 묶여 있을 뿐이다.
특히 중요한 부분은 설치 과정에서 실행되는 스크립트다. 많은 패키지들은 postinstall, preinstall과 같은 훅을 통해 추가 작업을 수행한다. 이 작업은 단순한 설정을 넘어서, 실제 코드 실행을 포함할 수 있다. 즉, 개발자가 의도하지 않았더라도, 특정 패키지를 설치하는 순간 그 패키지의 코드가 자동으로 실행될 수 있는 구조가 이미 존재한다. 이 구조는 편의성을 위해 설계된 것이지만, 동시에 공격자가 활용할 수 있는 매우 강력한 실행 경로이기도 하다.
이 과정을 조금 더 구체적으로 들여다보면, 설치는 단순한 다운로드가 아니라 하나의 파이프라인에 가깝다. 코드 다운로드, 의존성 확장, 스크립트 실행, 환경 구성이라는 단계가 순차적으로 진행되며, 그 중 어느 한 지점에서도 외부 코드가 실행될 수 있다. 이 파이프라인은 개발자가 직접 작성하지 않은 코드로 구성되어 있으며, 대부분의 경우 그 내부를 검증하지 않는다. 우리는 이 전체 과정을 하나의 명령으로 추상화하고, 그 결과만을 받아들인다.
문제는 이 추상화가 지나치게 많은 것을 가리고 있다는 점이다. 개발자는 설치가 무엇을 하는지 정확히 이해하지 못한 채, 결과만을 신뢰한다. 이 신뢰는 코드 자체에 대한 신뢰가 아니라, 과정에 대한 무조건적인 신뢰다. 그리고 이 신뢰는 한 번 깨지기 시작하면 전체 시스템에 영향을 미친다. 실제로 최근의 공급망 공격 사례들은 바로 이 지점을 정확하게 파고들고 있다.
이제 설치라는 행위를 다시 정의할 필요가 있다. 설치는 더 이상 안전한 준비 과정이 아니라, 외부 코드가 시스템에 진입하고 실행되는 첫 번째 단계다. 이 인식이 없다면, 이후에 발생하는 문제들을 이해하기 어렵다. 그리고 이 구조가 실제로 어떻게 공격에 활용되는지를 보여주는 사례가 바로 Axios 사건이다.
Axios 사건 — 우리가 의존하는 경로가 공격당한 순간
Axios 사건은 겉으로 보면 단순한 패키지 오염 사건처럼 보일 수 있다. 하지만 이 사건의 핵심은 코드 자체가 아니라, 코드가 전달되는 경로에 있다. 공격자는 복잡한 취약점을 이용하지 않았다. 대신 패키지를 배포하는 권한을 확보했고, 정상적인 코드에 악성 코드를 일부 삽입한 뒤 그대로 배포했다. 기존 기능은 유지되었고, 대부분의 사용자는 아무런 이상을 느끼지 못한 채 패키지를 설치했다. 문제는 그 설치 과정에서 이미 공격이 완료된다는 점이다.
이 사건의 흐름을 따라가 보면 구조가 명확하게 드러난다. 먼저 패키지의 publish 권한이 공격자에게 넘어간다. 그 다음, 기존 코드에 최소한의 변경을 가해 악성 로직을 삽입한다. 이후 이 패키지는 정상적인 경로를 통해 배포된다. 개발자는 평소와 다름없이 해당 패키지를 설치하고, 그 과정에서 악성 코드가 실행된다. 이 모든 과정은 기존의 개발 흐름을 전혀 벗어나지 않는다. 오히려 너무 자연스럽기 때문에 의심하기 어렵다.
여기서 중요한 것은 이 공격이 어떤 취약점을 이용한 것이 아니라는 점이다. 코드의 버그를 악용하거나, 시스템의 보안 설정을 우회한 것이 아니다. 단지 정상적인 배포와 설치 과정을 그대로 활용했을 뿐이다. 다시 말해, 우리가 신뢰하고 있는 개발 파이프라인 자체가 공격 경로로 전환된 것이다. 이 지점에서 기존의 보안 개념은 거의 무력화된다. 코드 리뷰도, 테스트도, 런타임 모니터링도 이 문제를 막아주지 못한다.
이 사건은 하나의 패키지에 국한된 문제가 아니다. Axios는 단지 사례일 뿐이며, 동일한 구조는 다른 모든 패키지에도 적용될 수 있다. 우리가 사용하는 수많은 라이브러리들은 동일한 배포 방식과 설치 구조를 공유하고 있다. 따라서 이 사건은 특정 라이브러리의 문제가 아니라, 우리가 의존하고 있는 전체 생태계의 구조적 문제를 드러낸다. 그리고 이 구조는 이미 광범위하게 사용되고 있다.
결국 Axios 사건은 하나의 질문으로 귀결된다. 우리는 지금 무엇을 신뢰하고 있는가. 코드인가, 배포자 인가, 아니면 단순히 익숙함인가. 이 질문에 대한 답이 명확하지 않다면, 우리는 이미 공격 경로 위에서 개발하고 있는 것과 다르지 않다.
공격은 서버가 아니라 개발 환경에서 시작된다
전통적인 보안 관점에서 공격은 항상 서버를 향해 있었다. 외부에서 내부로 침투하고, 네트워크를 통과해 취약점을 찾고, 데이터베이스나 애플리케이션을 장악하는 흐름이 일반적이었다. 그래서 대부분의 보안 설계는 서버를 중심으로 구성되어 왔다. 방화벽, WAF, 인증 시스템, 접근 제어 같은 것들이 그 대표적인 예다. 하지만 Axios 사건과 같은 공급망 공격은 이 전제를 완전히 뒤집는다. 공격은 더 이상 서버를 향하지 않는다. 대신 개발 환경에서 시작된다.
이 변화는 단순한 타겟 변경이 아니라 공격 전략 자체의 전환을 의미한다. 공격자는 더 이상 방어가 잘 되어 있는 서버를 직접 공략할 필요가 없다. 대신 개발자가 사용하는 환경, 즉 상대적으로 통제가 느슨한 지점을 선택한다. 개발자 PC, 로컬 환경, 그리고 CI/CD 파이프라인은 대부분 서버에 비해 보안 정책이 약하다. 여기에 외부 코드가 자동으로 실행되는 구조가 결합되면, 공격자는 훨씬 낮은 비용으로 더 큰 효과를 얻을 수 있다. 이 지점에서 공격은 “침투”가 아니라 “유입”에 가까워진다.

개발 환경에서 시작된 공격은 이후 자연스럽게 확장된다. 개발자의 로컬 환경에서 실행된 악성 코드는 인증 정보나 토큰을 탈취할 수 있다. 이 정보는 Git 저장소나 클라우드 서비스 접근 권한으로 이어진다. 그리고 CI/CD 파이프라인을 통해 빌드된 결과물은 아무런 의심 없이 production 환경에 배포된다. 이 과정에서 공격자는 별도의 침투 과정 없이도 시스템 내부 깊숙이 들어갈 수 있다. 이 흐름은 너무 자연스럽기 때문에, 공격이 발생했는지조차 인지하지 못하는 경우가 많다.
결국 중요한 것은 공격의 출발점이 바뀌었다는 사실이다. 더 이상 서버만 보호해서는 충분하지 않다. 개발 환경 자체가 공격 표면이 되었기 때문이다. 그리고 이 환경은 우리가 매일 사용하는 도구와 명령 위에 구축되어 있다. 즉, 우리가 일상적으로 수행하는 개발 행위 자체가 공격 경로의 일부가 될 수 있다는 의미다. 이 인식을 받아들이지 않는다면, 이후에 설명할 신뢰 문제를 이해하기 어렵다.
신뢰는 어디에 존재하는가
개발자는 다양한 방식으로 신뢰를 형성한다. 어떤 패키지를 선택할 때 다운로드 수를 보고, GitHub 스타 수를 확인하고, 커뮤니티에서 많이 사용되는지를 기준으로 판단한다. 이 과정은 합리적으로 보인다. 많은 사람들이 사용하는 라이브러리는 검증되었을 것이라는 기대가 있기 때문이다. 하지만 이 신뢰는 실제 보안과는 거의 연결되지 않는다. 단지 “많이 사용된다”는 사실이 “안전하다”는 결론으로 이어질 뿐이다.
이 지점에서 신뢰의 본질을 다시 생각해볼 필요가 있다. 우리는 코드 자체를 검증하지 않는다. maintainer를 직접 확인하지도 않는다. 플랫폼이 제공하는 기본적인 구조를 신뢰할 뿐이다. 그리고 그 신뢰는 대부분 경험에 기반한다. 문제가 발생하지 않았던 경험이 쌓이면서, 점점 더 의심하지 않게 된다. 하지만 Axios 사건은 이 신뢰가 얼마나 쉽게 무너질 수 있는지를 보여준다. 단 하나의 계정이 탈취되는 것만으로도 전체 신뢰 체인이 붕괴될 수 있기 때문이다.
문제는 이 신뢰가 어디에도 명확하게 존재하지 않는다는 점이다. 코드에 있는 것도 아니고, 플랫폼에 있는 것도 아니며, 특정 개인에게 귀속되는 것도 아니다. 그저 우리가 그렇게 믿고 있을 뿐이다. 이 상태에서 신뢰는 객관적인 기준이 아니라 심리적인 안정감에 가깝다. 그리고 이 안정감은 실제 공격 상황에서는 아무런 방어 역할을 하지 못한다. 오히려 공격자가 가장 쉽게 이용할 수 있는 요소가 된다.
이러한 신뢰 구조는 개발 속도를 높이는 데에는 기여했지만, 동시에 위험을 누적시켰다. 우리는 빠르게 개발할 수 있게 되었지만, 그만큼 통제력을 잃었다. 그리고 이 통제력 상실은 의존성 구조와 결합되면서 더 큰 문제를 만들어낸다. 이제는 단순히 어떤 패키지를 선택하는 문제가 아니라, 우리가 전혀 인지하지 못하는 코드까지 실행하는 상황이 되었기 때문이다.
우리가 통제하지 못하는 코드의 양은 이미 임계점을 넘었다
현대 소프트웨어는 의존성 위에 구축된다. 하나의 프로젝트는 수십 개의 직접 의존성과, 그 아래에 연결된 수백 개의 간접 의존성을 포함한다. 이 구조는 트리 형태로 확장되며, 개발자가 직접 작성한 코드보다 외부 코드가 훨씬 많은 비중을 차지하게 된다. 문제는 이 모든 코드를 개발자가 이해하거나 검증하는 것이 사실상 불가능하다는 점이다. 우리는 단지 최상위 의존성 몇 개만을 인식할 뿐, 그 아래에 무엇이 포함되어 있는지는 거의 알지 못한다.
이 구조는 단순히 복잡한 수준을 넘어서, 통제 불가능한 영역으로 확장되었다. 하나의 라이브러리를 추가하면 그 아래에 수십 개의 패키지가 자동으로 포함된다. 그리고 이 패키지들 각각이 또 다른 의존성을 가진다. 이 과정은 재귀적으로 반복되며, 최종적으로 어떤 코드가 실행되는지 정확히 파악하기 어렵게 만든다. 우리는 이 전체 구조를 하나의 명령으로 생성하고, 그 결과를 그대로 받아들인다.
여기서 중요한 것은 “통제”라는 개념이다. 개발자는 자신이 작성한 코드에 대해서는 책임을 질 수 있다. 하지만 의존성 체인 전체에 대해서는 책임을 지기 어렵다. 그럼에도 불구하고, 이 코드들은 동일한 권한으로 실행된다. 즉, 우리가 이해하지 못하는 코드가 우리가 작성한 코드와 동일한 수준의 권한을 가진 채 시스템에서 실행되고 있는 것이다. 이 상태는 구조적으로 매우 취약하다.
이러한 상황에서 공격자는 특정 지점을 선택하기만 하면 된다. 전체를 장악할 필요가 없다. 단 하나의 패키지, 그것도 깊은 의존성 트리의 일부만 조작해도 충분하다. 그 패키지가 설치되는 순간, 공격 코드는 실행된다. 그리고 우리는 그 경로를 추적하기 어렵다. 이 지점에서 의존성 구조는 단순한 편의 기능이 아니라, 공격 확산의 매개체가 된다.
결국 우리는 이미 임계점을 넘어선 상태에 있다. 더 이상 모든 코드를 이해하거나 통제하는 것은 현실적으로 불가능하다. 그럼에도 불구하고 우리는 여전히 이 구조 위에서 개발을 계속한다. 그리고 이 모순이 다음 단계에서 설명할 구조적 필연성으로 이어진다.
공급망 공격이 성공하는 이유
앞에서 살펴본 구조를 하나로 묶어 보면, 공급망 공격은 우연히 성공하는 것이 아니라 구조적으로 성공할 수밖에 없는 환경 위에서 동작한다는 사실이 드러난다. 많은 개발자는 이런 사건을 예외적인 보안 사고로 인식하려고 하지만, 실제로는 그 반대에 가깝다. 현재의 개발 생태계는 공격자가 활용할 수 있는 경로를 기본적으로 포함하고 있다. 그리고 그 경로는 우리가 매일 사용하는 도구와 프로세스 안에 자연스럽게 녹아 있다. 즉, 공격은 특별한 조건에서만 가능한 것이 아니라, 이미 준비된 흐름 위에 올라타기만 하면 되는 형태다.
이 구조에서 가장 핵심적인 요소는 “자동 실행”이다. 패키지를 설치하는 순간 특정 스크립트가 실행될 수 있고, 이 실행은 별도의 확인 과정 없이 진행된다. 개발자는 이 과정을 편의 기능으로 받아들이지만, 공격자 입장에서는 코드 실행 권한을 얻는 가장 간단한 방법이다. 여기에 더해 중앙에서 검증하는 체계가 거의 존재하지 않는다는 점도 중요한 요소다. 패키지는 publish 되는 순간 전 세계로 배포되고, 그 내부에 어떤 코드가 포함되어 있는지는 기본적으로 작성자의 책임으로 남는다. 이 구조에서는 계정 하나가 탈취되는 것만으로도 전체 생태계에 영향을 줄 수 있다.

또 다른 중요한 요소는 업데이트와 의존성 관리 방식이다. 많은 프로젝트는 최신 버전을 자동으로 받아오도록 설정되어 있고, 이 과정에서 새로운 코드가 검증 없이 시스템에 유입된다. 이 자동화는 개발 속도를 높이는 데에는 기여하지만, 동시에 공격 코드가 빠르게 확산될 수 있는 경로를 제공한다. 공격자는 특정 시점에 악성 코드를 포함한 버전을 배포하기만 하면, 별도의 개입 없이 수많은 환경에 코드를 실행시킬 수 있다. 이 과정은 매우 조용하게 이루어지며, 대부분의 경우 문제가 발생한 이후에야 인지된다.
결국 공급망 공격은 복잡한 기술적 트릭의 결과가 아니다. 오히려 단순한 구조 위에서 작동한다. 코드가 자동으로 실행되고, 검증이 부족하며, 배포가 빠르고, 의존성이 깊은 환경에서는 공격이 성공하는 것이 자연스러운 결과다. 이 지점에서 우리는 한 가지를 분명히 인식해야 한다. 이 문제는 특정 취약점을 수정한다고 해결되는 것이 아니라, 전체 구조를 다시 바라보지 않으면 반복될 수밖에 없다는 점이다.
이건 보안 문제가 아니라 개발 패러다임의 문제다
많은 조직은 이런 사건을 보안 이슈로 분류하고, 보안 도구나 정책을 강화하는 방식으로 대응하려 한다. 물론 이런 대응은 일정 부분 필요하다. 하지만 근본적인 문제를 해결하지는 못한다. 왜냐하면 이 문제는 특정 보안 취약점에서 발생한 것이 아니라, 개발 방식 자체에서 비롯된 것이기 때문이다. 우리가 코드를 작성하고 실행하는 방식이 바뀌면서, 그 과정에 새로운 위험이 포함되었고, 이 위험은 기존의 보안 접근 방식으로는 완전히 통제할 수 없다.
과거의 개발은 상대적으로 단순한 구조를 가지고 있었다. 코드의 대부분이 내부에서 작성되었고, 외부 코드의 비중은 제한적이었다. 따라서 보안은 주로 애플리케이션 내부나 네트워크 경계에서 이루어졌다. 하지만 현재의 개발은 외부 코드에 크게 의존한다. 개발자는 코드를 직접 작성하기보다, 이미 존재하는 코드를 조합하고 연결하는 역할을 수행한다. 이 과정에서 코드의 출처와 실행 경로는 점점 더 복잡해지고, 그만큼 통제하기 어려워진다.
이 변화는 단순한 기술적 진화가 아니라 패러다임의 전환이다. 우리는 더 이상 “코드를 작성하는 개발자”가 아니라, “코드를 선택하고 실행하는 운영자”에 가까워졌다. 하지만 우리의 보안 인식은 여전히 과거에 머물러 있다. 우리는 여전히 서버를 보호하고, 입력을 검증하고, 접근을 통제하는 데 집중한다. 반면, 코드가 어떻게 유입되고 실행되는지는 상대적으로 덜 중요하게 다뤄진다. 이 불균형이 바로 공급망 공격이 반복되는 이유다.
이제 질문을 다시 설정해야 한다. 우리는 어떻게 코드를 보호할 것인가가 아니라, 어떤 코드를 실행하고 있는가를 먼저 물어야 한다. 이 질문은 개발 방식 자체를 다시 생각하게 만든다. 코드의 출처를 어떻게 검증할 것인지, 실행 권한을 어떻게 제한할 것인지, 의존성을 어떻게 관리할 것인지와 같은 문제는 더 이상 선택이 아니라 필수가 된다. 그리고 이 변화는 단순한 도구 도입으로 해결되지 않는다. 개발 문화와 프로세스 전체가 함께 바뀌어야 한다.
우리는 이미 공격 경로 안에서 개발하고 있다
지금까지의 흐름을 따라오면 하나의 결론에 도달하게 된다. 우리는 아직 공격을 당하지 않았을 뿐, 이미 공격이 가능한 구조 안에서 개발을 하고 있다는 사실이다. 이 구조는 특정 환경이나 조직에만 해당되는 것이 아니라, 현대 개발 생태계 전반에 걸쳐 동일하게 적용된다. 패키지 매니저를 사용하고, 외부 라이브러리에 의존하며, 자동화된 빌드와 배포를 사용하는 모든 환경은 동일한 조건을 공유한다. 즉, 문제는 일부가 아니라 전체에 해당한다.
이 상황에서 가장 위험한 것은 익숙함이다. 우리는 매일 같은 명령을 사용하고, 같은 방식으로 코드를 가져오고, 같은 흐름으로 빌드와 배포를 진행한다. 이 반복되는 과정은 안정감을 제공하지만, 동시에 경계심을 낮춘다. 그리고 이 낮아진 경계심이 바로 공격자가 의존하는 요소다. 공격자는 새로운 경로를 만들 필요가 없다. 이미 존재하는 흐름을 그대로 활용하면 된다. 이 단순함이 공급망 공격의 가장 강력한 특징이다.

이제 우리는 개발자를 다시 정의해야 한다. 개발자는 더 이상 단순히 코드를 작성하는 사람이 아니다. 외부 코드가 시스템에 유입되고 실행되는 흐름의 일부다. 이 흐름 안에서 개발자는 시작점이자 중간 경로이며, 때로는 최종 실행 지점이 된다. 이 역할을 인식하지 못하면, 우리는 계속해서 동일한 위험을 반복하게 된다. 그리고 그 위험은 점점 더 커질 것이다.
이 지점에서 중요한 것은 공포를 느끼는 것이 아니라, 현실을 정확히 이해하는 것이다. 우리는 이미 공격 경로 안에 있다. 이 사실을 인정하는 순간, 비로소 다음 단계로 넘어갈 수 있다. 어떤 코드를 신뢰할 것인지, 어떻게 실행을 제한할 것인지, 그리고 어떤 방식으로 개발을 지속할 것인지에 대한 질문은 이제 선택이 아니라 필수가 된다. 이 질문에 대한 답이 다음 단계의 방향을 결정하게 된다.
이제 무엇을 바꿔야 하는가
여기까지의 흐름을 따라오면, 문제를 단순히 인지하는 단계에서 멈출 수는 없다. 우리는 이미 공격 경로 안에서 개발하고 있다는 사실을 확인했다. 그리고 이 구조는 쉽게 바뀌지 않는다. 그렇다면 현실적으로 바꿀 수 있는 것은 무엇인가. 많은 사람들이 이 지점에서 특정 도구나 솔루션을 찾으려고 한다. 하지만 이 문제는 도구의 문제가 아니다. 어떤 보안 스캐너를 쓰느냐, 어떤 정책을 추가하느냐의 문제가 아니라, 개발자가 코드를 대하는 방식 자체를 바꾸는 문제다.
가장 먼저 바뀌어야 하는 것은 신뢰에 대한 태도다. 우리는 지금까지 외부 코드를 기본적으로 신뢰하는 방향으로 개발해왔다. 인기 있는 패키지, 많은 다운로드 수, 활발한 커뮤니티를 기준으로 판단하고, 그 위에서 의심 없이 코드를 실행해왔다. 하지만 이제 이 기준은 더 이상 충분하지 않다. 외부 코드는 기본적으로 신뢰하는 대상이 아니라, 검증이 필요한 대상이 되어야 한다. 이 변화는 생각보다 크다. 왜냐하면 개발 속도를 희생해야 하는 순간이 반드시 발생하기 때문이다. 하지만 이 선택을 하지 않으면, 우리는 계속해서 동일한 위험 위에 시스템을 쌓게 된다.

다음으로 고려해야 할 것은 실행에 대한 통제다. 우리는 지금까지 코드 실행을 너무 쉽게 허용해왔다. 설치 과정에서 자동으로 실행되는 스크립트, 빌드 과정에서 수행되는 다양한 작업들은 대부분 별도의 제한 없이 동작한다. 하지만 이 구조에서는 어떤 코드가 실행되는지, 어떤 권한으로 실행되는지에 대한 통제가 거의 이루어지지 않는다. 이제는 실행 자체를 제한하는 방향으로 사고를 전환해야 한다. 모든 코드를 동일한 권한으로 실행하는 것이 아니라, 필요한 최소한의 권한만을 부여하고, 실행 환경을 분리하는 방식이 필요하다. 이 접근은 불편함을 동반하지만, 그 불편함이 바로 통제의 시작이다.
또 하나 중요한 변화는 의존성에 대한 태도다. 지금까지 우리는 의존성을 “편리함”의 관점에서만 바라봤다. 더 빠르게 개발할 수 있고, 이미 검증된 기능을 재사용할 수 있다는 점에서 의존성은 긍정적인 요소로 인식되었다. 하지만 이제는 의존성을 “리스크”의 관점에서도 함께 봐야 한다. 하나의 라이브러리를 추가하는 것이 단순한 기능 확장이 아니라, 새로운 코드 실행 경로를 시스템에 추가하는 행위라는 점을 인식해야 한다. 이 인식이 없다면, 우리는 계속해서 통제할 수 없는 코드의 양을 늘리게 된다.
이러한 변화는 기술적인 조치만으로는 완성되지 않는다. 개발 문화와 프로세스가 함께 바뀌어야 한다. 코드 리뷰의 범위는 내부 코드에만 머무르지 않아야 하고, 빌드와 배포 과정은 단순한 자동화가 아니라 검증 가능한 흐름으로 재구성되어야 한다. 무엇보다 중요한 것은, 개발자가 자신의 환경이 공격 경로의 일부가 될 수 있다는 사실을 항상 인식하는 것이다. 이 인식이 있어야만, 이후의 모든 선택이 달라진다.
결국 우리가 바꿔야 하는 것은 하나로 수렴된다. 우리는 더 이상 “편리하게 개발하는 방법”만을 기준으로 선택할 수 없다. 그 선택이 어떤 실행을 유발하는지, 어떤 경로를 열어주는지, 그리고 그 결과가 어디까지 확장될 수 있는지를 함께 고려해야 한다. 이 기준이 없다면, 어떤 도구를 도입하더라도 근본적인 문제는 해결되지 않는다.
이 글의 시작에서 우리는 단순한 명령 하나를 다시 보았다. npm install이라는 익숙한 명령은 이제 더 이상 단순한 설치가 아니다. 그것은 외부 코드가 우리의 시스템에 들어오고, 실행되고, 확산되는 첫 번째 단계다. 그리고 우리는 그 과정을 너무 오랫동안 당연하게 받아들여 왔다. 이제는 그 당연함을 의심해야 한다. 그 의심이 바로 다음 단계로 나아가기 위한 출발점이기 때문이다.