아이디어와 인사이트

AI 퍼스트 개발, 코딩 가드레일, 그리고 폐기 가능한 아키텍처를 탐구합니다.

의존성이 환경 변수를 읽을 수 있다. JavaScript는 이를 허용한다

Ambient Authority란 Node.js 프로세스 내의 모든 코드가 파일 시스템, 네트워크, 환경에 접근할 수 있는 상태를 말한다. 여기서는 lockdown, compartment, 명시적인 capability 전달을 통해 이를 제거하는 방법을 설명한다.

하나의 손상된 전이 의존성이 당신의 를 외부로 유출하고, 파일 시스템에 쓰며, 외부 연결을 열 수 있다. 이를 위해 당신의 코드에 취약점이 있을 필요는 없다. 단지 같은 프로세스 안에 존재하기만 하면 된다. JavaScript에서는 기본적으로 모든 모듈이 런타임의 전체 권한을…

네트워크 근접성은 신원이 아니다: 서비스가 주변 권한 없이 인증하는 방법

대부분의 서비스 간 신뢰는 암호학적 증명이 아닌 네트워크 위치에서 비롯된다. 다음은 권능, mTLS, SPIFFE를 통해 서비스가 주변 권한 없이 인증하는 방법과 그것이 예상보다 어려운 이유다.

당신의 마이크로서비스는 VPC를 공유하므로 서로를 신뢰한다. 이 신뢰는 주변 권한이다. 서비스를 호출할 수 있는 권한은 암호학적 증명이 아닌 네트워크 토폴로지에 의해 부여된다. 경계를 침범한 공격자는 이 모든 권한을 그대로 물려받는다. 서비스는 주변 권한 없이도 충분히 인증할 수…

의존성은 디스크의 어떤 파일이든 읽을 수 있다. cap-std는 접근 권한을 요청하게 만든다.

Rust 표준 라이브러리는 모든 의존성에 주변 파일 시스템 권한을 부여한다. cap-std는 이를 기능 기반 API로 대체하여 코드가 파일을 열기 전에 해당 경로에 접근할 권리가 있음을 증명하도록 강제한다.

의존성 트리의 모든 크레이트는 를 열거나 디렉터리에 쓰거나, 프로젝트의 모든 파일을 열거할 수 있다. Rust 표준 라이브러리는 접근 권한을 묻지 않는다. 을 호출할 수 있는 코드라면 운영체제가 허용하는 어떤 경로든 접근할 수 있다고 가정한다. cap-std는 이러한 가정을 뒤집는다.…

당신의 서비스는 눈치 채지 못한 공범이다: Confused Deputy 공격의 작동 원리

Confused Deputy 공격은 특권을 가진 서비스를 속여 자신의 권한을 스스로 해치는 데 이용한다. 공격자가 암묵적 신뢰를 어떻게 악용하는지, 그리고 능력 기반 보안이 왜 해결책인지 알아본다.

당신의 서비스에는 클라우드 스토리지에 접근할 수 있는 API 키가 있다. 사용자가 요청을 본다. 서비스는 충실하게 파일을 작성한다. 그 파일은 당신의 버킷이 아닌 공격자의 버킷에 저장되고, 이제 공격자는 당신의 데이터를 손에 넣는다. 당신은 방금 눈치 채지 못한 공범이 되었다. 이것이…

당신의 권한 검사는 거짓말을 하고 있다

능력 기반 보안은 흩어져 있는 역할 검사를 위조할 수 없는 권한 토큰으로 대체합니다. 코드를 읽기 어렵게 만들지 않고 TypeScript에서 이를 구현하는 방법을 소개합니다.

모든 권한 버그는 돌이켜볼 때 똑같이 보인다. 호출 스택 깊숙한 어딘가에 있는 함수가 호출자가 이미 을 검사했을 것이라고 가정한다. 그러나 그렇지 않았다. 또는 새로운 역할이 추가되면, 47개 파일 전체에서 를 grep하며 하나라도 놓치지 않았는지 바라며 뒤진다. 능력 기반 보안은…

만약 모든 함수가 권한을 가정하지 않고 명시적으로 전달받아야 한다면?

대부분의 코드는 주변 권한(ambient authority) 하에서 실행된다: 모든 함수가 모든 것에 접근할 수 있다. 기능 기반 보안(capability-based security)은 함수가 명시적이고 범위가 제한된 권한을 전달받도록 강제함으로써 이 모델을 뒤집는다.

함수는 같은 프로세스 안에 존재한다는 이유만으로 데이터베이스, 결제 게이트웨이, 감사 로그에 모두 접근할 수 있다. 공격자가 하나의 HTTP 핸들러에서 인젝션 버그를 발견하면, 그 모든 권한을 그대로 물려받는다. 이 함수는 이런 권한을 요청한 적 없다. 단지 배치된 위치 덕분에 마치…

타입 검사기가 볼 수 없는 에러 던지기는 이제 그만

던져진 예외는 실패 경로를 타입 시스템에서 감춥니다. 명시적인 에러 반환이 코드를 더 정직하게 만드는 이유, 그리고 삶을 싫어하지 않으면서 이를 도입하는 방법을 알아봅니다.

함수 시그니처가 를 반환한다고 합니다. 그렇지 않습니다. 를 반환하거나 폭발합니다. 타입 시스템은 두 번째 분기에 대해 전혀 모릅니다. 이것이 예외 기반 에러 핸들링의 근본적인 불성실함입니다. 모든 는 컴파일러가 볼 수도, 검사할 수도, 강제할 수도 없는 제어 흐름 경로입니다.…

Rust newtype는 잘못된 상태를 컴파일 타임에, 아무런 비용 없이 표현 불가능하게 만든다

단일 필드 래퍼 struct가 단 한 바이트의 오버헤드도 추가하지 않고 단위 혼합 버그와 타입 혼란을 잡아낸다.

인 사용자 ID를 주문 ID를 기대하는 함수에 넘기면 Rust는 불평하지 않는다. 둘 다 이기 때문이다. 컴파일러는 두 타입이 동일하게 보이므로 당신을 도울 수 없다. 버그는 런타임에, 보통 프로덕션에서, 보통 당신이 안전하다고 생각한 리팩터링 이후에야 발견된다. 이것이 바로…

Switch 문은 컴파일 시점에도 버그를 숨긴다

TypeScript switch 문은 조용히 케이스를 빠뜨리게 만든다. 빌드에서 실패하고 런타임에서는 실패하지 않는 exhaustiveness-checked 패턴으로 대체하는 방법을 알아본다.

shape 타입을 리팩토링하고 새로운 variant를 추가했는데도 TypeScript는 초록불이었다. CI를 통과했고, 배포도 나갔다. 그런데 사용자가 를 반환하는 런타임 분기를 만났고, 앱이 프로덕션에서 터져 버렸다. 범인은 거의 확실히 switch 문이었다. TypeScript의…

컴파일러가 누락된 상태 케이스를 잡아내야 한다. QA팀이 아니라

상태 머신에 새 상태를 추가하는 건 쉽다. 모든 switch 문을 업데이트하는 건 아니다. TypeScript가 케이스를 잊으면 컴파일을 거부하게 만드는 방법을 알아보자.

상태 머신은 처음엔 깔끔하다. 값 세 개, switch 가지 세 개. 그러다 재시도 로직이 네 번째 상태를 추가한다. 부분 실패가 다섯 번째를 추가한다. reducer는 업데이트했는데, 상태 배지 컴포넌트와 analytics 매퍼, 그리고 export 포맷터를 놓친다. 모든 게…