기업 환경에서 Snyk Code 기반의 정적 애플리케이션 보안 테스팅(SAST)을 구축할 때 애플리케이션 보안(AppSec) 팀과 보안 아키텍트가 직면하는 가장 큰 고충은 사내 전용 라이브러리로 인한 과도한 오탐(False Positive)과 개발팀의 알림 피로(Alert Fatigue)입니다. Snyk Code는 AI 기반 심볼릭 실행 기술인 DeepCode 엔진을 통해 신뢰할 수 없는 데이터의 유입 지점인 소스(Source)부터 시스템의 치명적인 실행 지점인 싱크(Sink)까지의 오염 흐름(Taint Flow)을 파일 간에 걸쳐 추적합니다.
그러나 개발팀이 자체 구축한 보안 SDK, 사내 공통 입력값 검증 클래스, 커스텀 이스케이프 유틸리티 등을 사용할 경우, 기본 SAST 엔진은 해당 함수가 악성 페이로드를 완벽히 정화하는지 스스로 인지하지 못합니다. 그 결과 실제로는 안전하게 방어된 코드임에도 SQL 인젝션, 크로스 사이트 스크립팅(XSS), 경로 순회(Path Traversal)와 같은 고위험 알림이 PR(Pull Request)마다 지속적으로 발생합니다.
웹 콘솔에서 수동으로 알림을 무시하거나 개별 리포지토리에 .snyk 정책 파일을 추가하는 임시방편은 운영 비용이 과도할 뿐 아니라 실제 취약점을 가려버릴 위험이 큽니다. Snyk은 Enterprise 요금제 사용자를 위해 이러한 문제를 근본적으로 해결하는 규칙 확장(Rule Extensions) 기능을 제공합니다. 사내 함수의 완전 정규화 이름(Fully Qualified Name, FQN)을 특정 보안 규칙 키와 바인딩하고 정확한 새니타이저 동작 유형을 정의함으로써, 전체 조직에 걸쳐 오탐을 체계적으로 제거하면서도 실제 취약점에 대한 탐지력을 완벽히 유지할 수 있습니다.
오염 흐름 분석과 오탐 발생의 메커니즘
커스텀 새니타이저를 올바르게 구성하려면 Snyk Code 엔진이 오염 흐름을 평가하는 기본 원리를 이해해야 합니다.
[소스 (Source): 신뢰할 수 없는 외부 입력값]
│
▼
[사내 자체 보안 검증 함수] ──► (규칙 확장이 없으면 엔진에는 불투명한 블랙박스)
│
▼ (오염 플래그가 유지된 채 전파)
[싱크 (Sink): 취약점이 발생하는 실행부] ──► 오탐(False Positive) 알림 발생
- 소스(Source): HTTP 요청 파라미터, 헤더, 파일 업로드 등 외부 데이터가 유입되는 경계면.
- 전파 경로(Propagation Path): 변수 할당, 함수 인자 전달, 객체 변환 등을 거치며 여러 파일로 데이터가 이동하는 경로.
- 새니타이저(Sanitizer): 특수문자 이스케이프, 필터링, 화이트리스트 검증을 통해 위험 데이터를 안전하게 정화하는 방어 함수.
- 싱크(Sink): 정화되지 않은 데이터가 도달했을 때 치명적인 보안 취약점을 유발하는 함수(SQL 쿼리 실행, DOM 출력, 파일 시스템 접근 등).
기본 제공되는 SAST 분석 모델은 언어 표준 라이브러리와 널리 알려진 오픈소스 패키지만을 사전에 인식합니다. 따라서 MyOrg.Security.Encoder.CleanHtml이나 PathValidator.assertSafePath와 같은 사내 전용 클래스를 통과하더라도 엔진은 오염이 제거되지 않았다고 간주하여 싱크 도달 시 즉시 알림을 발생시킵니다.
기존 예외 처리 방식의 한계
- 웹 콘솔 개별 무시(Ignore): 현재 스냅샷에만 유효하며, 다른 프로젝트나 신규 브랜치에서 동일한 코드가 사용되면 다시 알림이 발생하여 지속적인 인력 낭비를 유발합니다.
.snyk정책 파일 분산 관리: 수백 개의 저장소마다 개별 파일을 유지보수하기 어렵고, 개발자가 광범위한 와일드카드를 적용해 실제 보안 위협까지 덮어버릴 위험이 있습니다.- 전체 검사 규칙 비활성화: 개발팀의 불만을 줄이기 위해 XSS 등의 규칙 자체를 꺼버리는 것은 보안 가시성을 포기하는 치명적인 선택입니다.
규칙 확장은 엔진 레벨에서 사내 함수를 공인된 새니타이저로 등록하므로, 정책이 적용된 모든 프로젝트에서 오염 전파 체인이 자동으로 차단됩니다.
4가지 새니타이저 동작 모델과 시맨틱 매핑
보안 함수는 구조와 역할에 따라 데이터를 처리하는 방식이 상이합니다. Snyk Code 규칙 확장은 4가지 구체적인 새니타이징 유형(REST API의 extension_type)을 지원합니다.
| 새니타이징 유형 | API extension_type | 엔진의 동작 평가 방식 | 대표적인 적용 사례 |
|---|---|---|---|
| 플로우 스루 (Flow Through) | flows_through_sanitizer | 함수를 거쳐 반환되는 모든 데이터를 정화된 것으로 간주. 입력값 상태와 무관하게 반환값은 항상 안전. | HTML 엔티티 이스케이프, URL 인코딩 등 안전한 문자열을 반환하는 유틸리티. |
| 참일 때 (If True) | if_true_sanitizer | 함수의 반환값이 true로 평가되는 조건문 분기 내에서만 전달된 인자를 안전한 것으로 간주. | 불리언 검증 함수, 정규식 화이트리스트 검사(예: isAlphaNumeric). |
| 거짓일 때 (If False) | if_false_sanitizer | 함수의 반환값이 false로 평가되는 조건문 분기 내에서만 전달된 인자를 안전한 것으로 간주. | 블랙리스트 필터링, 악성 프로토콜 검사(예: containsJavascriptProtocol). |
| 모든 사용 (Any Usage) | any_usage_sanitizer | 함수 실행이 완료된 직후부터 인자를 안전한 것으로 간주. 유효하지 않은 입력 시 예외를 던지거나 객체를 직접 수정하는 함수에 적합. | 규칙 위반 시 런타임 예외를 발생시켜 실행을 중단하는 단언(Assertion) 함수. |
1. 플로우 스루 (Flow Through)
오염된 입력을 받아 정제된 새로운 값을 반환하는 변환형 유틸리티에 사용합니다.
package com.company.utils;
public class SecurityUtils {
public static String escapeHtml(String data) {
if (data == null) return "";
return data.replace("<", "<").replace(">", ">");
}
}
이 함수를 XSS 규칙에 대해 flows_through_sanitizer로 등록하면, 반환값이 HTTP 응답 출력부로 흘러 들어가도 취약점으로 보고되지 않습니다.
2. 참일 때 (If True)
조건문에서 입력값의 안전성을 검사하는 불리언 함수에 적용합니다.
package com.company.utils;
public class SecurityUtils {
public static boolean isAlphaNumeric(String data) {
return data != null && data.matches("^[a-zA-Z0-9]*$");
}
}
비즈니스 로직 적용 예:
if (SecurityUtils.isAlphaNumeric(username)) {
// 참인 조건 분기 내에서 오염이 제거되어 파일 접근이 안전함
File userProfile = new File("/data/profiles/" + username);
} else {
// else 블록에서는 여전히 오염 상태로 평가됨
response.sendError(400, "Invalid username");
}
3. 거짓일 때 (If False)
악성 패턴 여부를 판별하는 부정형 가드 함수에 적용합니다.
package com.company.utils;
public class SecurityUtils {
public static boolean containsJavascriptProtocol(String data) {
if (data == null) return false;
return data.toLowerCase().trim().startsWith("javascript:");
}
}
비즈니스 로직 적용 예:
if (!SecurityUtils.containsJavascriptProtocol(redirectUrl)) {
// 반환값이 false일 때 오염이 해제되어 안전한 리다이렉트 수행
response.sendRedirect(redirectUrl);
} else {
response.sendError(400, "Invalid redirect URL");
}
4. 모든 사용 (Any Usage)
유효하지 않은 데이터 입력 시 예외를 발생시켜 프로세스를 중단시키는 단언 함수에 적용합니다.
package com.company.utils;
public class SecurityUtils {
public static void validateFileId(String fileId) {
if (fileId == null || !fileId.matches("^[a-zA-Z0-9_-]+$")) {
throw new IllegalArgumentException("Invalid File ID format");
}
}
}
비즈니스 로직 적용 예:
// 입력값이 잘못된 경우 즉시 예외 발생 및 흐름 중단
SecurityUtils.validateFileId(fileId);
// 정상 통과 시 다음 라인부터 fileId는 안전한 상태로 취급됨
File file = new File("/data/uploads/" + fileId);
보안 주의사항: 예외를 던지지도 않고 객체를 제자리 수정하지도 않는 일반 함수에
any_usage_sanitizer를 적용하면 심각한 미탐(False Negative)이 발생합니다. 개발자가 검증 누락을 범하더라도 엔진이 인자를 무조건 안전하다고 오판하게 됩니다.
완전 정규화 이름(FQN) 표기 규칙
Snyk Code는 대상 함수를 **완전 정규화 이름(FQN)**으로 엄격히 식별합니다. 부분 이름(PQN)이나 와일드카드는 지원되지 않습니다. 주요 언어별 규칙은 다음과 같습니다.
- Java:
package.name.ClassName.methodName- 예:
com.company.utils.SecurityUtils.escapeHtml - 주의사항: 코드 내에서 와일드카드 임포트(
import com.company.utils.*)를 사용하면 정적 분석기의 타입 추론이 저하될 수 있으므로 명시적 임포트를 권장합니다.
- 예:
- C#:
global::Namespace.Class.Method- 예:
global::MyCorp.Security.InputSanitizer.Clean - 필수 요구사항: C#의 모든 FQN은 반드시 맨 앞에
global::접두사를 포함해야 합니다.
- 예:
- Kotlin:
- 클래스 메서드:
package.name.ClassName.methodName(예:com.company.utils.Validator.clean) - 파일 최상위 함수:
package.name.FileNameKt.functionName(예:com.company.utils.FiltersKt.sanitizeInput)
- 클래스 메서드:
- Python:
module.submodule.function_name또는module.ClassName.method_name- 예:
corp_security.filters.clean_sql_input - 제한사항: 데코레이터(
@sanitize)는 FQN으로 직접 매핑할 수 없습니다.
- 예:
- JavaScript / TypeScript:
module_name.export_name또는npm_package.export_name- 예:
corp-sec-lib.escapeHtml - 기본 내보내기(default export): 끝에
.default추가 (예:security.cleaner.default). - 주의사항: 복잡한 상대 경로(
../../utils) 대신 npm 패키지명이나 절대 경로를 사용하십시오.
- 예:
- Go:
host.com/user/project/package.Function- 예:
github.com/myorg/secutils/filter.SanitizeString - 구조체 메서드:
github.com/myorg/secutils/filter.XSSFilter.Clean
- 예:
- Apex:
ClassName.methodName또는Namespace.ClassName.methodName- 예:
MyCorp.SecurityUtils.escapeHtml
- 예:
콘솔 및 REST API 설정 절차
AppSec 팀은 웹 UI 또는 REST API를 통해 규칙 확장을 구성할 수 있습니다.
웹 콘솔 설정 순서
- Snyk 웹 콘솔에 로그인하고 대상 **그룹(Group)**을 선택합니다.
- Group settings(그룹 설정) ➔ Snyk Code ➔ **Rule Extensions(규칙 확장)**으로 이동합니다.
- Create sanitizer를 클릭하고 필수 항목을 입력합니다.
- Sanitizer: 식별된 완전 정규화 이름(FQN) 입력.
- Rule key: 지원 목록에서 해당 취약점 규칙 선택 (예:
XSS,Sqli,PT,CommandInjection,OR,LogForging). - Sanitization type: 4가지 동작 유형 중 알맞은 유형 선택.
- 관리 속성 설정:
- Scope: 전체 그룹 적용 또는 특정 조직(Organization) 한정 적용.
- Description: 티켓 번호, 담당 팀 등 변경 배경 기록.
- Status: Draft(초안) 또는 Published(게시됨) 선택.
- Save를 눌러 저장합니다.
신규 규칙 확장은 먼저 Draft 상태로 저장한 뒤, 영향 테스트를 거쳐 안전성이 입증되었을 때 Published로 전환하는 것이 권장됩니다.
배포 전 검증: 영향 테스트(Impact Testing)
검증되지 않은 규칙 확장을 즉시 배포하면 FQN 오탈자로 인해 규칙이 무효화되거나, 너무 느슨한 규칙으로 인해 실제 취약점을 놓칠 위험이 있습니다. Snyk의 **영향 테스트(Impact Testing)**는 신규 규칙이 대상 프로젝트의 스캔 결과에 미치는 변경 사항을 사전에 시뮬레이션할 수 있도록 지원합니다.
API 자동화 워크플로우
1단계: 영향 테스트 요청
POST /rest/groups/{group_id}/sast/rule_extensions/tests로 요청을 전송합니다.
POST /rest/groups/b1d4c802-9a3e-4b62-9e81-2c1a84f5093e/sast/rule_extensions/tests HTTP/1.1
Host: api.snyk.io
Authorization: token YOUR_API_TOKEN
Content-Type: application/json
{
"data": {
"type": "tests",
"attributes": {
"project_id": "a3b8d4e1-7f1c-4a09-8b7e-2c6f5d1a0b3f",
"org_id": "123e4567-e89b-12d3-a456-426614174000",
"fully_qualified_name": "com.company.utils.SecurityUtils.escapeHtml",
"rule_keys": ["XSS"],
"extension_type": "flows_through_sanitizer"
}
}
}
성공 시 202 Accepted와 함께 고유한 test_id가 반환됩니다.
2단계: 테스트 완료 상태 폴링
GET /rest/groups/{group_id}/sast/rule_extensions/tests/{test_id}를 호출하여 상태가 STARTED에서 COMPLETED로 변경될 때까지 대기합니다.
3단계: 결과 변경 리포트 조회
GET /rest/groups/{group_id}/sast/rule_extensions/tests/{test_id}/results를 호출하여 결과 리포트를 확인합니다.
{
"data": {
"attributes": {
"total_count": 4,
"removed_findings": {
"count": 4,
"changes": [
{
"rule_id": "java/XSS",
"rule_name": "Cross-site Scripting (XSS)",
"file_path": "src/main/java/com/company/web/UserController.java",
"severity": "high",
"priority_score": 710
}
]
},
"added_findings": {
"count": 0,
"changes": []
}
}
}
}
리포트 지표 분석
removed_findings: 새니타이저 매핑에 의해 제거된 알림 목록. 제거된 항목들이 의도된 오탐인지 철저히 검토합니다.added_findings: 규칙 변경으로 인해 부수적으로 추가된 알림(새니타이저의 경우 0이어야 함).total_count = 0: 대상 프로젝트에 아무런 변화가 없음을 의미합니다. FQN 경로 오류, 잘못된 규칙 키 선택, 임포트 분석 불가 등이 주요 원인입니다.
기업 AppSec 거버넌스 모범 사례
- 사내 보안 SDK의 단일화: 각 개발팀이 임의로 검증 코드를 작성하지 않도록 전사 공통 보안 모듈(공통 Jar, Go 모듈, npm 패키지 등)로 일원화하고, 규칙 확장은 해당 공인 인터페이스에 대해서만 설정합니다.
- 명시적 임포트 표준화: 정적 분석기가 모듈 간 호출 관계(Call Graph)를 명확히 해석할 수 있도록 와일드카드 임포트나 동적 리플렉션 호출을 제한하는 코딩 규칙을 적용합니다.
- 라이브러리 수명주기 감사: 사내 공통 라이브러리가 리팩터링되거나 패키지 경로가 변경될 때 Snyk Code의 규칙 확장 FQN도 동기화하여 오탐 재발을 방지합니다.
- 단계적 롤아웃 체계: 초안(Draft) 등록 ➔ API 영향 테스트 ➔ 파일럿 조직 검증 ➔ 그룹 단위 전체 게시(Published)의 점진적 단계를 거쳐 안전하게 배포합니다.
이러한 체계를 정립함으로써 AppSec 팀은 개발팀의 알림 피로를 해소하고, 정적 분석 도구 본연의 보안 감시 역량을 극대화할 수 있습니다.