웹 개발 데일리 브리핑 — 2026-11-03
Soshy··조회수 —

기준 시점: 2026-11-03 06:00 KST
조사 범위: 전일 2026-11-02 06:00 ~ 당일 2026-11-03 06:00 KST
추천 글 범위: 기준 시점 당시 최근 7일
1️⃣ 프론트엔드
Vercel Flags, 프로젝트 간 Feature Flag 공유 지원 — SDK Key를 복사하지 않고 배포 ID로 접근 권한 관리
- 발표일: 2026-11-02 — 정확한 게시 시각 미확인
- 안정화 단계: 정식 기능
- 적용 대상: Vercel Flags, Flags SDK, 동일 Team의 Vercel 프로젝트
- 필요 버전:
@flags-sdk/vercel1.6.0 이상 또는@vercel/flags-core1.11.0 이상 - 원문: https://vercel.com/changelog/evaluate-vercel-flags-from-another-project-without-sharing-sdk-keys
Vercel이 서로 다른 프로젝트에서 같은 Feature Flag를 사용할 때 SDK Key를 복사하지 않아도 되도록 Project Connections 기능을 확장했다.
Feature Flag는 특정 기능의 활성화 여부를 코드 배포와 별개로 제어하는 도구다.
예를 들어 새로운 결제 화면을 개발했다고 가정해 보자.
개발자는 새로운 화면의 코드를 운영 환경에 배포할 수 있다. 하지만 모든 사용자에게 즉시 공개하지 않고 일부 사용자에게만 제공할 수 있다.
이러한 기능은 여러 애플리케이션에서 공유해야 할 때도 있다.
예를 들어 다음 프로젝트가 존재한다고 가정해 보자.
- 고객용 웹 애플리케이션
- 관리자 페이지
- 별도의 API 서버
세 프로젝트 모두 새로운 결제 시스템을 사용하는지 확인해야 할 수 있다.
이때 각 프로젝트가 독립적으로 Feature Flag를 관리하면 설정이 서로 달라질 가능성이 있다.
따라서 하나의 프로젝트에서 Flag를 정의하고 다른 프로젝트에서 해당 값을 조회하는 구조가 필요하다.
기존 방식: SDK Key 공유
기존에는 다른 프로젝트의 Feature Flag를 조회하기 위해 Server SDK Key를 복사해야 했다.
예를 들어 프로젝트 A에서 Feature Flag를 관리한다고 가정해 보자.
프로젝트 B가 해당 Flag를 사용하려면 A의 SDK Key를 B의 환경 변수에 등록해야 했다.
이러한 방식에서는 다음 문제가 발생할 수 있다.
첫 번째는 인증 정보의 배포 범위다.
여러 프로젝트에 동일한 Key가 저장된다. 따라서 Key를 교체하거나 폐기해야 할 때 모든 프로젝트의 설정을 확인해야 한다.
두 번째는 접근 권한 관리다.
어떤 프로젝트가 실제로 해당 Key를 사용하는지 별도로 관리해야 할 수 있다.
세 번째는 인증 정보의 회전이다.
Key가 노출되거나 권한 정책이 변경되면 관련 프로젝트의 환경 변수를 수정하고 다시 배포해야 할 수 있다.
이번 Project Connections는 이러한 관리 작업을 줄인다.
새로운 방식: Deployment Identity
새로운 구조에서는 다른 프로젝트에 SDK Key를 전달하지 않고 Vercel이 관리하는 배포의 신원을 이용해 인증한다.
예를 들어 프로젝트 A가 Feature Flag를 관리한다고 가정해 보자.
프로젝트 B는 Project Connections를 통해 A의 Flag를 읽을 수 있는 권한을 부여받는다.
이후 B의 애플리케이션은 자신이 가진 Deployment Identity를 이용해 A의 Flag를 평가한다.
즉 인증 정보를 복사하는 대신 프로젝트 사이의 접근 관계를 명시적으로 관리한다.
접근 관계는 다음 위치에서 관리할 수 있다.
Flags > Connections
CLI에서는 다음 명령 그룹을 사용할 수 있다.
vercel flags connections
연결을 삭제하면 기존 SDK Key를 찾아 삭제하거나 애플리케이션을 다시 배포하지 않고도 접근을 취소할 수 있다.
SDK에서 사용하는 방법
소비자 프로젝트에서는 다음 함수를 사용한다.
createVercelAdapter()
이 함수에 Feature Flag를 관리하는 원본 프로젝트의 ID를 전달한다.
대표적인 설정은 다음과 같다.
createVercelAdapter({ projectId: "prj_source" })
이후 해당 Adapter를 Flag 정의에 연결할 수 있다.
예를 들어 새로운 결제 화면을 제어하는 Flag를 다음과 같이 정의할 수 있다.
- Key:
new-checkout - 기본값:
false - Adapter: 원본 프로젝트와 연결한 Vercel Adapter
이렇게 하면 여러 프로젝트가 같은 Feature Flag 설정을 사용할 수 있다.
다만 실제로 해당 Flag가 존재하지 않거나 정상적으로 평가되지 못하는 상황에서는 기본값 처리 등 애플리케이션의 오류 대응 정책을 고려해야 한다.
환경별 Flag 평가
이번 기능에서는 환경을 구분하는 방식도 중요하다.
Project Connections는 연결한 프로젝트에 대해 모든 환경의 Flag를 읽을 수 있는 권한을 부여한다.
하지만 실제 평가할 Flag 환경은 소비자 프로젝트의 Deployment Environment에 따라 결정된다.
예를 들어 Production Deployment에서는 Production Flag를 평가한다.
따라서 원본 프로젝트의 Flag가 Production에서 활성화돼 있고 Preview에서는 비활성화돼 있다면 소비자 프로젝트의 배포 환경에 따라 다른 결과가 반환될 수 있다.
하지만 연결 자체가 특정 환경에만 접근하도록 제한된다는 의미는 아니다.
연결 권한의 범위와 실제 Flag를 평가할 환경은 서로 다른 개념이다.
로컬 개발 환경
로컬 개발에서는 다음 명령을 활용할 수 있다.
vercel env pull
이 명령을 통해 프로젝트의 OIDC Token을 가져올 수 있다.
로컬 환경에서도 별도의 원본 프로젝트 SDK Key를 직접 복사하지 않고 연결된 Flag를 평가할 수 있도록 지원하는 것이다.
다만 로컬 환경에서 사용하는 인증 정보도 실제 권한이 부여된 정보다.
따라서 해당 정보를 소스코드나 공개 Repository에 포함하지 않도록 관리해야 한다.
지원 범위와 한계
이번 기능은 모든 Vercel 요금제에서 사용할 수 있다.
필요한 패키지 버전은 다음과 같다.
| 구성 | 최소 버전 |
|---|---|
@flags-sdk/vercel | 1.6.0 |
@vercel/flags-core | 1.11.0 |
| Vercel CLI | 63.0.0 |
다만 연결은 동일한 Vercel Team 안의 프로젝트를 대상으로 한다.
서로 다른 Team 사이에서 모든 Feature Flag를 자유롭게 공유할 수 있다는 의미는 아니다.
또한 Project Connections는 읽기 권한을 제공한다.
소비자 프로젝트가 원본 프로젝트의 Feature Flag 정의를 임의로 수정할 수 있도록 하는 기능이 아니다.
이번 발표에는 성능 개선에 관한 정량적인 Benchmark가 포함되지 않았다.
여러 프론트엔드 및 백엔드 애플리케이션에서 동일한 Feature Flag를 사용하면서 인증 정보를 복사하는 부담을 줄인 관리 및 보안 기능이다.
Cloudflare Flagship, 로컬 Feature Flag 저장소 지원 — 운영 설정에 영향을 주지 않고 오프라인 테스트 가능
- 발표일: 2026-11-02 — 정확한 게시 시각 미확인
- 안정화 단계: 정식 개발 환경 기능
- 적용 대상: Cloudflare Flagship, Workers, Vite, Vitest
- 필요 버전: Wrangler 4.135.0 이상 또는 Cloudflare Vite Plugin 1.56.0 이상
- 원문: https://developers.cloudflare.com/changelog/post/2026-11-02-local-flag-store/
Cloudflare가 로컬 개발 환경에서 Flagship Feature Flag를 평가할 때 원격 서버 대신 로컬 저장소를 사용하도록 동작을 변경했다.
이번 기능은 Feature Flag의 사용자 타기팅 규칙이나 Percentage Rollout을 새롭게 도입하는 업데이트가 아니다.
기존 Flagship을 실제 개발 및 테스트 환경에서 더 독립적으로 사용할 수 있도록 만든 변경이다.
기존 원격 Flag 평가의 문제
개발자가 새로운 기능을 구현한다고 가정해 보자.
해당 기능은 다음 조건에서 활성화된다.
new-checkout = true
개발자는 로컬 환경에서 해당 기능을 테스트하고 싶다.
하지만 Feature Flag 평가를 위해 운영 또는 원격 Flagship 애플리케이션에 연결해야 한다면 다음 문제가 발생할 수 있다.
첫 번째는 네트워크 연결에 대한 의존성이다.
인터넷 연결이 없으면 Flag 평가에 실패할 수 있다.
두 번째는 설정 변경의 영향 범위다.
로컬 테스트를 위해 원격 Flag 값을 수정하면 다른 개발자나 실제 서비스에도 영향을 줄 가능성이 있다.
세 번째는 테스트의 재현성이다.
테스트를 실행하는 동안 원격 Flag 설정이 변경되면 같은 테스트가 서로 다른 결과를 반환할 수 있다.
이번 변경은 로컬 Flag Store로 이러한 문제를 줄인다.
새로운 기본 동작
Cloudflare는 다음 환경에서 로컬 Flag Store를 사용하도록 변경했다.
wrangler dev- Cloudflare Vite Plugin
- Vitest Integration
cf dev
로컬 환경에서는 원격 Flagship 애플리케이션을 호출하지 않고 로컬 저장소의 값을 평가한다.
따라서 인터넷 연결 없이도 Feature Flag를 사용할 수 있다.
또한 로컬에서 변경한 Flag가 운영 환경의 Flag 설정을 직접 변경하지 않는다.
기본값 처리
새로운 로컬 저장소는 처음에 비어 있다.
예를 들어 개발자가 다음 Flag를 평가한다고 가정해 보자.
new-checkout
하지만 로컬 저장소에는 아직 해당 Flag가 존재하지 않는다.
이 경우 애플리케이션 코드에서 전달한 기본값이 반환된다.
예를 들어 기본값이 false라면 새로운 결제 화면은 비활성화된 상태로 평가된다.
여기서 중요한 점은 로컬 저장소가 원격 설정을 자동으로 복제한 상태로 시작하지 않는다는 사실이다.
따라서 운영 환경과 동일한 Flag 상태로 테스트하려면 관련 데이터를 가져와야 한다.
원격 Flag를 로컬로 가져오기
Cloudflare는 다음 명령을 제공한다.
npx wrangler flagship flags pull <APP_ID>
이 명령은 원격 Flagship 애플리케이션의 Flag 설정을 로컬로 가져오는 데 사용할 수 있다.
이후 로컬 저장소에서 값을 수정해 테스트할 수 있다.
개별 Flag를 관리할 때는 --local 옵션을 사용한다.
예를 들어 다음 작업이 가능하다.
- 로컬 Flag 생성
- 로컬 Flag 수정
- 로컬 Flag 평가
이러한 방식은 운영 설정을 변경하지 않고 특정 Feature Flag 조합을 테스트하는 데 도움이 된다.
Local Explorer
이번 기능은 Local Explorer와도 연결된다.
Local Explorer에서는 개발 서버를 실행한 상태로 다음 작업을 수행할 수 있다.
- Flag 생성
- Flag 수정
- Flag 활성화 및 비활성화
- Flag 평가 결과 확인
예를 들어 새로운 화면을 개발하면서 Flag를 여러 번 활성화하고 비활성화할 수 있다.
이러한 작업을 위해 코드를 수정하거나 원격 Dashboard에 접속할 필요가 줄어든다.
다만 Local Explorer에는 더 높은 버전이 필요하다.
| 기능 | Wrangler | Vite Plugin |
|---|---|---|
| 로컬 Flag Store | 4.135.0 이상 | 1.56.0 이상 |
| Local Explorer | 4.148.0 이상 | 1.63.0 이상 |
기존 원격 평가 유지
개발자는 필요한 경우 기존처럼 원격 Flagship 애플리케이션을 직접 사용할 수도 있다.
Binding에 다음 설정을 추가한다.
remote: true
이렇게 하면 로컬 개발 환경에서도 원격 Flag 평가를 사용하도록 구성할 수 있다.
하지만 로컬 테스트가 실제 운영 설정에 영향을 받지 않도록 하려는 경우에는 새로운 기본 동작이 더 적합할 수 있다.
적용 범위와 한계
이번 변경은 로컬 개발 환경의 Flag 평가 방식을 변경한다.
운영 환경에서 사용하던 Flag가 자동으로 로컬 환경에 복제되는 것은 아니다.
또한 로컬 저장소를 사용한다는 사실만으로 실제 운영 환경의 사용자 타기팅과 완전히 동일한 결과가 보장되는 것은 아니다.
로컬에 어떤 Flag와 평가 문맥을 준비했는지에 따라 결과가 달라질 수 있기 때문이다.
따라서 실제 배포 전에는 원격 설정과 필요한 사용자 조건을 별도로 검증할 필요가 있다.
이번 발표에는 평가 지연 시간이나 테스트 실행 속도에 관한 정량적인 Benchmark가 포함되지 않았다.
Feature Flag를 외부 서비스에 의존하지 않고 로컬 개발 및 자동화 테스트에서 독립적으로 사용할 수 있도록 한 개발 환경 개선이다.
2️⃣ 웹 플랫폼·브라우저
이번 조사 범위에서 포함할 만한 주요 신규 발표를 확인하지 못했습니다.
W3C, WHATWG, TC39, MDN, Chrome·Chromium, Firefox·Mozilla, WebKit·Safari의 공식 공개 정보와 JavaScript·TypeScript·CSS, 웹 성능 및 접근성 관련 발표를 확인했다.
이번 기간에 해당하는 중요한 웹 표준의 신규 채택이나 브라우저 API의 안정화 발표는 확인하지 못했다.
최근 공개된 Chrome 158 Beta와 Firefox 159는 이전 브리핑에서 이미 다뤘으므로 반복하지 않았다.
또한 브라우저의 개발 빌드와 기존 API를 설명하는 기술 글은 새로운 안정 기능의 출시와 구분했다.
다만 이번 조사에서 확인한 백엔드 발표 가운데에는 웹 플랫폼 표준과 연결되는 변경이 존재했다.
Google Cloud Application Load Balancer의 분산 추적 기능은 W3C Trace Context와 OpenTelemetry를 사용한다.
Cloudflare Workers의 request.signal 기본 활성화 예고 역시 웹 플랫폼의 AbortSignal 인터페이스와 연결된다.
두 발표는 새로운 브라우저 기능의 출시가 아니라 기존 웹 표준을 서버 및 클라우드 실행 환경에 적용하는 변경이므로 백엔드·인프라 섹션에서 다룬다.
3️⃣ 백엔드·인프라
Cloudflare Workers, 11월 17일부터 새로운 Module Registry 기본 적용 — Node.js 모듈 호환성 확대
- 발표일: 2026-11-02 — 정확한 게시 시각 미확인
- 적용 예정일: 2026-11-17
- 안정화 단계: 기존 기능의 기본 동작 변경 예고
- 적용 대상: JavaScript Workers
- 적용 조건:
compatibility_date가2026-11-17이상인 Worker - 원문: https://developers.cloudflare.com/changelog/post/2026-11-02-module-registry-default/
Cloudflare가 2026년 11월 17일부터 새로운 Module Registry를 JavaScript Workers의 기본 모듈 로딩 방식으로 사용한다고 발표했다.
이번 변경은 Module Registry를 처음 출시하는 발표가 아니다.
새로운 Registry는 2026년 9월에 이미 공개됐다.
이번 발표의 핵심은 어떤 Compatibility Date부터 새로운 동작을 기본값으로 적용할 것인지 확정했다는 점이다.
Module Registry의 역할
JavaScript에서는 여러 파일을 모듈로 분리할 수 있다.
예를 들어 다음과 같은 구조다.
index.jsdatabase.jsauth.jsutils.js
애플리케이션은 import나 require()를 이용해 다른 모듈을 사용할 수 있다.
하지만 실행 환경마다 모듈을 해석하는 방식이 다를 수 있다.
대표적으로 다음 두 모듈 시스템이 존재한다.
- ECMAScript Modules(ESM)
- CommonJS
ESM은 일반적으로 import와 export를 사용한다.
CommonJS는 require()와 module.exports를 사용한다.
Node.js는 두 모듈 시스템을 지원하지만 실행 환경과 패키지 설정에 따라 동작이 달라질 수 있다.
Cloudflare Workers 역시 Node.js 생태계의 라이브러리를 사용하기 위해 모듈 로딩 방식의 호환성이 중요하다.
새로운 Module Registry는 이러한 차이를 줄이는 데 목적이 있다.
CommonJS require()에서 ESM 로딩
새로운 Registry에서는 Node.js Compatibility가 활성화된 Worker가 require()로 ES Module을 로딩할 수 있다.
예를 들어 기존 라이브러리가 CommonJS를 사용하지만 실제 의존성은 ESM으로 작성된 상황을 생각할 수 있다.
이 경우 모듈 시스템의 차이 때문에 로딩이 실패할 수 있었다.
새로운 Registry는 이러한 조합을 지원한다.
하지만 중요한 제한사항이 있다.
require()로 로딩하는 ES Module과 그 의존성에는 Top-level await가 없어야 한다.
Top-level await는 모듈의 최상위 영역에서 비동기 작업을 기다리는 문법이다.
일반적인 require()는 동기적인 모듈 로딩 방식에 기반한다.
따라서 비동기적인 모듈 초기화가 필요한 ESM과는 충돌할 수 있다.
새로운 Registry가 모든 ESM을 제한 없이 require()할 수 있도록 만드는 것은 아니다.
import.meta 지원
새로운 Registry에서는 다음 정보도 사용할 수 있다.
import.meta.urlimport.meta.mainimport.meta.resolve()
import.meta.url은 현재 모듈의 URL을 제공한다.
import.meta.main은 해당 모듈이 실행 진입점인지 확인하는 데 사용할 수 있다.
import.meta.resolve()는 다른 모듈의 URL을 계산할 수 있다.
예를 들어 다음 표현을 사용할 수 있다.
import.meta.resolve("./helper.js")
이 함수는 해당 모듈의 URL을 반환한다.
하지만 URL을 계산한다고 해서 실제 파일이 존재하는지 확인하거나 해당 모듈을 로딩하는 것은 아니다.
또한 Bundler가 import.meta를 변환할 수 있으므로 실제 결과는 번들링 구성에도 영향을 받는다.
Cloudflare는 공식 예제에서 Unbundled Deployment를 기준으로 동작을 설명했다.
Import Attribute 검증 강화
새로운 Registry에서는 잘못된 Import Attribute를 거부한다.
Import Attribute는 모듈을 가져올 때 추가 정보를 전달하는 문법이다.
예를 들어 JSON Module과 같은 특정 리소스의 유형을 명시할 때 사용할 수 있다.
기존 Registry에서 허용되던 일부 잘못된 표현은 새로운 Registry에서 오류가 발생할 수 있다.
따라서 기존 의존성이 비표준적인 Import Attribute를 사용하고 있다면 호환성 문제가 나타날 수 있다.
Compatibility Date
새로운 기본 동작은 다음 조건에서 적용된다.
compatibility_date >= "2026-11-17"
기존 Worker는 Compatibility Date나 Flag를 변경하지 않는 한 기존 Registry를 계속 사용한다.
따라서 11월 17일이 됐다는 이유만으로 모든 기존 Worker의 모듈 로딩 방식이 일괄 변경되는 것은 아니다.
새로운 Registry를 미리 사용하려면 다음 Compatibility Flag를 사용할 수 있다.
new_module_registry
반대로 새로운 Compatibility Date를 사용하면서 기존 Registry를 유지하려면 다음 Flag를 지정할 수 있다.
legacy_module_registry
Python Workers는 이번 변경 이후에도 기존 Registry를 사용한다.
적용 범위와 한계
이번 발표는 JavaScript Workers의 모듈 로딩 방식 변경에 관한 것이다.
Node.js Runtime 전체를 Cloudflare Workers에서 완전히 동일하게 제공한다는 의미는 아니다.
예를 들어 Node.js의 모든 Native Module이나 운영체제 기능을 사용할 수 있다는 보장은 없다.
실제 호환성은 Cloudflare의 Node.js Compatibility 지원 범위에 영향을 받는다.
이번 발표에는 모듈 로딩 속도나 메모리 사용량에 관한 정량적인 Benchmark가 포함되지 않았다.
Workers의 JavaScript 모듈 로딩 방식을 표준과 Node.js 생태계에 더 가깝게 변경하면서 기존 프로젝트에는 Compatibility Date를 통한 전환 기간을 제공하는 업데이트다.
Cloudflare Workers, 11월 17일부터 request.signal 기본 활성화 — 클라이언트 연결 종료를 AbortSignal로 전달
- 발표일: 2026-11-02 — 정확한 게시 시각 미확인
- 적용 예정일: 2026-11-17
- 적용 대상: Cloudflare Workers
- 적용 조건:
compatibility_date가2026-11-17이상인 Worker - 변경 유형: 기존 기능의 기본 활성화
- 원문: https://developers.cloudflare.com/changelog/post/2026-11-02-request-signal-default/
Cloudflare가 11월 17일부터 Workers에서 클라이언트의 연결 종료를 request.signal로 전달하는 기능을 기본 활성화한다고 발표했다.
이번 변경은 브라우저의 새로운 AbortSignal API를 출시한 것이 아니다.
기존 Web API를 Cloudflare Workers의 요청 처리 환경에서 기본적으로 사용할 수 있도록 하는 변경이다.
요청이 종료돼도 서버 작업은 계속될 수 있다
예를 들어 사용자가 웹 애플리케이션에서 보고서 생성을 요청했다고 가정해 보자.
Worker는 외부 API를 호출해 필요한 데이터를 가져온다.
이후 보고서를 생성해 사용자에게 반환한다.
하지만 사용자가 요청 중간에 브라우저 탭을 닫을 수 있다.
또는 다른 페이지로 이동할 수 있다.
이 경우 클라이언트는 더 이상 응답을 기다리지 않는다.
하지만 서버가 실제 연결 종료를 인식하지 못한다면 외부 API 호출이나 데이터 처리 작업을 계속 수행할 수 있다.
이는 불필요한 리소스 사용으로 이어질 수 있다.
특히 실행 시간이 긴 AI 모델 요청이나 대용량 데이터 처리에서 영향을 받을 수 있다.
request.signal
AbortSignal은 비동기 작업의 취소 상태를 전달하는 Web API다.
Cloudflare Workers에서는 요청 객체의 다음 속성을 사용할 수 있다.
request.signal
새로운 기본 동작에서는 클라이언트의 연결이 종료되면 해당 Signal에 Abort 이벤트가 전달될 수 있다.
애플리케이션은 다음 이벤트를 구독할 수 있다.
request.signal.addEventListener("abort", ...)
예를 들어 연결이 종료됐다는 사실을 로그에 기록할 수 있다.
또는 해당 요청을 위해 시작한 외부 작업을 취소할 수 있다.
외부 fetch()에 Signal 전달
여기서 중요한 제한사항이 있다.
request.signal이 존재한다고 해서 모든 외부 요청이 자동으로 취소되는 것은 아니다.
외부 fetch()를 요청과 함께 취소하려면 Signal을 명시적으로 전달해야 한다.
대표적인 형태는 다음과 같다.
fetch("https://example.com/api", { signal: request.signal })
이렇게 하면 클라이언트의 요청 취소가 외부 fetch()에도 전달될 수 있다.
하지만 이미 외부 서버에서 처리한 작업까지 되돌리는 것은 아니다.
예를 들어 외부 결제 API가 실제 결제를 완료한 이후 클라이언트 연결이 종료됐다고 가정해 보자.
클라이언트에서 요청을 취소하더라도 이미 처리된 결제가 자동으로 취소되는 것은 아니다.
따라서 요청 취소와 실제 비즈니스 트랜잭션의 취소는 구분해야 한다.
기본값 변경과 Opt-in
새로운 동작은 다음 Compatibility Date부터 기본으로 제공된다.
2026-11-17
기존 Worker는 Compatibility Date를 변경하지 않으면 기존 동작을 유지한다.
미리 활성화하려면 다음 Flag를 사용할 수 있다.
enable_request_signal
새로운 Compatibility Date에서 기존 동작을 유지하려면 다음 Flag를 사용할 수 있다.
disable_request_signal
또한 이번 변경은 다음 기능을 자동으로 활성화하지 않는다.
request_signal_passthrough
따라서 외부 작업에 취소 Signal을 전달하려면 앞서 설명한 것처럼 명시적으로 연결해야 한다.
장시간 작업의 안정성
이번 기능은 불필요한 작업을 줄이는 데 활용할 수 있다.
하지만 중요한 백그라운드 작업을 무조건 클라이언트 연결에 종속시키는 것은 적절하지 않을 수 있다.
예를 들어 사용자가 파일을 업로드하고 서버가 처리 완료 여부를 기록해야 한다고 가정해 보자.
사용자가 브라우저를 닫았다는 이유로 필요한 데이터 정리나 트랜잭션 기록까지 중단해서는 안 될 수 있다.
따라서 어떤 작업은 요청 취소와 함께 중단하고, 어떤 작업은 독립적으로 완료해야 하는지 구분해야 한다.
이번 발표에는 실제 실행 시간이나 비용 감소를 측정한 Benchmark가 포함되지 않았다.
클라이언트의 연결 종료를 서버의 비동기 작업과 연결해 불필요한 작업을 중단할 수 있도록 기본 실행 동작을 변경하는 업데이트다.
Google Cloud Application Load Balancer, 분산 추적 Preview 공개 — Load Balancer부터 백엔드까지 하나의 Trace로 분석
- 발표일: 2026-11-02 — 정확한 게시 시각 미확인
- 안정화 단계: Preview
- 적용 대상: Google Cloud Application Load Balancer, Cloud Trace
- 관련 표준: W3C Trace Context, OpenTelemetry
- 원문: https://docs.cloud.google.com/load-balancing/docs/release-notes
- 상세 문서: https://docs.cloud.google.com/load-balancing/docs/distributed-tracing-overview
Google Cloud가 Application Load Balancer에서 시작해 백엔드 서비스까지 이어지는 요청을 하나의 Trace로 분석할 수 있는 분산 추적 기능을 Preview로 공개했다.
이번 기능은 기존 Cloud Logging이나 일반적인 HTTP 요청 로그와 다르다.
하나의 사용자 요청이 여러 서비스와 실행 환경을 거쳐 처리되는 과정을 연결해 보여주는 기능이다.
기존 Load Balancer 로그의 한계
예를 들어 사용자가 웹 애플리케이션에서 주문 목록을 조회했다고 가정해 보자.
요청은 다음 순서로 처리될 수 있다.
- 사용자의 브라우저
- Application Load Balancer
- API 서버
- 인증 서비스
- 데이터베이스
전체 응답 시간이 2초였다고 가정해 보자.
하지만 어디에서 시간이 많이 사용됐는지는 별도의 정보가 필요하다.
Load Balancer 로그에서는 전체 요청 시간과 상태 코드를 확인할 수 있다.
백엔드 서버에서도 개별 작업의 실행 시간을 기록할 수 있다.
하지만 두 기록이 연결되지 않았다면 하나의 요청 흐름을 추적하기 어렵다.
특히 요청이 백엔드에 도착하기 전에 Load Balancer에서 오류가 발생했다면 백엔드 로그에는 해당 요청이 나타나지 않을 수 있다.
새로운 분산 추적 기능은 이러한 구간까지 확인하도록 설계됐다.
Trace와 Span
OpenTelemetry에서는 하나의 요청 흐름을 Trace로 표현한다.
Trace 안에는 여러 Span이 존재할 수 있다.
Span은 하나의 작업을 나타낸다.
예를 들어 다음과 같은 구조다.
- HTTP 요청 처리
- 인증 확인
- 데이터베이스 Query
- 외부 API 호출
각 Span에는 실행 시간과 상태 등 관련 정보를 기록할 수 있다.
여러 Span을 같은 Trace ID로 연결하면 전체 요청 흐름을 분석할 수 있다.
이번 Google Cloud 기능은 Load Balancer에서 Trace를 생성하고 백엔드로 전달할 수 있도록 한다.
W3C Trace Context
Google Cloud는 다음 표준을 사용한다.
- W3C Trace Context
- OpenTelemetry
W3C Trace Context는 서로 다른 서비스가 같은 Trace에 속한다는 정보를 전달하기 위한 표준이다.
대표적인 HTTP Header는 다음과 같다.
traceparent
이 Header에는 Trace ID와 Span ID 등의 정보가 포함된다.
예를 들어 Load Balancer가 요청을 받은 뒤 Trace를 생성한다고 가정해 보자.
이후 백엔드로 요청을 전달할 때 Trace Context를 함께 전달할 수 있다.
백엔드에서 OpenTelemetry Instrumentation을 사용하고 있다면 같은 Trace를 이어서 생성할 수 있다.
따라서 Load Balancer에서 발생한 작업과 백엔드에서 발생한 작업을 하나의 흐름으로 분석할 수 있다.
TelemetryPolicy
새로운 기능은 Network Services API의 다음 리소스를 이용해 구성한다.
TelemetryPolicy
이 정책을 Load Balancer의 Forwarding Rule에 연결한다.
정책에는 다음 정보를 지정할 수 있다.
- Trace를 수집할 대상
- Sampling 설정
Sampling은 전체 요청 가운데 일부만 Trace로 기록하는 방식이다.
예를 들어 초당 10,000개의 요청이 발생하는 서비스에서 모든 요청의 Span을 저장하면 많은 데이터가 생성될 수 있다.
따라서 일부 요청만 선택해 기록할 수 있다.
이렇게 하면 추적 데이터의 저장 비용을 제한할 수 있다.
하지만 Sampling Rate가 너무 낮으면 드물게 발생하는 오류를 놓칠 수 있다.
따라서 실제 서비스의 트래픽과 장애 분석 요구사항에 따라 결정해야 한다.
지원하는 Load Balancer
현재 지원 범위는 다음과 같다.
| Load Balancer | 지원 여부 |
|---|---|
| Global External Application Load Balancer | Preview |
| Regional External Application Load Balancer | Preview |
| Regional Internal Application Load Balancer | Preview |
| Cross-region Internal Application Load Balancer | Preview |
| Classic Application Load Balancer | 지원하지 않음 |
모든 지원 Load Balancer에서 확률 기반 Sampling을 사용할 수 있다.
하지만 Parent-based Sampling은 Internal Application Load Balancer에서만 지원한다.
Parent-based Sampling은 상위 요청의 Trace 수집 여부를 고려해 하위 Span의 수집을 결정하는 방식이다.
따라서 외부와 내부 Load Balancer의 Sampling 지원 범위가 동일하다고 가정해서는 안 된다.
Preview 제한사항
Google Cloud는 다음 제한을 명시했다.
- External Application Load Balancer에서 Parent-based Sampling 미지원
- 사용자 정의 Span Attribute 미지원
- GKE Gateway API를 통한 설정 미지원
- Terraform을 통한 설정 미지원
- 하나의 Telemetry Policy가 최대 50개 리소스만 대상으로 설정 가능
또한 Preview 기능이므로 지원 범위와 API가 이후 변경될 수 있다.
과금
Load Balancer 자체는 Span을 생성하는 작업에 별도의 비용을 부과하지 않는다.
하지만 Cloud Trace에는 Span 수집 비용이 발생할 수 있다.
Google Cloud는 높은 트래픽을 처리하는 서비스에서 다음과 같은 낮은 Sampling Rate를 검토하도록 안내한다.
- 1%
- 0.1%
하지만 이러한 수치는 모든 서비스의 권장 정답이 아니다.
실제 오류 발생 빈도와 추적해야 하는 요청의 종류에 따라 적합한 설정이 달라질 수 있다.
이번 발표에는 분산 추적 기능을 활성화했을 때 실제 요청 지연 시간이 얼마나 증가하는지에 관한 독립적인 Benchmark가 포함되지 않았다.
Load Balancer와 백엔드의 실행 기록을 개별 로그가 아니라 하나의 요청 흐름으로 연결할 수 있도록 관측 범위를 확장한 Preview 기능이다.
Google Cloud Application Load Balancer, mTLS 인증서 검증 예외 정식 지원 — 기존 인증서 마이그레이션을 위한 제한적 완화
- 발표일: 2026-11-02 — 정확한 게시 시각 미확인
- 안정화 단계: Generally Available
- 적용 대상: Google Cloud Application Load Balancer의 Frontend mTLS
- 신규 검증 모드:
ALLOW_VALIDATION_RELAXATIONS - 원문: https://docs.cloud.google.com/load-balancing/docs/release-notes
- 상세 문서: https://docs.cloud.google.com/load-balancing/docs/mtls
Google Cloud가 Application Load Balancer의 mTLS 인증에서 일부 인증서 검증 조건을 선택적으로 완화하는 기능을 정식 출시했다.
이번 기능은 인증서 검증을 전부 비활성화하는 기능이 아니다.
오래된 인증서를 사용하는 시스템을 새로운 인증 체계로 이전하는 동안 일부 조건에만 예외를 적용할 수 있도록 한다.
mTLS와 일반적인 TLS의 차이
일반적인 HTTPS에서는 클라이언트가 서버의 인증서를 확인한다.
예를 들어 브라우저가 웹사이트에 접속할 때 해당 서버가 올바른 인증서를 가지고 있는지 확인할 수 있다.
반면 mTLS(Mutual TLS)에서는 서버도 클라이언트의 인증서를 검증한다.
즉 양방향으로 인증서를 이용한다.
이러한 방식은 다음 환경에서 사용할 수 있다.
- 기업 내부 API
- 서비스 간 통신
- 특정 인증서를 가진 기기만 접근하는 시스템
- 금융 및 업무용 API
예를 들어 외부에 공개된 API 서버가 있다고 가정해 보자.
서버는 HTTPS를 제공하지만 모든 사용자의 요청을 허용하지 않는다.
특정 클라이언트 인증서를 가진 사용자에게만 연결을 허용할 수 있다.
기존 인증서의 호환성 문제
실제 기업에서는 오래된 인증서를 사용하는 경우가 있다.
예를 들어 다음과 같은 인증서다.
- Extended Key Usage가 없는 인증서
- SHA-1 서명을 사용하는 오래된 인증서
- 유효 기간이 종료된 인증서
보안상으로는 새로운 인증서를 발급하는 것이 바람직하다.
하지만 실제 서비스에서는 모든 클라이언트의 인증서를 한 번에 교체하기 어려울 수 있다.
예를 들어 수많은 기기가 특정 인증서를 사용하고 있다고 가정해 보자.
모든 기기를 동시에 업데이트하지 못하면 새 서버에서 연결이 실패할 수 있다.
이번 기능은 이러한 마이그레이션 기간에 사용할 수 있는 선택적인 예외를 제공한다.
세 가지 Validation Relaxation
새로운 설정에서는 다음 값을 사용할 수 있다.
| 설정 | 허용하는 예외 |
|---|---|
ALLOW_MISSING_EKU | EKU Extension 자체가 없는 클라이언트 인증서 |
ALLOW_SHA1_HASHING | SHA-1로 서명된 인증서 체인 |
ALLOW_EXPIRED_LEAF_CERTIFICATE | 만료된 클라이언트 Leaf Certificate |
첫 번째는 Extended Key Usage와 관련된다.
EKU는 인증서가 어떤 용도로 사용될 수 있는지 나타내는 정보다.
예를 들어 클라이언트 인증에는 clientAuth 용도가 사용될 수 있다.
이번 설정은 EKU Extension 자체가 없는 인증서에 예외를 적용한다.
하지만 EKU가 존재하면서 clientAuth를 허용하지 않는 인증서까지 무조건 허용하는 것은 아니다.
두 번째는 SHA-1과 관련된다.
SHA-1은 오래된 Hash 알고리즘이다.
현재는 충돌 공격 등의 보안 문제로 인해 새로운 인증서에 사용하는 것이 적절하지 않다.
이번 기능은 오래된 인증서를 사용하는 클라이언트를 지원하기 위해 선택적으로 허용한다.
하지만 장기적으로 유지하는 것을 권장하지 않는다.
세 번째는 만료된 Leaf Certificate와 관련된다.
해당 인증서를 검증할 때 실제 만료 시각보다 1초 이전의 시점으로 평가하는 방식이다.
하지만 이 예외는 클라이언트의 Leaf Certificate에만 적용된다.
중간 인증서나 Root Certificate의 만료 문제까지 모두 무시하는 것은 아니다.
여전히 유지되는 검증
새로운 모드에서는 다음 값으로 설정한다.
ALLOW_VALIDATION_RELAXATIONS
이 경우 선택한 예외를 제외한 인증서 검증은 계속 수행한다.
예를 들어 인증서의 신뢰 체인과 비공개 키의 소유 여부는 여전히 중요하다.
클라이언트가 인증서를 제출하지 않거나 허용하지 않은 다른 검증 조건에 실패하면 연결이 거부될 수 있다.
따라서 모든 잘못된 인증서를 받아들이는 모드와는 구분된다.
보안상의 트레이드오프
이번 기능에는 명확한 보안 위험이 존재한다.
예를 들어 ALLOW_SHA1_HASHING을 활성화하면 현재 권장하지 않는 오래된 서명 알고리즘의 인증서를 허용하게 된다.
또한 만료된 인증서를 허용하면 인증서 교체를 통해 접근 권한을 종료하려는 정책과 충돌할 수 있다.
Google Cloud는 이러한 설정을 기존 인증서를 교체하는 기간에 한정해 일시적으로 사용하도록 권장한다.
특히 EKU가 없는 인증서를 허용하려면 클라이언트 인증 전용으로 사용하는 Private CA를 이용하는 것이 적절하다고 설명한다.
다른 용도의 인증서까지 발급하는 CA를 사용하면 원래 클라이언트 인증 용도가 아닌 인증서가 허용될 가능성이 있기 때문이다.
이번 기능은 모든 종류의 Application Load Balancer에서 사용할 수 있다.
다만 기존 인증서 검증 정책을 자동으로 완화하지 않는다.
운영자가 필요한 예외를 명시적으로 설정해야 한다.
오래된 클라이언트 인증서를 사용하는 시스템을 이전하는 동안 일부 검증 조건만 선택적으로 완화할 수 있도록 한 mTLS 관리 기능이다.
Cloudflare AI Search, 과금 시작을 12월 1일로 연기하고 기본 동기화 주기를 24시간으로 변경
- 발표일: 2026-11-02 — 정확한 게시 시각 미확인
- 적용 대상: Cloudflare AI Search
- 변경된 과금 시작일: 2026-12-01
- 기본 동기화 주기: 기존 6시간 → 24시간
- 원문: https://developers.cloudflare.com/changelog/post/2026-11-02-billing-postponed-default-sync-interval/
Cloudflare가 AI Search의 과금 시작일을 한 달 연기하고 기본 데이터 동기화 간격을 변경했다.
이번 업데이트는 새로운 검색 모델이나 검색 알고리즘의 출시가 아니다.
AI Search를 실제 서비스에 적용할 때 영향을 주는 과금 및 데이터 갱신 정책의 변경이다.
AI Search의 역할
AI Search는 문서를 수집하고 인덱싱해 검색 및 AI 기반 응답 생성에 활용하는 기능이다.
예를 들어 기술 문서 검색 서비스를 생각할 수 있다.
기업은 다음 데이터를 AI Search에 연결할 수 있다.
- 제품 설명
- 고객지원 문서
- 개발자 가이드
- 내부 지식 문서
이후 사용자는 자연어로 질문할 수 있다.
검색 시스템은 관련 문서를 찾는다.
필요한 경우 AI 모델이 해당 정보를 바탕으로 응답을 생성할 수 있다.
이러한 구조에서는 문서 검색의 품질뿐 아니라 최신 데이터가 언제 반영되는지도 중요하다.
과금 시작일 변경
기존 과금 시작일은 다음과 같았다.
2026-11-01
새로운 시작일은 다음과 같다.
2026-12-01
따라서 2026년 12월 1일 이전의 AI Search 사용량에는 해당 서비스의 요금이 부과되지 않는다.
하지만 12월 1일 이후의 요금 단가나 무료 제공량이 변경된 것은 아니다.
Cloudflare는 기존 가격과 포함된 사용량을 유지한다고 설명했다.
따라서 이번 변경은 가격 인하가 아니라 과금 시작 시점의 연기다.
기본 동기화 간격 변경
두 번째 변경은 데이터 동기화 주기다.
기존 기본값은 다음과 같았다.
6시간
새로운 기본값은 다음과 같다.
24시간
예를 들어 오전 9시에 기술 문서를 수정했다고 가정해 보자.
AI Search는 해당 문서의 변경 사항을 인덱스에 반영해야 한다.
하지만 동기화가 하루에 한 번만 수행된다면 수정된 내용이 즉시 검색 결과에 반영되지 않을 수 있다.
이러한 지연은 실제 서비스의 성격에 따라 다르게 평가할 수 있다.
예를 들어 변경이 거의 없는 제품 소개 문서라면 하루 단위 동기화도 충분할 수 있다.
반면 재고와 가격, 긴급 장애 공지처럼 자주 변경되는 데이터에서는 문제가 될 수 있다.
동기화 간격을 직접 지정
개발자는 기본값을 그대로 사용하지 않아도 된다.
지원하는 범위는 다음과 같다.
15분부터 24시간까지
Dashboard에서 Sync Interval을 변경할 수 있다.
API에서는 다음 필드를 사용한다.
sync_interval
따라서 데이터의 갱신 빈도와 실제 서비스의 요구사항에 맞춰 동기화 간격을 선택할 수 있다.
다만 동기화 주기를 짧게 설정한다고 해서 문서가 변경되는 즉시 모든 검색 결과에 반영된다는 의미는 아니다.
실제 문서 수집과 인덱싱에 추가 시간이 필요할 수 있기 때문이다.
비용과 최신성의 관계
이번 변경은 검색 데이터의 최신성과 운영 비용 사이의 관계를 보여준다.
예를 들어 문서가 거의 변경되지 않는 서비스에서 15분마다 전체 동기화 작업을 수행한다면 불필요한 처리 비용이 발생할 수 있다.
반면 동기화를 하루에 한 번만 수행하면 실제 서비스의 변경 사항을 늦게 반영할 수 있다.
따라서 데이터의 특성을 고려해야 한다.
이번 발표에는 동기화 주기 변경으로 실제 인프라 비용이 얼마나 감소하는지에 관한 정량적인 Benchmark가 포함되지 않았다.
또한 검색 정확도나 응답 지연 시간의 개선 수치도 발표되지 않았다.
AI Search의 기본 데이터 갱신 정책을 변경하고 과금 시작 시점을 연기한 운영 정책 업데이트다.
Vercel Sandbox, Drive 연결 환경의 시작 시간 최대 2.4배 단축 — 중앙값 1.7초에서 0.7초로 감소
- 발표일: 2026-11-02 — 정확한 게시 시각 미확인
- 적용 대상: Vercel Sandbox, Drives
- 안정화 단계: 정식 플랫폼 최적화
- 발표 성능: 최대 2.4배 빠른 시작
- 측정 주체: Vercel 자체 측정
- 원문: https://vercel.com/changelog/vercel-sandbox-with-drives-now-starts-up-to-2-4x-faster
Vercel이 Drive가 연결된 Sandbox를 생성하는 과정의 시작 지연 시간을 줄였다고 발표했다.
Sandbox는 외부 코드나 AI가 생성한 코드를 격리된 환경에서 실행하는 데 사용할 수 있다.
예를 들어 AI 코딩 에이전트가 사용자의 요청에 따라 코드를 수정한다고 가정해 보자.
에이전트는 실제 프로젝트 파일을 읽고 변경해야 한다.
또한 테스트를 실행하거나 빌드 결과를 확인할 수도 있다.
이러한 작업에서는 실행 환경을 빠르게 생성하는 것이 중요하다.
특히 요청마다 새로운 Sandbox를 생성하는 구조에서는 초기화 시간이 사용자 경험에 영향을 줄 수 있다.
Drive와 Sandbox
Vercel Sandbox는 실행 환경과 파일 저장소를 연결할 수 있다.
예를 들어 작업 중에 생성한 프로젝트 파일이나 결과물을 Drive에 저장할 수 있다.
이러한 구성에서는 새로운 Sandbox를 시작하면서 Drive를 연결해야 한다.
따라서 일반적인 명령 실행 환경보다 초기화에 추가 작업이 필요할 수 있다.
이번 업데이트는 이러한 연결 환경의 시작 시간을 개선한 것이다.
실제 발표 수치
Vercel이 공개한 중앙값은 다음과 같다.
| 항목 | 변경 전 | 변경 후 |
|---|---|---|
| Drive 연결 Sandbox 생성 시간 | 1.7초 | 0.7초 |
두 값을 비교하면 약 2.4배 차이가 발생한다.
하지만 이 수치는 Vercel Image에서 Drive가 연결된 Sandbox를 생성한 경우의 자체 측정 결과다.
모든 Sandbox 실행이 반드시 0.7초 안에 시작한다는 의미는 아니다.
또한 실제 AI 코딩 작업 전체가 2.4배 빨라졌다는 의미도 아니다.
예를 들어 에이전트가 실제로 코드를 분석하고 테스트를 실행하는 데 30초를 사용한다면 Sandbox 초기화에서 1초를 줄인 효과는 전체 실행 시간에서 상대적으로 작을 수 있다.
반면 짧은 작업을 매우 자주 실행하는 환경에서는 초기화 비용의 감소가 더 큰 영향을 줄 수 있다.
이번 변경은 별도의 SDK API를 추가하는 기능이 아니다.
Drive가 연결된 격리 실행 환경의 초기화 비용을 줄인 플랫폼 내부 성능 최적화다.
4️⃣ 개발 도구 및 보안
Vercel, 악성 npm 패키지를 설치 전과 설치 중 차단 — Install Script 실행 이전의 공급망 보안 강화
- 발표일: 2026-11-02 — 정확한 게시 시각 미확인
- 안정화 단계: 정식 플랫폼 보안 기능
- 적용 대상: Vercel Build, 공개 npm 패키지
- 지원 요금제: 모든 Vercel 요금제
- 적용 방식: 별도 설정 없이 신규 Build에 자동 적용
- 원문: https://vercel.com/changelog/blocking-malicious-npm-packages-before-and-during-installation
Vercel이 알려진 악성 npm 패키지를 의존성 설치 이전과 설치 과정에서 차단하는 기능을 추가했다.
이번 업데이트는 JavaScript 프로젝트의 공급망 보안과 관련된다.
npm 패키지는 단순히 소스코드 파일만 포함하지 않을 수 있다.
일부 패키지는 설치 과정에서 Script를 실행할 수 있다.
대표적으로 다음 Script가 존재할 수 있다.
preinstallinstallpostinstall
이러한 Script는 정상적인 패키지 설치 과정에서도 사용된다.
예를 들어 Native Module을 컴파일하거나 필요한 파일을 생성할 수 있다.
하지만 공격자가 악성 코드를 포함한 패키지를 배포한다면 설치 과정에서 의도하지 않은 명령이 실행될 수 있다.
기존 보안 검사의 한계
일반적인 의존성 보안 검사는 설치가 완료된 이후 실행할 수 있다.
예를 들어 npm audit은 설치된 의존성에 알려진 취약점이 존재하는지 확인하는 데 사용할 수 있다.
하지만 악성 패키지의 설치 Script가 이미 실행된 이후라면 문제가 된다.
예를 들어 악성 패키지가 postinstall에서 환경 변수를 읽으려고 시도했다고 가정해 보자.
설치가 완료된 이후 악성 패키지를 발견하더라도 이미 실행된 명령의 영향을 되돌릴 수는 없다.
따라서 보안 검사의 시점이 중요하다.
이번 업데이트는 패키지를 설치하기 전부터 알려진 위험 버전을 검사한다.
첫 번째 단계: Lockfile 검사
Vercel은 의존성 설치 전에 지원하는 Lockfile을 검사한다.
예를 들어 다음과 같은 파일이다.
package-lock.jsonpnpm-lock.yamlyarn.lock
Lockfile은 실제 설치할 패키지와 버전을 기록한다.
Vercel은 해당 정보가 차단 목록에 존재하는지 확인한다.
예를 들어 특정 패키지의 1.2.3 버전에 악성 코드가 발견됐다고 가정해 보자.
해당 버전이 Lockfile에 기록돼 있다면 설치 전에 문제를 발견할 수 있다.
이번 검사는 Build Cache에서 의존성을 복구하는 경우에도 적용된다.
따라서 이전에 성공한 Build의 Cache에 특정 패키지가 존재했다는 이유만으로 검사를 생략하지 않는다.
두 번째 단계: 다운로드 중 차단
Vercel은 설치 과정에서도 공개 npm 패키지의 다운로드를 검사한다.
이는 Lockfile에 기록된 정보만 검사하는 방식보다 넓은 보호 범위를 제공하려는 목적이다.
예를 들어 패키지 매니저가 실제 의존성을 가져오는 과정에서 차단된 버전을 요청할 수 있다.
이 경우 Vercel은 해당 다운로드를 막을 수 있다.
따라서 악성 패키지의 Install Script가 실행되기 전에 설치가 실패하도록 할 수 있다.
하지만 모든 악성 코드가 npm 패키지 다운로드 과정에서만 유입되는 것은 아니다.
예를 들어 다음과 같은 경로도 존재할 수 있다.
- Git Repository에서 직접 가져오는 코드
- 외부 설치 Script
- Build 과정에서 직접 다운로드하는 실행 파일
- 악성 코드가 포함된 자체 소스코드
이번 기능이 이러한 모든 경로를 동일하게 차단한다고 가정해서는 안 된다.
기존 검사와의 관계
Vercel은 설치 이후에도 기존 보안 검사를 유지한다.
대표적으로 다음 대상을 검사한다.
- 설치된 의존성
- 배포할 Function의 출력 결과
따라서 보안 검사가 하나의 시점에만 수행되는 것은 아니다.
이번 업데이트에서는 다음 세 단계가 연결된다.
- 설치 전 Lockfile 검사
- 설치 중 패키지 다운로드 차단
- 설치 후 및 배포 전 검사
이는 알려진 악성 패키지가 실제 빌드 환경에 포함되거나 실행되는 상황을 줄이기 위한 다중 방어 구조다.
차단 목록의 갱신
Vercel은 특정 패키지와 버전을 차단 목록에 등록한다.
차단 대상에는 다음 정보가 사용될 수 있다.
- 악성 코드
- 알려진 보안 취약점
새로운 차단 규칙은 일반적으로 몇 분 안에 신규 Build에 반영된다고 설명한다.
하지만 차단 목록은 이미 발견한 위험에 의존한다.
따라서 아직 발견되지 않은 악성 패키지까지 모두 차단할 수 있다는 의미는 아니다.
공급망 보안에서는 패키지의 출처와 유지보수 상태, 설치 Script의 사용 여부 등을 계속 고려해야 한다.
차단이 발생한 경우
Build에서 차단된 패키지가 발견되면 Vercel은 배포를 실패 처리한다.
오류에는 문제가 된 패키지와 버전이 표시된다.
또한 영향을 받은 Build Cache를 무효화하려고 시도한다.
이후 안전한 버전으로 의존성을 업데이트할 수 있다.
이때 실제 패키지 버전뿐 아니라 Lockfile도 함께 변경해야 한다.
예를 들어 package.json만 수정했지만 Lockfile에는 여전히 기존 버전이 남아 있다면 동일한 문제가 계속 발생할 수 있다.
적용 범위와 한계
이번 기능은 모든 요금제에 자동 적용된다.
별도의 설정 변경은 필요하지 않다.
하지만 이번 발표에는 차단 정확도나 False Positive 비율, 실제 악성 패키지 발견 건수에 관한 독립적인 Benchmark가 포함되지 않았다.
또한 차단 목록에 포함되지 않은 취약한 패키지가 모두 안전하다는 의미는 아니다.
의존성 설치 이후에 위험을 발견하는 방식에서 한 단계 나아가 설치 이전과 다운로드 과정에서도 알려진 악성 패키지를 차단하도록 확장한 공급망 보안 업데이트다.
GitHub Actions, 12월 2일부터 공개 Repository의 pull_request_target 기본 차단 — 사전 차단 테스트 일정 공개
- 발표일: 2026-11-02 — 정확한 게시 시각 미확인
- 기본 차단 적용 예정일: 2026-12-02
- 현재 상태: Evaluate Mode
- 적용 대상: GitHub의 공개 Repository
- 제외 대상: Private 및 Internal Repository, GitHub Enterprise Server
- 원문: https://github.blog/changelog/2026-11-02-pull_request_target-blocked-by-default-december-2/
GitHub가 공개 Repository에서 pull_request_target 이벤트를 기본적으로 차단하는 정책의 적용일을 2026년 12월 2일로 확정했다.
기존에는 11월 2일부터 적용할 예정이었다.
하지만 이번 발표에서 적용일을 한 달 연기했다.
또한 실제 차단 전에 두 차례 임시 차단 테스트를 진행할 예정이다.
이번 변경은 새로운 GitHub Actions 이벤트를 추가하는 기능이 아니다.
기존 이벤트의 실행 권한을 더 보수적으로 관리하기 위한 보안 정책 변경이다.
pull_request와 pull_request_target
GitHub Actions에서는 Pull Request와 관련된 여러 이벤트를 사용할 수 있다.
대표적으로 다음 두 가지다.
pull_requestpull_request_target
두 이벤트는 이름이 비슷하지만 실제 실행 Context가 다르다.
pull_request는 일반적인 Pull Request 이벤트를 처리한다.
반면 pull_request_target은 기본 Repository의 Context에서 실행된다.
이 차이는 인증 정보와 연결된다.
pull_request_target Workflow는 Base Repository의 권한과 Secret에 접근할 수 있는 상황이 존재한다.
따라서 외부 기여자가 제출한 코드를 이 Workflow에서 직접 실행하면 위험할 수 있다.
외부 PR 코드의 실행 위험
예를 들어 공개 오픈소스 프로젝트를 운영한다고 가정해 보자.
외부 사용자는 Repository를 Fork한 뒤 PR을 생성할 수 있다.
이때 PR에는 공격자가 작성한 코드가 포함될 수 있다.
문제는 해당 코드를 어떤 권한으로 실행하는지다.
만약 pull_request_target Workflow가 Fork의 코드를 Checkout한 뒤 테스트 Script를 실행한다면 공격자가 작성한 코드가 Base Repository의 높은 권한을 가진 실행 환경에서 동작할 수 있다.
이러한 상황에서는 다음 문제가 발생할 가능성이 있다.
- Repository Secret 노출
- 쓰기 권한이 있는 Token 악용
- CI Pipeline 변조
- 악성 Artifact 생성
GitHub는 이러한 위험을 줄이기 위해 공개 Repository에서 해당 이벤트를 기본적으로 차단하도록 정책을 변경한다.
새로운 기본 정책
2026년 12월 2일부터 공개 Repository에서는 pull_request_target이 기본적으로 차단된다.
하지만 예외가 존재한다.
관리자가 명시적인 Actions Event Policy를 통해 허용한 경우에는 계속 실행할 수 있다.
즉 모든 pull_request_target Workflow를 영구적으로 제거하는 변경은 아니다.
필요한 Workflow를 검토한 뒤 명시적으로 허용하도록 하는 방식이다.
예를 들어 PR의 Label을 관리하는 Workflow가 있다고 가정해 보자.
이 Workflow는 외부 PR의 코드를 실제로 실행하지 않고 GitHub API만 호출할 수 있다.
해당 Workflow가 안전하게 구성됐다면 관리자 정책을 통해 실행을 허용할 수 있다.
Evaluate Mode
현재 기본 정책은 Evaluate Mode로 적용된다.
Evaluate Mode는 실제 Workflow를 차단하지 않고 어떤 실행이 차단 대상에 해당하는지 기록하는 방식이다.
따라서 기존 Workflow는 계속 실행될 수 있다.
하지만 관리자는 Insights에서 이후 차단될 Workflow를 확인할 수 있다.
이러한 방식은 새 보안 정책을 적용하기 전에 기존 CI에 미치는 영향을 확인하기 위한 것이다.
두 차례 임시 차단
GitHub는 12월 2일의 정식 적용 전에 두 차례 Brownout을 진행한다.
Brownout은 제한된 시간 동안 새로운 정책을 실제로 강제하는 테스트다.
일정은 다음과 같다.
| 구분 | UTC | 한국 시간 |
|---|---|---|
| 첫 번째 | 11월 9일 16:00~18:00 | 11월 10일 01:00~03:00 |
| 두 번째 | 11월 16일 16:00~18:00 | 11월 17일 01:00~03:00 |
해당 시간에는 차단 대상에 해당하는 Workflow가 실제로 실행되지 않는다.
대신 실패한 Run이 생성된다.
오류 Annotation을 통해 차단 사유를 확인할 수 있다.
Brownout이 종료되면 정책은 다시 Evaluate Mode로 돌아간다.
따라서 11월 10일 또는 17일의 테스트 시간이 종료됐다고 해서 이후 모든 Workflow가 계속 차단되는 것은 아니다.
정식 기본 차단 적용일은 12월 2일이다.
Workflow별 허용 정책
GitHub는 필요한 경우 다음 방법을 제공한다.
첫 번째는 해당 이벤트를 기본적으로 차단하는 정책을 유지하는 것이다.
두 번째는 실제 필요한 Workflow에만 명시적으로 실행 권한을 부여하는 것이다.
이때 Workflow File Targeting을 이용해 특정 Workflow 파일만 허용할 수 있다.
따라서 조직 전체의 모든 pull_request_target을 무조건 허용할 필요는 없다.
이러한 방식은 다음 두 정책을 분리한다.
- 위험한 이벤트는 기본적으로 차단
- 검토한 Workflow만 예외적으로 허용
적용 범위와 제한사항
이번 정책은 다음 환경의 공개 Repository에 적용된다.
- GitHub Free
- GitHub Team
- GitHub Enterprise Cloud
Private 및 Internal Repository에는 이번 기본 정책이 적용되지 않는다.
GitHub Enterprise Server 역시 이번 변경의 직접적인 적용 대상이 아니다.
하지만 해당 환경이 기본 정책 변경의 대상이 아니라는 사실과 pull_request_target을 안전하게 사용할 수 있다는 사실은 다르다.
이 이벤트의 권한 특성은 동일하게 고려해야 한다.
이번 발표는 성능 개선이나 새로운 CI 기능에 관한 것이 아니다.
공개 Repository에서 높은 권한을 가진 PR 이벤트가 의도하지 않게 실행되는 위험을 줄이기 위해 기본 정책을 변경하고 사전 검증 기간을 제공하는 보안 업데이트다.
Google Cloud Model Armor, 스트리밍 AI 응답의 오탐을 줄이는 Exclusion Rule Preview 공개
- 발표일: 2026-11-02 — 정확한 게시 시각 미확인
- 안정화 단계: Preview
- 적용 대상: Google Cloud Model Armor
- 관련 API:
StreamSanitizeUserPrompt,StreamSanitizeModelResponse - 원문: https://docs.cloud.google.com/model-armor/configure-exclusion-rules
- 공식 릴리스 노트: https://docs.cloud.google.com/release-notes
Google Cloud가 Model Armor의 스트리밍 보안 검사에서 특정 문자열이나 패턴을 탐지 대상에서 제외할 수 있는 기능을 Preview로 공개했다.
Model Armor는 AI 모델에 전달하는 입력과 모델이 반환하는 출력을 검사하는 보안 기능이다.
대표적으로 다음과 같은 위험을 탐지하는 데 사용할 수 있다.
- Prompt Injection
- Jailbreak
- 안전 정책에 위배되는 콘텐츠
하지만 보안 검사에서는 False Positive가 발생할 수 있다.
False Positive는 실제로 문제가 없는 데이터를 위험하다고 잘못 판단하는 상황이다.
정상적인 기술 용어가 차단되는 문제
예를 들어 개발자용 AI 서비스를 운영한다고 가정해 보자.
사용자는 다음과 같은 질문을 할 수 있다.
프로세스를 종료하는 방법을 알려주세요.
또는 다음 표현을 사용할 수 있다.
ignore empty rows
이러한 표현은 정상적인 소프트웨어 개발 작업에서 사용될 수 있다.
하지만 일부 보안 필터는 명령형 문장이나 위험해 보이는 표현을 공격으로 잘못 분류할 수 있다.
예를 들어 ignore라는 단어가 존재한다고 해서 모든 문장이 Prompt Injection인 것은 아니다.
실제 문맥에 따라 정상적인 데이터 처리 지시일 수 있다.
이번 기능은 이러한 오탐을 줄이기 위해 특정 표현을 검사에서 제외하도록 한다.
Dictionary와 Regular Expression
이번 기능에서는 두 종류의 Exclusion Rule을 사용할 수 있다.
첫 번째는 Dictionary 기반 규칙이다.
예를 들어 다음 표현을 등록할 수 있다.
ignore empty rows
두 번째는 Regular Expression 기반 규칙이다.
예를 들어 특정 명령어와 숫자 형식을 조합한 문자열을 지정할 수 있다.
정규식은 동적인 식별자나 일정한 구조를 가진 문자열을 처리하는 데 사용할 수 있다.
이러한 규칙은 특정 Model Armor Template에 연결된다.
따라서 모든 AI 애플리케이션에 동일한 예외를 전역으로 적용하는 것이 아니라 서비스별 요구사항에 맞춰 설정할 수 있다.
Partial Match와 Full Match
Exclusion Rule에서는 일치 범위를 구분할 수 있다.
| 방식 | 의미 |
|---|---|
| Partial Match | 전체 입력 가운데 일부가 규칙과 일치하면 적용 |
| Full Match | 전체 입력이 규칙과 일치해야 적용 |
예를 들어 다음 표현을 생각해 보자.
ignore empty rows
Partial Match에서는 이 표현이 더 긴 문장 안에 포함돼 있어도 일치할 수 있다.
반면 Full Match에서는 전체 입력이 지정한 표현과 일치해야 한다.
이 차이는 보안상 중요하다.
예를 들어 정상적인 고정 문구를 예외로 허용하려는 상황에서 Partial Match를 사용하면 해당 문구 앞뒤에 악의적인 내용이 추가돼도 예외 규칙이 적용될 가능성이 있다.
따라서 필요한 범위만 예외로 지정해야 한다.
스트리밍 모드별 지원 범위
이번 기능은 스트리밍 AI 검사에 적용된다.
하지만 실행 모드에 따라 지원 범위가 다르다.
| 모드 | 지원 범위 |
|---|---|
STREAMING_MODE_BUFFERED | Partial Match, Full Match |
STREAMING_MODE_REALTIME | Partial Match |
Buffered Mode에서는 입력이나 출력의 일정 부분을 모아 검사할 수 있다.
반면 Realtime Mode에서는 데이터를 순차적으로 처리한다.
따라서 전체 입력이 완성됐는지 확인해야 하는 Full Match는 지원 범위가 제한된다.
이번 기능이 모든 스트리밍 설정에서 동일하게 동작한다고 가정해서는 안 된다.
보안상 제한사항
Exclusion Rule은 오탐을 줄이는 데 도움이 될 수 있다.
하지만 실제 공격을 탐지하지 못하도록 만드는 위험도 존재한다.
예를 들어 지나치게 넓은 정규식으로 정상적인 표현을 제외한다고 가정해 보자.
공격자가 같은 패턴을 악의적인 입력에 포함시킬 수 있다.
그 결과 해당 데이터의 일부 검사가 생략될 수 있다.
따라서 예외 규칙은 실제로 확인한 False Positive를 대상으로 제한적으로 사용하는 것이 적절하다.
또한 이번 기능은 다음 검사에 적용된다.
- Prompt Injection 및 Jailbreak Detection
- Responsible AI Safety Filter
Model Armor의 모든 보안 검사 종류를 동일하게 제어하는 것은 아니다.
지역별 API Endpoint
Model Armor Template을 관리할 때는 해당 Template과 같은 지역의 Endpoint를 사용해야 한다.
예를 들어 다음과 같은 형태다.
modelarmor.LOCATION.rep.googleapis.com
일반적인 Global Endpoint에서는 Template 관리 및 Prompt Sanitization의 일부 기능을 사용할 수 없다.
또한 설정을 변경하려면 적절한 IAM 권한이 필요하다.
이번 기능은 Preview이므로 향후 지원 범위와 API가 변경될 수 있다.
공식 발표에는 오탐 감소율이나 공격 탐지 정확도의 변화에 관한 정량적인 Benchmark가 포함되지 않았다.
AI 입력과 출력의 보안 필터를 완전히 비활성화하지 않고 확인된 정상 표현에만 제한적인 예외를 적용할 수 있도록 한 Preview 기능이다.
📚 추천 글
A 5.86 MB font sat in my app for three months. No CSS rule ever referenced it.
- 저자: Melisa Wong
- 매체: DEV Community
- 게시일: 2026-11-02 — 정확한 게시 시각 미확인
- 기술: CSS, Web Font, WOFF2, Capacitor, SVG, Font Subsetting
- 원문: https://dev.to/melisawrites/a-586-mb-font-sat-in-my-app-for-three-months-no-css-rule-ever-referenced-it-25k3
실제 웹·모바일 애플리케이션에서 약 5.86MB의 Font 파일이 세 달 동안 배포 결과물에 포함됐지만 정작 어떤 화면에서도 사용되지 않았다는 사실을 발견하고 원인을 분석한 글이다.
이번 글의 특징은 문제가 일반적인 웹 성능 검사에서 발견되지 않았다는 점이다.
브라우저의 Network Panel에는 해당 Font 요청이 존재하지 않았다.
Console에도 오류가 발생하지 않았다.
실제 화면 역시 정상적으로 표시됐다.
하지만 Android와 iOS 앱에는 해당 Font 파일이 계속 포함되고 있었다.
@font-face는 다운로드 명령이 아니다
저자는 다음 Font를 사용하려고 했다.
Ma Shan Zheng
CSS에는 해당 Font의 @font-face 정의가 존재했다.
파일의 경로는 다음과 같았다.
/fonts/MaShanZheng.ttf
하지만 실제 CSS와 JSX에서는 해당 Font Family를 사용하지 않았다.
저자는 총 52개의 소스 파일을 검색했다.
그 결과 Font 이름이 등장하는 위치는 @font-face 선언 하나뿐이었다.
실제 빌드된 JavaScript Bundle에도 Font 이름이 존재하지 않았다.
이러한 상황에서 브라우저는 Font 파일을 다운로드하지 않을 수 있다.
@font-face는 사용할 수 있는 Font를 선언하는 규칙이지 해당 파일을 무조건 다운로드하는 명령이 아니기 때문이다.
실제 요소에서 해당 Font Family를 사용하지 않는다면 브라우저는 Font를 가져올 필요가 없다.
따라서 Network Panel에서도 문제가 발견되지 않았다.
웹과 모바일 패키지의 차이
웹에서는 해당 Font가 사용되지 않았으므로 네트워크 비용이 발생하지 않았다.
하지만 모바일 앱에서는 상황이 달랐다.
저자는 Capacitor를 이용해 웹 애플리케이션을 Android 및 iOS 앱으로 배포했다.
Capacitor는 웹 애플리케이션의 정적 파일을 모바일 앱 패키지에 포함할 수 있다.
Font 파일은 public/ 디렉터리에 존재했다.
따라서 실제 화면에서 사용되지 않더라도 정적 파일로 복사돼 앱 패키지에 포함됐다.
그 결과 다음 문제가 발생했다.
- 브라우저에서 실제 다운로드하는 Font 데이터: 0바이트
- 앱 패키지에 포함되는 불필요한 Font 파일: 5,857,164바이트
이는 웹페이지의 네트워크 성능과 모바일 앱의 설치 크기가 서로 다른 지표라는 점을 보여준다.
실제 Font 파일 분석
저자는 TTF 파일의 내부 구조를 분석했다.
결과는 다음과 같았다.
| 항목 | 측정 결과 |
|---|---|
| 파일 크기 | 5,857,164바이트 |
glyf Table | 5,696,434바이트 |
glyf 비중 | 97.3% |
| 전체 Glyph 수 | 7,016개 |
| 한자 Codepoint | 6,763개 |
| ASCII Codepoint | 95개 |
glyf Table은 글자의 실제 윤곽선 정보를 저장하는 부분이다.
따라서 Font 파일의 대부분이 글자 모양 데이터로 구성돼 있었다.
하지만 실제 애플리케이션은 6,763개의 한자를 모두 사용할 필요가 없었다.
실제 서비스에서 사용하는 문자 조사
저자는 애플리케이션의 데이터 파일과 소스코드에서 실제로 사용하는 한자를 추출했다.
결과는 다음과 같았다.
| 데이터 범위 | 서로 다른 한자 수 |
|---|---|
| 성씨와 이름 데이터 | 1,388개 |
| 모든 데이터 파일 | 4,196개 |
| 52개 소스 파일 전체 | 4,626개 |
| Font가 지원하는 한자 | 6,763개 |
이름에 사용하는 한자만 비교하면 Font의 전체 한자 가운데 약 20.5%가 필요했다.
전체 소스코드 기준으로 비교하면 약 68.4%였다.
따라서 실제 용도에 따라 필요한 Glyph의 범위가 달랐다.
저자는 이러한 결과를 바탕으로 Font Subsetting을 검토했다.
Font Subsetting은 전체 Font에서 실제로 필요한 문자만 남기는 작업이다.
WOFF2 Subsetting
저자는 FontTools를 이용해 실제 필요한 문자를 포함하는 WOFF2 파일을 생성했다.
결과는 다음과 같았다.
| 파일 | 크기 |
|---|---|
| 원본 TTF | 5,857,164바이트 |
| 전체 문자를 포함한 WOFF2 | 3,234,084바이트 |
| 필요한 이름 문자 중심의 Subset WOFF2 | 604,040바이트 |
원본 TTF와 Subset WOFF2를 비교하면 약 9.7배 차이가 발생했다.
하지만 Font를 Subset으로 줄였다고 해서 실제 서비스에서 사용하는 모든 문자가 반드시 포함되는 것은 아니다.
저자는 문자 지원 범위를 다시 검사했다.
그 과정에서 실제 데이터에 필요한 한자 두 개가 누락된 사실을 확인했다.
따라서 Subsetting 이후에도 실제 콘텐츠의 모든 문자를 표현할 수 있는지 검증해야 했다.
실제 공유 이미지에서는 다른 Font를 사용
저자는 또 다른 문제도 발견했다.
애플리케이션에는 공유용 이미지를 생성하는 기능이 있었다.
이 기능에서는 SVG로 텍스트를 생성한 뒤 이미지로 변환했다.
하지만 해당 SVG에서는 원래 Font를 사용하지 않았다.
대신 다음과 같은 Font Stack을 사용했다.
STSongti-SCSongti SCNoto Serif SCSimSun
문제는 이러한 Font가 실제 앱 패키지에 포함되지 않았다는 것이다.
따라서 기기와 운영체제에 따라 서로 다른 Font가 선택될 수 있었다.
또한 일부 이름은 이미 WebP 이미지로 사전 렌더링돼 있었다.
총 23개의 이름 이미지가 존재했다.
즉 하나의 기능에 다음 세 가지 방식이 혼합돼 있었다.
- 사용하지 않는 TTF 파일
- 기기별 시스템 Font에 의존하는 SVG
- 사전 렌더링된 WebP 이미지
저자는 이 구조를 정리할 필요가 있다고 판단했다.
CI 검사의 한계
저자는 사용되지 않는 Font를 발견하기 위해 소스코드에서 Font Family 이름을 검색하는 검사를 제안한다.
하지만 문자열 검색만으로 모든 사용 여부를 정확하게 판단할 수는 없다.
예를 들어 JavaScript에서 Font Family 이름을 동적으로 생성할 수 있다.
또한 외부 스타일 파일이나 런타임 데이터에서 Font를 사용할 수도 있다.
따라서 검색 결과가 없다는 사실만으로 항상 안전하게 파일을 삭제할 수 있는 것은 아니다.
실제 번들 구조와 동적인 사용 경로도 확인해야 한다.
이번 글은 브라우저에서 다운로드되지 않는 정적 파일도 모바일 앱 패키지나 배포 결과물에는 불필요한 크기를 추가할 수 있다는 점을 실제 파일 크기와 문자 분석으로 보여준다.
Are You a Web Developer or a Browser Expert? 7 Tests I Failed Building a Photo Editor That Never Uploads Your Photos
- 저자: Akhouri Anmol Kumar
- 매체: DEV Community
- 게시일: 2026-11-01
- 기술: JavaScript, WebAssembly, ONNX Runtime Web, Canvas, Cache API, Web Worker
- 원문: https://dev.to/akhourianmolkumar/are-you-a-web-developer-or-a-browser-expert-7-tests-i-failed-building-a-photo-editor-that-never-2j97
브라우저에서 이미지를 서버로 업로드하지 않고 배경 제거와 객체 제거, 압축, 색상 변환 등을 수행하는 이미지 편집기를 구현하면서 발견한 문제를 정리한 기술 글이다.
저자가 개발한 서비스는 APIC Web이다.
사용자는 이미지를 선택한다.
이후 브라우저 안에서 이미지 편집 작업을 수행한다.
실제 이미지 데이터는 별도의 백엔드 서버로 업로드하지 않는다.
이러한 구조는 개인정보 처리와 서버 비용을 줄이는 데 도움이 될 수 있다.
하지만 일반적인 JavaScript 애플리케이션과 다른 제약이 존재한다.
특히 AI 모델을 브라우저에서 직접 실행하는 과정이 복잡했다.
WebAssembly와 멀티스레드 제약
저자는 ONNX Runtime Web을 이용해 AI 모델을 실행했다.
ONNX Runtime Web은 브라우저에서 WebAssembly 등을 이용해 AI 모델을 실행할 수 있도록 하는 도구다.
하지만 WebAssembly의 멀티스레드 실행에는 브라우저의 보안 조건이 필요할 수 있다.
대표적으로 다음 기능이 관련된다.
SharedArrayBuffer
SharedArrayBuffer를 사용하려면 일반적으로 Cross-origin Isolation이 필요하다.
이를 위해 서버에서는 다음 Header를 구성할 수 있다.
- Cross-Origin-Opener-Policy
- Cross-Origin-Embedder-Policy
문제는 저자가 GitHub Pages에서 하나의 HTML 파일로 서비스를 제공하고 있었다는 점이다.
해당 배포 환경에서는 필요한 Header를 원하는 방식으로 구성할 수 없었다.
따라서 다음 값이 false였다.
crossOriginIsolated
데스크톱에서는 일부 기능이 정상적으로 동작했다.
하지만 모바일 브라우저에서는 AI 실행이 완료되지 않고 계속 대기하는 상황이 발생했다.
저자는 이를 해결하기 위해 다음 설정을 사용했다.
- WebAssembly Thread 수: 1
- Proxy 실행 비활성화
- 일부 메모리 최적화 비활성화
이렇게 하면 실행 성능의 일부 이점을 포기하는 대신 브라우저 지원 범위를 넓힐 수 있었다.
하지만 단일 Thread 실행이 모든 모바일 기기에서 높은 성능을 제공한다는 의미는 아니다.
실제 추론 속도는 모델 크기와 기기의 CPU, 메모리에 영향을 받는다.
모델 다운로드의 Timeout
AI 모델은 크기가 매우 클 수 있다.
저자가 사용한 모델의 크기는 다음과 같았다.
| 모델 | 크기 |
|---|---|
| 배경 제거 Lite | 약 5MB |
| 배경 제거 Fast | 약 44MB |
| 배경 제거 Maximum | 약 176MB |
| 고품질 객체 제거 | 약 208MB |
예를 들어 208MB 모델을 모바일 네트워크에서 다운로드한다고 가정해 보자.
전체 다운로드 시간이 30초를 초과할 수 있다.
이때 단순히 30초 Timeout을 설정하면 정상적인 느린 다운로드까지 실패 처리할 수 있다.
저자는 다른 방법을 선택했다.
전체 다운로드 시간 대신 실제 데이터가 도착하지 않는 시간을 검사한다.
40초 동안 새로운 Byte가 도착하지 않으면 요청을 중단한다.
반면 다운로드가 느리더라도 데이터가 계속 도착한다면 계속 기다릴 수 있다.
이는 느린 네트워크와 실제로 멈춘 연결을 구분하기 위한 방식이다.
하지만 이런 방식에도 제한사항이 있다.
매우 느린 속도로 데이터가 계속 도착한다면 다운로드가 장시간 지속될 수 있다.
따라서 실제 서비스에서는 전체 다운로드에 관한 별도 정책이 필요할 수 있다.
Cache API
저자는 다운로드한 모델을 Cache API에 저장한다.
이렇게 하면 같은 모델을 다음에 사용할 때 네트워크 요청을 반복하지 않아도 된다.
하지만 브라우저의 Cache 저장 공간은 무제한이 아니다.
또한 Private Browsing이나 저장 공간 제한으로 인해 저장에 실패할 수 있다.
따라서 Cache에 저장하는 작업도 실패할 수 있는 작업으로 처리한다.
Cache 저장에 실패했다고 해서 이미 다운로드한 모델의 사용까지 실패 처리하지 않는다.
이러한 방식은 Cache를 필수 데이터 저장소가 아닌 성능 최적화 수단으로 취급한 것이다.
AI 모델의 Fallback
객체 제거 작업에서는 큰 AI 모델을 사용할 수 있다.
하지만 모든 기기가 해당 모델을 안정적으로 실행할 수 있는 것은 아니다.
저자는 여러 모델과 간단한 처리 방식을 순차적으로 시도한다.
대표적인 순서는 다음과 같다.
- 고품질 AI 모델
- 상대적으로 가벼운 AI 모델
- 간단한 이미지 처리 Fallback
이렇게 하면 고품질 모델의 실행에 실패하더라도 일부 편집 기능을 제공할 수 있다.
하지만 Fallback의 품질은 같지 않다.
간단한 이미지 처리 방식은 복잡한 배경을 자연스럽게 복원하는 데 한계가 있다.
따라서 사용자에게 제공되는 결과의 품질은 실제 기기의 실행 환경에 따라 달라질 수 있다.
목표 파일 크기에 맞춘 이미지 압축
저자는 사용자가 다음과 같은 요구사항을 지정할 수 있도록 했다.
200KB 이하로 압축
문제는 JPEG 품질 값을 단순히 낮춘다고 해서 모든 이미지가 목표 크기에 들어가는 것은 아니라는 점이다.
예를 들어 해상도가 매우 큰 이미지는 낮은 JPEG 품질에서도 목표 크기를 초과할 수 있다.
저자는 이를 해결하기 위해 두 단계를 사용했다.
첫 번째는 JPEG 품질을 조정하는 것이다.
Binary Search 방식으로 품질 값을 변경하면서 실제 파일 크기를 확인한다.
두 번째는 이미지 해상도를 줄이는 것이다.
품질을 충분히 낮췄는데도 목표 크기를 초과한다면 이미지의 가로와 세로를 축소한다.
이후 압축을 다시 수행한다.
하지만 JPEG로 변환할 때는 투명도도 고려해야 한다.
투명한 PNG 이미지를 JPEG로 변환하면 투명한 영역이 검게 표시될 수 있기 때문이다.
저자는 변환 전에 흰색 배경을 명시적으로 그리도록 처리했다.
이미지의 특정 영역 제거
객체 제거에서는 이미지 전체를 모델에 전달하지 않았다.
대신 사용자가 선택한 영역을 중심으로 필요한 부분을 잘라냈다.
이후 해당 영역을 AI 모델에 전달했다.
예를 들어 4000×3000 이미지에서 작은 물체 하나를 제거한다고 가정해 보자.
전체 이미지를 고해상도로 처리하면 많은 메모리와 계산이 필요할 수 있다.
하지만 실제 수정할 영역은 일부에 불과하다.
따라서 관련 영역만 추출해 모델을 실행하면 필요한 처리량을 줄일 수 있다.
이후 결과를 원본 이미지에 다시 합성한다.
이 과정에서는 다음 문제가 존재한다.
- 수정 영역과 원본의 경계
- 밝기 차이
- 질감의 차이
- 주변 노이즈와 Grain
저자는 경계를 부드럽게 합성하기 위한 Feathering 등의 기법을 사용했다.
하지만 원본 이미지의 모든 질감과 구조를 완벽하게 복원할 수 있는 것은 아니다.
3D LUT와 효과 표현의 한계
이번 이미지 편집기에서는 색상 변환에 3D LUT를 사용한다.
LUT(Lookup Table)는 입력 색상에 대응하는 출력 색상을 정의한 데이터다.
예를 들어 특정 RGB 색상을 다른 RGB 색상으로 변환할 수 있다.
저자는 33×33×33 크기의 색상 격자를 이용했다.
하지만 LUT는 주로 색상 변환을 표현한다.
다음 작업은 같은 방식으로 완전히 표현할 수 없다.
- Sharpening
- Blur
- Grain
- 공간적인 이미지 효과
따라서 색상 조정과 공간적인 이미지 처리를 구분해야 한다.
구현의 한계
이번 글은 브라우저에서 실제로 발견한 문제와 해결 방법을 설명한다.
하지만 모든 기능에 대한 독립적인 성능 Benchmark를 제공하지는 않는다.
특히 AI 모델의 실제 실행 속도는 기기의 CPU와 메모리, WebAssembly 지원 범위에 영향을 받는다.
또한 사진을 서버에 업로드하지 않는다는 사실이 브라우저 안에서 처리하는 모든 데이터가 자동으로 안전하다는 의미는 아니다.
실제 애플리케이션이 로딩하는 외부 Script와 모델 파일, 브라우저의 보안 정책도 고려해야 한다.
이번 글은 브라우저의 보안 제약과 메모리 제한, 이미지 형식, AI 모델 실행 방식이 실제 웹 애플리케이션의 구조를 어떻게 결정하는지 일곱 가지 구현 사례로 설명한다.
I grepped for @ts-ignore and found zero. Then I grepped for the cast nobody bans
- 저자: chet100
- 매체: DEV Community
- 게시일: 2026-11-01
- 기술: TypeScript, ESLint, CI, Runtime Validation
- 원문: https://dev.to/chet103cc/i-grepped-for-ts-ignore-and-found-zero-then-i-grepped-for-the-cast-nobody-bans-723
실제 TypeScript Repository에서 명시적인 타입 검사 무시 주석은 존재하지 않았지만, 타입 시스템을 사실상 우회하는 이중 타입 단언이 117건 존재했다는 사실을 조사한 글이다.
이번 글은 TypeScript 문법을 단순히 설명하는 자료가 아니다.
저자는 실제 코드베이스를 검색했다.
그리고 어떤 파일에 타입 우회 코드가 많이 존재하는지 확인했다.
또한 이러한 코드가 반복해서 생성되는 이유와 앞으로 증가하지 않도록 관리하는 방법을 제안했다.
처음에는 타입 검사가 잘 관리되는 프로젝트처럼 보였다
저자는 약 610개의 TypeScript 소스 파일을 확인했다.
먼저 다음 문자열을 검색했다.
@ts-ignore
결과는 0건이었다.
이 주석은 TypeScript에서 다음 줄의 오류를 무시하도록 할 때 사용할 수 있다.
따라서 팀에서는 사용을 제한하는 경우가 많다.
저자의 프로젝트에서도 해당 주석을 사용하지 않도록 관리하고 있었다.
하지만 다른 표현을 검색하자 결과가 달라졌다.
대표적인 표현은 다음과 같다.
as unknown as
이 표현은 총 117건 발견됐다.
실제 조사 결과
| 표현 | 발견 횟수 |
|---|---|
@ts-ignore | 0 |
@ts-expect-error | 0 |
as any | 3 |
as unknown as | 117 |
eslint-disable 계열 | 53 |
console.log | 4 |
저자는 이러한 결과를 바탕으로 타입 검사를 우회하는 방식이 주석에서 타입 단언으로 이동했을 수 있다고 설명한다.
하지만 117건의 코드가 모두 실제 버그라는 의미는 아니다.
TypeScript에서는 일부 동적 데이터나 외부 라이브러리의 타입 문제를 처리하기 위해 타입 단언이 필요할 수 있다.
중요한 것은 실제 데이터의 구조를 확인하지 않은 상태에서 컴파일러에게 특정 타입이라고 믿도록 만드는 코드가 증가하고 있다는 점이다.
이중 타입 단언의 의미
예를 들어 다음 표현을 생각할 수 있다.
value as unknown as User
첫 번째 단언에서는 value를 unknown으로 취급한다.
두 번째 단언에서는 해당 값을 다시 User 타입이라고 선언한다.
이러한 방식은 TypeScript의 일부 타입 호환성 검사를 우회할 수 있다.
하지만 실제 JavaScript Runtime에서 데이터가 변경되는 것은 아니다.
예를 들어 다음 객체가 있다고 가정해 보자.
{ name: 123 }
이 객체를 User 타입이라고 단언하더라도 name의 실제 값은 숫자다.
TypeScript의 타입 단언은 Runtime Validation이 아니기 때문이다.
따라서 이중 타입 단언으로 컴파일 오류를 제거하더라도 실제 데이터의 구조가 올바른지 검증해야 한다.
특정 파일에 집중된 타입 단언
저자는 이중 타입 단언이 어떤 파일에 존재하는지 조사했다.
대표적인 결과는 다음과 같았다.
| 파일 | 발견 횟수 |
|---|---|
api/src/routes/admin.ts | 14 |
api/src/stories/worker.ts | 11 |
api/src/app.ts | 7 |
api/src/assistant/worker.ts | 6 |
web/src/lib/messages/index.ts | 5 |
흥미로운 점은 이러한 파일들이 외부 데이터와 내부 타입 시스템이 만나는 위치에 집중돼 있었다는 사실이다.
대표적으로 다음 작업이 포함된다.
- HTTP Request Body 처리
- 데이터베이스에서 가져온 데이터 변환
- Worker의 직렬화된 데이터 복원
- 동적인 i18n Key 처리
이러한 곳에서는 실제 데이터가 TypeScript의 정적인 타입 정의와 정확하게 일치하지 않을 수 있다.
따라서 개발자는 타입 오류를 해결하기 위해 단언을 추가하고 싶어질 수 있다.
하지만 타입 단언을 추가하면 원래 데이터가 올바른지 검증해야 할 책임이 사라지는 것은 아니다.
AI 코딩 에이전트와 타입 단언
저자는 AI 코딩 에이전트가 이러한 표현을 선택하기 쉬운 이유도 설명한다.
예를 들어 에이전트에게 다음 작업을 요청했다고 가정해 보자.
TypeScript 오류를 수정해 줘.
에이전트는 여러 방법 가운데 하나를 선택할 수 있다.
첫 번째는 실제 데이터 구조를 분석하고 타입 정의를 수정하는 것이다.
두 번째는 Runtime Validation을 추가하는 것이다.
세 번째는 이중 타입 단언으로 오류를 제거하는 것이다.
세 번째 방법은 코드 수정량이 작다.
또한 컴파일러 오류를 빠르게 제거할 수 있다.
따라서 에이전트가 테스트 통과나 컴파일 성공만을 목표로 작업한다면 해당 표현을 선택할 가능성이 있다.
하지만 실제 데이터의 안전성이 개선된 것은 아니다.
저자는 이를 방지하기 위해 타입 단언을 코드 리뷰에서 별도로 확인하도록 제안한다.
CI에서 증가량 제한
저자는 기존 117건을 즉시 모두 제거하지 않았다.
대신 다음 파일을 이용해 현재 허용량을 관리한다.
.cast-budget
CI에서는 실제 이중 타입 단언의 수를 계산한다.
그리고 이전에 기록한 수보다 증가했는지 확인한다.
이러한 방식을 Ratchet이라고 할 수 있다.
예를 들어 기존에 117건이 존재했다고 가정해 보자.
새 PR에서 3건을 제거했다.
이후 허용량을 114건으로 낮춘다.
다음 PR에서는 다시 117건으로 증가하지 못하도록 한다.
이렇게 하면 기존 문제를 한 번에 모두 수정하지 않더라도 점진적으로 줄일 수 있다.
하지만 문자열 검색만으로 모든 이중 타입 단언을 발견할 수 있는 것은 아니다.
예를 들어 여러 줄에 나누어 작성한 코드나 다른 문법 구조는 단순한 grep에서 누락될 수 있다.
따라서 더 정확한 검사가 필요하다면 TypeScript AST 기반의 Lint 규칙을 고려할 수 있다.
실제 데이터 경계에서의 해결 방법
저자는 이중 타입 단언을 제거하는 방법을 데이터의 종류에 따라 구분한다.
첫 번째는 외부 JSON이다.
이 경우 Zod 같은 Schema Validation 도구로 실제 데이터를 검증할 수 있다.
두 번째는 데이터베이스 결과다.
이 경우 데이터베이스의 Row를 실제 애플리케이션의 Domain Model로 변환하는 Mapper를 사용할 수 있다.
세 번째는 i18n Key다.
이 경우 satisfies를 이용해 허용된 Key와 Value의 구조를 검사할 수 있다.
하지만 모든 타입 단언을 무조건 금지하는 것이 적절한 것은 아니다.
실제 라이브러리의 타입 정보가 부족하거나 테스트 Double을 생성하는 상황에서는 제한적인 예외가 필요할 수 있다.
중요한 것은 해당 예외의 이유를 명시하는 것이다.
조사 결과의 한계
이번 글의 수치는 하나의 실제 Repository를 대상으로 한다.
따라서 모든 TypeScript 프로젝트에서 비슷한 비율의 이중 타입 단언이 존재한다고 판단할 수는 없다.
또한 117건이라는 숫자만으로 실제 Runtime 오류의 발생률을 추정할 수 없다.
실제 위험은 각 타입 단언이 어떤 데이터에 적용되고 해당 데이터가 얼마나 신뢰할 수 있는지에 따라 달라진다.
이번 글은 TypeScript 컴파일이 성공한다는 사실과 Runtime에서 실제 데이터가 올바른 구조를 가진다는 사실을 구분해야 한다는 점을 코드베이스 조사와 CI 정책으로 설명한다.
Constraining agent CSS to design tokens (without pasting the whole theme)
- 저자: Sherotree
- 매체: DEV Community
- 게시일: 2026-11-02 — 정확한 게시 시각 미확인
- 기술: CSS, Tailwind CSS, Design Tokens, AI Coding Agent, CI
- 원문: https://dev.to/sherotree/constraining-agent-css-to-design-tokens-without-pasting-the-whole-theme-19o8
AI 코딩 에이전트에게 디자인 시스템의 전체 CSS 파일을 제공했는데도 기존 규칙에 없는 색상과 간격을 계속 생성했던 문제를 해결하기 위해 제한된 Token 목록과 CI 검사를 함께 사용한 사례다.
이번 글은 디자인 시스템을 AI에게 설명하는 방법보다 어떤 값이 허용되지 않는지 명확하게 정의하는 방법에 초점을 맞춘다.
디자인 시스템의 기본 구조
일반적인 웹 애플리케이션에서는 여러 종류의 스타일 값을 반복해서 사용한다.
대표적으로 다음과 같다.
- 색상
- 간격
- Border Radius
- Shadow
- Font Size
- Font Weight
예를 들어 Button과 Card, Dialog에서 서로 다른 Blue 색상을 사용하면 화면의 일관성이 깨질 수 있다.
따라서 디자인 시스템에서는 이러한 값을 Token으로 관리한다.
대표적인 CSS 변수는 다음과 같다.
--color-accent-primary
--space-4
--radius-md
컴포넌트에서는 실제 색상 코드나 간격 숫자를 직접 작성하는 대신 해당 Token을 참조한다.
이렇게 하면 전체 서비스의 디자인을 일관되게 유지하기 쉽다.
AI에게 전체 Theme을 제공한 결과
저자는 처음에 디자인 시스템의 전체 CSS 파일을 AI 코딩 에이전트에게 제공했다.
파일은 약 800줄이었다.
그리고 새로운 설정 화면을 디자인 시스템에 맞게 구현하도록 요청했다.
하지만 결과는 예상과 달랐다.
AI는 기존 Token과 비슷한 새로운 값을 생성했다.
예를 들어 다음과 같은 값이다.
#3B82F6padding: 18px--card-shadow-softbg-[#0f172a]
이러한 값은 기존 디자인 시스템과 비슷하게 보일 수 있다.
하지만 실제로는 허용된 Token 목록에 존재하지 않는다.
따라서 화면 전체의 디자인 일관성을 유지하기 어렵다.
저자는 원인을 다음과 같이 분석했다.
AI에게 존재하는 Token을 알려주는 것과 존재하지 않는 값을 사용하지 말라고 제한하는 것은 서로 다른 작업이다.
전체 Theme 대신 작은 허용 목록
저자는 약 40~60줄의 Token 안내문을 작성했다.
여기에는 실제 사용해도 되는 값만 포함했다.
예를 들어 다음과 같은 구조다.
| 분류 | 허용 값 예시 |
|---|---|
| Color | bg.default, fg.muted, accent.primary |
| Space | 1, 2, 3, 4, 5, 6, 8, 10, 12 |
| Radius | sm, md, lg, full |
| Shadow | sm, md, lg |
| Font Size | sm, md, lg, xl |
이후 AI에게 해당 목록에 있는 값만 사용하도록 지시했다.
특히 다음 작업을 금지했다.
- 임의의 Hex 색상 생성
- 허용되지 않은 Pixel 간격 사용
- 새로운 CSS Custom Property 생성
- Tailwind의 Arbitrary Value 사용
예를 들어 디자인 시안에 18px 간격이 존재하지만 실제 Token에는 해당 값이 없다고 가정해 보자.
AI가 임의로 18px을 사용하지 않도록 한다.
대신 가장 가까운 기존 Token을 사용할 수 있는지 확인하거나 새로운 Token이 필요하다고 보고하도록 한다.
Closest-token 규칙
저자는 디자인 시안에 허용되지 않은 값이 존재할 때 무조건 가장 비슷한 값을 선택하도록 하지 않았다.
예를 들어 18px 간격이 필요하지만 실제 Token에는 16px과 20px만 존재한다고 가정해 보자.
이때 AI가 18px을 임의로 추가하면 디자인 시스템이 점점 복잡해질 수 있다.
반대로 무조건 16px을 사용하면 실제 디자인 요구사항과 달라질 수 있다.
따라서 저자는 허용된 Token으로 구현할 수 없는 요구사항을 명시적으로 보고하도록 했다.
이렇게 하면 실제 디자인 시스템을 확장해야 하는지 사람이 검토할 수 있다.
Prompt만으로는 부족한 이유
AI가 특정 규칙을 따르도록 Prompt를 작성하더라도 항상 지킨다는 보장은 없다.
따라서 저자는 실제 코드의 변경 내용을 검사하는 Script를 추가했다.
대표적으로 다음 패턴을 검색한다.
- Hex Color
- RGB 및 HSL 함수
- 임의의 Pixel 값
- Tailwind Arbitrary Value
예를 들어 다음 코드가 존재하면 검사에 실패하도록 할 수 있다.
bg-[#0f172a]
또는 다음과 같은 값이다.
padding: 18px
이러한 규칙은 코드 리뷰 전에 실행할 수 있다.
따라서 AI가 디자인 시스템에 없는 값을 생성하더라도 CI에서 발견할 가능성이 높아진다.
검사 예외
모든 원시 CSS 값이 반드시 잘못된 것은 아니다.
예를 들어 다음과 같은 경우가 존재할 수 있다.
- 1px Border
- SVG의 실제 좌표
- 특정 브라우저 버그에 대응하는 CSS
- 외부 라이브러리와의 호환성 처리
따라서 저자는 제한적인 예외를 허용한다.
하지만 예외를 추가할 때는 이유를 설명하도록 한다.
예를 들어 특정 1px Border가 필요한 이유를 주석으로 남길 수 있다.
이러한 방식은 모든 예외를 제거하기보다 실제 예외가 왜 존재하는지 검토할 수 있도록 하는 접근이다.
PR Template
저자는 PR에도 디자인 시스템 검사 항목을 추가했다.
대표적인 항목은 다음과 같다.
- 허용되지 않은 색상과 간격을 사용하지 않았는가?
- 새로운 Token을 임의로 추가하지 않았는가?
- 기존 Token으로 구현할 수 없는 요구사항을 기록했는가?
- 관련 CI 검사를 통과했는가?
이렇게 하면 코드 리뷰가 개인의 디자인 취향을 평가하는 과정에만 의존하지 않는다.
실제로 정의된 규칙을 충족하는지 확인할 수 있다.
구현의 한계
이번 글에는 도입 전후의 디자인 오류 발생률이나 AI의 정확도 개선을 측정한 정량적인 Benchmark가 존재하지 않는다.
따라서 40~60줄 안내문이 모든 프로젝트에서 가장 적합하다고 단정할 수는 없다.
또한 단순한 정규식 검사는 실제 CSS의 문법 구조를 완벽하게 이해하지 못한다.
예를 들어 문자열 안에 존재하는 색상 값이나 동적인 CSS 표현식 때문에 오탐과 누락이 발생할 수 있다.
보다 정확한 검사가 필요하다면 CSS Parser나 ESLint 기반 규칙을 사용할 수 있다.
또한 이번 방식은 디자인 시스템에 이미 존재하는 Token을 강제하는 데 적합하다.
디자인 시스템 자체를 새롭게 설계하는 작업에는 다른 접근이 필요할 수 있다.
이번 글은 AI 코딩 에이전트에게 디자인 시스템을 단순히 참고 자료로 제공하는 것보다 허용된 값과 금지된 값을 명확하게 정의하고 실제 코드에서 검증하는 구조가 중요하다는 점을 설명한다.
Securing an Angular Firebase AI App with Email/Password Authentication (Part 1: Emulator, AuthService, and Route Guard) [GDE]
- 저자: Connie Leung
- 매체: Google Developer Experts / DEV Community
- 게시일: 2026-11-02 — 정확한 게시 시각 미확인
- 기술: Angular, Firebase Authentication, Signals, Functional Route Guard
- 원문: https://dev.to/gde/securing-an-angular-firebase-ai-app-with-emailpassword-authentication-part-1-emulator-4jm
실제 Angular 애플리케이션에 Firebase Authentication을 추가하면서 초기 JavaScript 크기를 줄이고 인증 상태를 Signal로 관리하며, 인증되지 않은 사용자의 특정 Route 접근을 제한하는 방법을 설명한 글이다.
저자가 구현한 애플리케이션에는 AI 기반 기능이 존재한다.
대표적으로 다음 작업을 수행할 수 있다.
- 이미지 분석
- Text-to-Speech
하지만 누구나 공개 URL에 접근해 해당 기능을 사용할 수 있다면 AI API의 사용량과 비용이 증가할 수 있다.
따라서 저자는 인증되지 않은 사용자가 주요 기능을 사용하는 상황을 제한하려고 했다.
이번 글은 전체 인증 및 보안 시스템의 완성본이 아니다.
Firebase Auth Emulator와 Angular의 인증 상태 관리, 클라이언트 Route Guard를 구성하는 첫 번째 단계를 설명한다.
Firebase Auth Emulator
저자는 실제 Firebase Authentication 서비스 대신 로컬 Emulator를 사용해 인증 기능을 개발했다.
대표적인 Port는 다음과 같다.
| 구성요소 | Port |
|---|---|
| Angular 개발 서버 | 4200 |
| Firebase Auth Emulator | 9099 |
| Firebase Emulator UI | 4000 |
Emulator를 사용하면 실제 운영 사용자 계정을 생성하지 않고도 로그인 흐름을 테스트할 수 있다.
예를 들어 다음 상황을 검증할 수 있다.
- 올바른 이메일과 비밀번호
- 잘못된 비밀번호
- 존재하지 않는 사용자
- 로그아웃
- 인증 상태 복원
하지만 로컬 Emulator에서 테스트를 통과했다고 해서 실제 운영 Firebase 프로젝트의 모든 인증 설정이 올바르다는 의미는 아니다.
운영 환경에서는 실제 인증 제공업체 설정과 보안 규칙을 별도로 확인해야 한다.
Session Persistence
저자는 다음 Firebase 설정을 사용했다.
browserSessionPersistence
이 설정은 브라우저 Session 단위로 인증 상태를 유지하도록 한다.
대표적으로 sessionStorage를 사용할 수 있다.
사용자가 로그인한 뒤 페이지를 새로고침해도 인증 상태를 유지할 수 있다.
반면 브라우저 탭을 종료하면 해당 Session의 인증 정보가 제거될 수 있다.
이는 Local Persistence와 다른 방식이다.
Local Persistence에서는 브라우저를 다시 열었을 때도 로그인 상태가 유지될 수 있다.
따라서 어떤 방식을 사용할지는 실제 서비스의 사용자 경험과 보안 요구사항에 따라 달라진다.
저자는 AI 기능에 대한 무제한 접근을 줄이려는 목적에서 Session 단위의 인증 상태를 선택했다.
Firebase SDK의 Lazy Loading
Firebase Authentication SDK는 다양한 인증 기능을 제공한다.
하지만 모든 Angular 페이지에서 해당 기능이 필요한 것은 아니다.
예를 들어 단순한 소개 페이지에서는 인증 상태를 즉시 확인할 필요가 없을 수 있다.
저자는 다음 Module을 Dynamic Import로 가져오도록 했다.
firebase/auth
이렇게 하면 애플리케이션의 초기 Bundle에 모든 인증 코드를 포함하지 않고 실제 필요한 시점에 로딩할 수 있다.
하지만 Dynamic Import는 최초 사용 시 추가적인 다운로드와 초기화가 발생할 수 있다.
따라서 초기 JavaScript 크기와 실제 로그인 UI가 준비되는 시간 사이에 트레이드오프가 존재한다.
ensureAuth()와 Single-flight
비동기 모듈을 로딩할 때는 여러 컴포넌트가 같은 초기화 작업을 동시에 요청할 수 있다.
예를 들어 다음 두 작업이 거의 동시에 실행된다고 가정해 보자.
- Login Form에서 AuthService 초기화
- Route Guard에서 인증 상태 확인
각 작업이 독립적으로 Firebase SDK를 초기화하면 불필요한 중복 작업이 발생할 수 있다.
저자는 이를 방지하기 위해 다음 함수를 사용했다.
ensureAuth()
이 함수는 초기화 작업을 Promise로 관리한다.
이미 초기화가 진행 중이라면 새로운 작업을 시작하지 않고 기존 Promise를 재사용한다.
이러한 구조를 Single-flight라고 부를 수 있다.
동시에 같은 작업을 요청하더라도 하나의 실행 결과를 공유하는 방식이다.
하지만 초기화가 실패한 경우에는 다음 재시도를 어떻게 처리할지도 고려해야 한다.
실패한 Promise를 영구적으로 재사용한다면 이후 정상적인 초기화도 계속 실패할 수 있기 때문이다.
Angular Signal로 인증 상태 관리
저자는 Angular Signal을 이용해 인증 상태를 관리한다.
대표적으로 다음 값을 사용한다.
- 현재 사용자
- 인증 완료 여부
Firebase Authentication에서는 다음 함수로 인증 상태의 변화를 관찰할 수 있다.
onAuthStateChanged()
이 Callback이 실행되면 현재 사용자 정보를 Angular Signal에 반영한다.
이후 다른 컴포넌트에서는 해당 Signal을 사용할 수 있다.
예를 들어 다음 상태를 계산할 수 있다.
isAuthenticated
이 값은 현재 사용자 객체가 존재하는지에 따라 계산된다.
이러한 구조에서는 여러 컴포넌트가 Firebase의 인증 상태를 개별적으로 구독할 필요가 줄어든다.
Functional Route Guard
저자는 Angular Router에서 다음 기능을 사용했다.
CanActivateFn
Functional Route Guard는 특정 Route로 이동하기 전에 접근 가능 여부를 판단하는 함수다.
예를 들어 /dashboard를 인증된 사용자에게만 제공한다고 가정해 보자.
사용자가 해당 Route에 접근하려고 하면 Guard가 인증 상태를 확인한다.
인증된 사용자라면 Route를 활성화한다.
인증되지 않은 사용자라면 로그인 화면으로 이동하도록 구성할 수 있다.
하지만 여기에는 중요한 보안상 제한사항이 있다.
Angular의 Route Guard는 클라이언트에서 실행되는 코드다. 실제 서버의 인증 및 인가를 대체하지 않는다.
예를 들어 사용자가 브라우저의 JavaScript를 직접 수정하거나 HTTP 요청을 직접 전송할 수 있다.
따라서 Route Guard에서 버튼이나 페이지를 숨긴다는 사실만으로 실제 AI API의 무단 사용을 방지할 수는 없다.
실제 AI 모델 요청을 처리하는 서버에서도 Firebase ID Token을 검증해야 한다.
또한 필요한 사용자 권한과 사용량 제한을 별도로 확인해야 한다.
이번 글의 적용 범위
이번 글은 인증 구조의 첫 번째 단계다.
따라서 다음 기능까지 모두 완성했다고 해석해서는 안 된다.
- 서버 측 Firebase Token 검증
- 사용자별 AI API 사용량 제한
- 결제 및 요금제별 권한 관리
- 실제 API Key의 접근 제어
- 운영 환경의 Abuse Protection
특히 공개된 API Key나 실제 AI 서비스의 사용량을 보호하려면 클라이언트 Route Guard만으로는 부족하다.
이번 글에는 인증 기능 도입 전후의 Bundle 크기나 초기 로딩 시간에 관한 정량적인 Benchmark가 포함되지 않았다.
따라서 Dynamic Import가 실제로 몇 KB를 줄였는지 단정할 수 없다.
이번 글은 Angular의 Signal과 비동기 모듈 로딩, Firebase Authentication, Functional Route Guard를 하나의 인증 흐름으로 연결하면서 클라이언트 인증 상태를 어떻게 일관되게 관리할 수 있는지 설명하는 기술 자료다.