본문으로 건너뛰기

© 2026 Molayo

Qiita헤드라인2026. 06. 22. 22:01

AI가 작성한 코드는 '작동'할 뿐 절반은 취약하다 — AI 생성 코드의 보안을 '작동 여부'와 '안전성'으로 나누어 검증하는 실전 가이드

요약

AI 생성 코드는 기능적으로는 95% 이상 작동하지만, 보안 측면에서는 약 55%의 합격률을 보이며 취약점을 포함할 확률이 높습니다. 모델의 크기와 상관없이 보안 성능은 정체되어 있으므로, '작동 여부'와 '안전성'을 분리하여 검증하는 체계적인 접근이 필요합니다.

핵심 포인트

  • AI 생성 코드는 기능적 작동과 보안 안전성이 별개임
  • LLM은 학습 데이터의 평균적인 패턴을 재현하여 취약점을 포함할 수 있음
  • 모델 파라미터 크기가 커져도 보안 성능 개선은 미미함
  • 기능적 요구사항 외에 보안 지침을 명시하는 검증 체계가 필수적임

AI에게 코드를 작성하게 하면 정말 빠릅니다.

사양을 대략적으로 전달하면 수십 초 만에 함수가 완성되고, 로컬에서 실행하면 제대로 작동합니다. 테스트도 통과하죠. "벌써 완성됐네"라는 기분이 듭니다. 저도 그렇습니다.

하지만 여기서 잠시 멈춰서 생각해 보고 싶습니다.

그 '작동함'이 '안전함'을 의미하지 않을 수도 있습니다.

자동차에 비유하면 이해하기 쉬울 것 같습니다. 엔진이 걸려 앞으로 나아가는 것과 브레이크가 제대로 작동하는 것은 별개의 문제입니다. 달리는 것만 확인하고 "이 자동차는 안전하다"라고 말하지는 않을 것입니다. 코드도 마찬가지로, "작동함(기능함)"과 "안전함(공격에 견딤)"은 별도로 확인해야 하는 별개의 검증 축입니다.

여기서 조금 긴장하게 만드는 수치를 소개하겠습니다.

보안 기업인 Veracode가 2026년 봄에 발표한 조사(GenAI Code Security Update, Spring 2026)에서, 150개 이상의 LLM(대규모 언어 모델)에 대해 보안 지시를 전혀 주지 않고 80개의 코딩 과제를 풀게 한 뒤, 생성된 코드를 SAST(정적 분석)에 돌렸습니다. 결과는 다음과 같습니다.

  • 코드가 구문적으로 올바르게 '작동'하는 비율은 95%를 넘었지만
  • 보안적으로 안전한 비율은 약 55%에 머물러 있습니다 (= 45%는 알려진 취약점을 포함하고 있었음) - 게다가 이 보안 합격률은,
  • 2023년부터 거의 제자리걸음입니다. 모델이 똑똑해져도 개선되지 않고 있습니다.

언어별로 보면 더 명확해집니다. Python 62%, C# 58%, JavaScript 57%, 그리고 **Java는 29%**입니다. Java의 경우 7할이 어떤 식으로든 취약점을 안고 있었다는 계산이 나옵니다.

"최신 모델을 쓰면 안전해지지 않을까?"라고 생각하실 겁니다. 하지만 Veracode에 따르면, 모델 크기의 효과는 매우 미미하여 200억 파라미터든 4000억 파라미터든 보안 성적은 모두 55% 부근에 모였다고 합니다. 똑똑함과 안전함은 별개의 능력입니다.

이 기사는 "AI가 무서우니 사용을 중단하자"는 이야기가 아닙니다. 오히려 반대입니다. AI로 빠르게 만들 수 있는 시대이기에, '작동 여부'와 '안전성'을 나누어 검증하는 체계를 한 번 만들어 두면, 내일의 나도, 코드를 사용하는 사용자도 훨씬 편해집니다. 그런 "계속 공격하기 위한 방어"를, 무지한 상태에서도 내일부터 바로 손을 움직일 수 있는 수준까지 구체적으로 써 내려가겠습니다.

대상 독자는 AI 코딩을 일상적으로 사용하며, 자신이 작성한(혹은 작성하게 한) 코드를 프로덕션(운영 환경)에 내보내는 입장인 분들입니다. 보안 전문가가 아니어도 괜찮습니다. 전문 용어는 나올 때마다 쉽게 풀어서 설명하겠습니다.

수치만 보고 "AI는 별로네"라고 끝낸다면 아무것도 개선되지 않습니다. 중요한 것은 "왜 그렇게 되는가"를 구조적으로 이해하는 것입니다. 원인을 알면 대책을 세울 위치도 결정됩니다. 이유는 크게 세 가지라고 생각합니다.

LLM(대규모 언어 모델)은 전 세계의 방대한 코드를 읽고 "다음에 올 법한 코드"를 예측하는 구조입니다. 즉, 세상에 존재하는 수많은 코드의 "평균"을 재현하기 쉽습니다.

여기서 잔혹한 사실이 있는데, 세상의 코드에는 안전하지 않은 작성 방식도 대량으로 섞여 있습니다. SQL을 문자열 연결로 구성하는 오래된 방식, API 키를 소스에 직접 적어 넣은 샘플, 비밀번호를 md5로 대충 해싱한 레거시 코드 등. 이런 "작동은 하지만 위험한" 패턴도 AI는 확실히 학습해 버립니다. 그래서 아무런 지시를 하지 않으면 평균적으로 위험한 작성 방식이 섞여 들어옵니다. 이는 정신론이 아니라 학습 구조에서 기인하는 구조적인 문제입니다.

우리가 AI에게 전달하는 지시의 대부분은 "사용자 목록을 가져오는 함수를 작성해 줘"와 같은 기능적 요구입니다. 안전성에 대해서는 대개 아무 말도 하지 않습니다.

AI는 주어진 요구를 충족하는 최단 경로를 찾습니다. 요구사항이 "작동하는 것"이라면 작동하는 것을 반환합니다. "파라미터화된 쿼리(Parameterized Query)를 사용해라", "비밀 정보는 환경 변수에서 읽어라"라고 명시하지 않으면 그 부분은 최적화 대상이 되지 않습니다. Veracode의 조사가 "보안 지시 없음" 상태로 측정된 것은 바로 평소 우리의 사용 방식을 재현하고 싶었기 때문입니다.

이 부분이 핵심일지도 모릅니다.

스탠퍼드 대학교의 유명한 연구(Perry, Srivastava, Kumar, Boneh 「Do Users Write More Insecure Code with AI Assistants?」 CCS 2023, arXiv:2211.03622)에서 다음과 같은 결과가 나왔습니다.

  • AI 어시스턴트를 사용한 참가자는,
    사용하지 않은 사람보다 안전하지 않은 코드를 작성했다 - 그럼에도 불구하고,
    "자신의 코드는 안전하다"라고 믿는 정도는 높았다 (과신)

즉, AI가 곁에 있으면 사람은 "해냈다"는 성취감이 앞서 검증의 손길을 늦추게 됩니다. "작동하니까, AI가 작성했으니 괜찮겠지"라는 안심이 가장 위험합니다. 작동함 + 자신감은 안전의 증명이 되지 않습니다.

이 세 가지를 나란히 놓고 보면 대책의 방향이 보입니다. AI의 평균을 덮어쓰는 지시(이유 1·2에 대한 대책)와 인간의 과신을 메우는 "기계적인 체크"(이유 3에 대한 대책). 이 두 바퀴가 모두 필요하다는 뜻입니다.

추상적인 논의만으로는 와닿지 않으므로, AI가 양산하기 쉬운 취약성을 before (위험) → after (안전) 코드로 구체적으로 살펴보겠습니다. 모두 더미 샘플입니다. 이것을 읽는 것만으로도 "아, 이거 내 코드에도 있을지 몰라"라는 점을 발견할 수 있을 것입니다.

SQL 인젝션 (SQL Injection) 이란, 사용자가 입력한 문자열이 그대로 데이터베이스 명령문 (SQL)의 일부로 실행되어 버리는 취약성입니다. 공격자가 입력란에 조작된 문자열을 넣으면, 본래 보이지 않아야 할 데이터를 탈취당하거나 테이블이 삭제될 수 있습니다.

AI는 문자열을 연결하여 SQL을 구성하는 방식을 꽤 자주 사용하곤 합니다.

# before (위험): 입력을 그대로 연결하고 있음
def get_user(user_id):
    query = f"SELECT * FROM users WHERE id = '{user_id}'"
...

해결 방법은 파라미터화 쿼리 (Parameterized Query, 플레이스홀더) 입니다. 값을 "문자열의 일부"가 아니라 "나중에 채워 넣을 데이터"로 전달하면, DB가 해당 값을 명령으로 해석하지 않게 됩니다.

# after (안전): 값은 플레이스홀더로 전달
def get_user(user_id):
    query = "SELECT * FROM users WHERE id = %s"
...

참고로 Veracode의 분류별 통계에 따르면, SQL 인젝션은 아직 나은 편(모델이 파라미터화를 학습하고 있음)이지만, 그럼에도 제로(0)가 되지는 않았습니다. "나은 편인데도 이 정도구나"라고 생각하며 적당한 긴장감을 유지하는 것이 좋겠습니다.

XSS (Cross-Site Scripting) 는 사용자 입력을 그대로 HTML에 삽입하여, 입력 내용에 심어진 스크립트가 브라우저에서 실행되어 버리는 취약성입니다. 타인의 세션을 탈취당할 수도 있습니다. Veracode의 조사에 따르면, XSS와 같은 "출력 계열"의 취약성이 특히 성적이 좋지 않았으며, AI가 가장 어려워하는 영역 중 하나였습니다.

// before (위험): 사용자 입력을 가공 없이 HTML에 삽입
function renderComment(comment) {
    document.getElementById("box").innerHTML = "<p>" + comment + "</p>";
...

해결 방법은 출력 시의 이스케이프 (Escape, 무해화) 입니다. 텍스트로 취급하거나, 프레임워크의 자동 이스케이프 기능에 맡깁니다.

// after (안전): 텍스트로 삽입 (HTML로 해석되지 않게 함)
function renderComment(comment) {
    const p = document.createElement("p");
...

React나 Vue와 같은 모던 프레임워크는 기본적으로 자동 이스케이프를 지원합니다. 하지만 dangerouslySetInnerHTML이나 v-html을 사용하여 스스로 무효화하면 다시 구멍이 생깁니다. AI가 이러한 "위험한 지름길"을 제안할 수 있으므로 주의가 필요합니다.

이것은 실제 감사에서 정말 자주 발생하는 사례입니다. API 키나 비밀번호를 소스 코드에 직접 작성해 버리는 것입니다. Git에 커밋하는 순간 이력에 영원히 남게 되며, 공개 리포지토리라면 전 세계 어디에서든 볼 수 있습니다.

# before (위험): 비밀 정보를 소스에 직접 작성
STRIPE_API_KEY = "sk_live_abc123_THIS_IS_A_SECRET"
client = StripeClient(STRIPE_API_KEY)

해결 방법은 환경 변수나 비밀 관리 도구 (Secrets Manager)에서 읽어오는 것입니다. 그리고 샘플 파일만을 커밋해야 합니다.

# after(안전): 환경 변수에서 읽어온다. 값은 리포지토리에 넣지 않는다
import os
STRIPE_API_KEY = os.environ["STRIPE_API_KEY"]
...
# .env.example(이것만 커밋. 실제 값은 쓰지 않는다)
STRIPE_API_KEY=
# .gitignore に必ず .env を追加
...

AI는 "일단 돌아가는 샘플"을 만들 때, 플레이스홀더(Placeholder) 용도로 비밀스러운 문자열을 채워 넣을 때가 있습니다. 그것을 그대로 커밋해 버리는 사고가 정말 많습니다. 나중에 자세히 쓰겠지만, 비밀 정보는 AI에게 전달하는 프롬프트(Prompt)에도 붙여넣지 않는 것이 철칙입니다.

비밀번호를 저장할 때, md5나 sha1 같은 빠른 해시(Hash)를 사용하는 것은 위험합니다. 이것들은 "빠르다"는 것이 특징인데, 즉 공격자가 무차별 대입 공격(Brute-force attack)을 하는 것도 빠르다는 뜻입니다. AI는 오래된 코드를 학습했기 때문에, md5를 아무렇지 않게 제안하곤 합니다.

# before(위험): 빠른 해시 = 무차별 대입이 쉬움
import hashlib
def hash_password(pw):
...

해결 방법은, 비밀번호 저장 전용의, 의도적으로 느린 알고리즘 (argon2id, bcrypt, scrypt 등)을 솔트(Salt)와 함께 사용하는 것입니다.

# after(안전): 비밀번호용 KDF를 사용 (솔트는 내부에서 자동으로 부여)
from argon2 import PasswordHasher
ph = PasswordHasher()
...

"빠른 게 좋은 거 아니야?"라는 직관이 여기서는 반대로 작용합니다. 이걸 모르면 평생 깨닫지 못할 포인트입니다.

마지막은 사소해 보이지만 효과적인 것입니다. API가 받은 데이터를 검증하지 않고 그대로 사용해 버리는 패턴입니다. 타입(Type)이 다르거나, 필수 항목이 없거나, 예상보다 길거나 하는 입력값으로 인해 후속 처리가 망가집니다.

// before(위험): body를 그대로 믿고 사용
app.post("/orders", (req, res) => {
const { quantity, productId } = req.body;
...

해결 방법은, **입구에서 스키마 검증(Schema Validation)**을 하는 것입니다. zod(TS)나 pydantic(Python)을 사용하여 형태, 타입, 범위를 처음에 확정 짓습니다.

// after(안전): 입구에서 형태를 보장함
import { z } from "zod";
const OrderSchema = z.object({
...

"외부에서 오는 것은 전부 의심한다". 이것은 보안의 기본 자세이며, AI에게 맡긴 부분이야말로 이 입구 검증이 누락되기 쉽습니다.

여기까지에서 "AI는 아무 말도 안 하면 위험하다", "인간은 과신하기 쉽다"라는 두 가지 측면이 보였습니다. 그럼 어떻게 해야 할까요?

여기서 제안하고 싶은 발상의 전환은, "AI가 할 수 있는가"로 생각하는 것을 그만두고, "어떤 검증을 누구(무엇)에게 맡길 것인가"로 생각하는 것입니다. 이는 제가 이전에 쓴 코드 마이그레이션(Code Migration) 기사와 같은 사상이며, 보안에도 그대로 적용됩니다. 검증 업무를 세 가지 레이어로 분류하는 것입니다.

레이어담당내용예시
① 결정적으로 검출 가능한 것결정적 도구 (Deterministic Tool)규칙으로 기계적으로 판정할 수 있는 것비밀 정보 직기입, 알려진 위험 패턴, 취약한 의존성
② 문맥에 의존하는 것AI (보조)코드의 의도를 고려한 지적이 인가(Authorization) 체크가 요구사항을 충족하는가, 설계상의 허점
③ 수용 여부를 판단하는 것인간리스크를 받아들일 것인가, 중단할 것인가고위험 지적에 대한 최종 판단, 불가역적 작업의 승인

포인트는 순서입니다. ①에서 기계적으로 없앨 수 있는 것을 먼저 없애고, AI(②)에게 맡기는 범위를 "문맥 판단이 필요한 부분"으로만 좁히는 것입니다. 그리고 마지막 "이것을 운영 환경에 배포해도 되는가"라는 수용 판단은 반드시 인간(③)이 쥐고 있어야 합니다.

왜 이 분류가 중요하냐면, AI에게 "보안 체크도 전부 다 해줘"라고 통째로 맡기는 것은 과신의 재생산이기 때문입니다. AI는 코드를 작성할 때 위험한 패턴을 섞는 것과 같은 이유로, 리뷰를 할 때도 위험한 패턴을 놓칩니다. 자신의 사각지대는 스스로 보기 어렵습니다. 그렇기에 기계적으로 판정할 수 있는 부분은 흔들림 없는 결정적 도구에 맡깁니다. 이것이 효과적입니다.

다음 장에서 이 분류를 "만들기 전부터 보호하는" 개발 플로우(Development Flow)로 녹여내겠습니다.

보안 분야에는 **Shift-Left(시프트 레프트)**라는 용어가 있습니다. 개발 과정을 왼쪽에서 오른쪽(설계 → 구현 → 테스트 → 배포)으로 배열했을 때, 문제 발견을 가능한 한 '왼쪽(초기 단계)'로 당기는 사고방식입니다. 버그든 취약점이든, 나중에 발견할수록 수정 비용이 급증합니다. 그래서 일찍 알아차리자는 것입니다.

AI 생성 코드에 이 Shift-Left를 4단계로 적용합니다.

'이유 2'에서 보았듯이, AI는 요청받은 것만 최적화합니다. 그렇다면 처음부터 보안 제약을 주어주는 것이 좋습니다. 이것만으로도 위험한 초기 출력을 상당히 줄일 수 있습니다.

【안전 코드 생성 프롬프트 (시스템 프롬프트/서두에 삽입)】
당신은 안전한 코드를 작성하는 시니어 엔지니어입니다. 다음을 항상 지켜가며 코드를 작성해 주세요.
- 비밀 정보(API 키/비밀번호/토큰/연결 문자열)는 절대 하드코딩하지 마세요.
...

다만, 여기서 강조하고 싶은 부분이 있습니다. secure-by-prompt는 '입구'일 뿐 '목표점'이 아닙니다. Veracode의 조사는 '지시 없음' 상태에서 측정한 것이지만, 지시를 추가한다고 해서 보안이 완벽해지는 것은 아닙니다. 프롬프트는 AI의 평균 수준을 조금 끌어올릴 뿐입니다. 그래서 다음 단계가 핵심입니다.

AI가 작성한 코드는 커밋하기 전에 기계적으로 스캔합니다. 사람의 리뷰에 넘기기 전에, 기계로 처리할 수 있는 것은 기계로 처리하는 것입니다. 여기가 3단계 ①입니다.

주역은 두 가지입니다. Gitleaks (비밀 정보 탐지에 특화된 경량 스캐너)와 Semgrep (SAST=코드의 위험 패턴을 의미적으로 탐지하는 정적 분석 도구). 이것들을 **pre-commit 훅(hook)**에 심어두면, '커밋하려는 순간 위험한 것을 차단'할 수 있게 됩니다. 집을 나서기 전 잠금장치 확인을 자동으로 해주는 느낌입니다.

# .pre-commit-config.yaml
repos:
# Gitleaks: 비밀 정보(API 키・토큰 등)의 직접 기입을 차단
...

설치는 한 번만 합니다.

pip install pre-commit
pre-commit install
# 이후, 커밋할 때마다 자동으로 스캔이 실행되며, 히트(hit)하면 커밋이 중지됩니다.
...

여기서 효과적인 것이 '일관성'입니다. AI는 같은 코드를 두 번 리뷰하게 하면 다른 말을 할 수도 있지만, Semgrep이나 Gitleaks는 동일한 입력에 동일한 판정을 반환합니다. 결정적이기 때문에 게이트(문지기)로 활용할 수 있는 것입니다.

기계로 처리할 수 없는 것이 바로 맥락에 의존하는 구멍입니다. '이 엔드포인트, 권한 검사가 요구 사항을 충족했나?', '이 권한 설계, 빠져나갈 길은 없나?' 같은 것들입니다. 여기가 3단계 ②, AI의 출전입니다.

다만, **AI의 리뷰는 '권위'가 아니라 '또 하나의 눈'**으로 취급해야 합니다. 지적에는 반드시 근거(파일과 행)를 제시하게 하고, 불분명하면 무리하게 단정 짓게 하지 않고 '모른다'고 말하게 해야 합니다. 이것이 환각(Hallucination: 그럴듯한 거짓말) 방지책입니다.

【보안 리뷰 프롬프트】
당신은 보안 리뷰어입니다. 다음 코드를 OWASP Top 10 관점에서 리뷰해 주세요.
출력 규칙:
...

마지막 한 문장이 중요합니다. 수용 판단(내놓을지 막을지)은 인간이 주도해야 합니다. AI는 재료를 내주는 담당일 뿐, 결정권자가 아닙니다.

마지막으로, pre-commit을 우회하거나 빼먹은 사람이 있어도 괜찮도록, CI(GitHub Actions 등)에서 같은 스캔을 다시 한번 실행합니다. pre-commit은 '각자의 선의'에 의존하는 부분이 있기 때문에, 리포지토리 측에도 문을 세워두는 것입니다. 벨트와 서스펜더, 둘 다 착용하는 이미지입니다.

# .github/workflows/security-gate.yml
name: security-gate
on: [push, pull_request]
...

조직 고유의 '이것은 절대 안 돼'를 커스텀 Semgrep 규칙으로 추가할 수도 있습니다. 예를 들어, 위험한 eval 사용을 금지하려면 다음과 같이 작성할 수 있습니다.

# .semgrep/no-eval.yml
rules:
- id: no-eval-on-user-input
...

그리고 고심각도(critical/high)의 지적이 남아있는 동안에는 머지(merge)할 수 없도록 합니다. **'사람이 보고, 받아들이기로 결정할 때까지 멈추는 문'**을 만들어 두는 것입니다.

이 4단계를 나열해 보면, 3레이어(3-layer)의 분류가 그대로 개발 플로우(flow)가 되어 있다는 것을 알 수 있습니다. ①기계(단계 2·4) → ②AI(단계 3) → ③인간(최종 승인). AI에 대한 과신을 기계의 일관성과 인간의 판단으로 끼워 넣는 구조입니다.

편리한 메커니즘도 토대를 잘못 디디면 오히려 사고로 이어집니다. 마지막으로 "이것만은"이라는 철칙을 전합니다.

이 부분이 가장 간과됩니다. 코드 속의 비밀(secret)은 주의하더라도, "이 에러 고쳐줘"라며 AI에게 실제 API 키가 포함된 코드나 운영(production) 로그를 붙여넣는 사고가 발생합니다.

AI에 전달한 텍텍스트는 어딘가에 저장되거나, 학습에 사용되거나, 외부로 전송될 가능성이 있는 경로를 타게 됩니다. 비밀 정보나 개인정보, 운영 데이터는 AI에 전달하기 전에 반드시 더미(dummy) 데이터로 교체해야 합니다. 스캐너의 로그나 에러 출력에도 비밀 정보가 섞이지 않도록 해야 합니다. "AI에 전달하는 것 = 외부로 내보내는 것"이라고 생각하는 것이 적당할 것 같습니다.

반복해서 말씀드리지만, AI는 자신이 코드를 작성할 때 놓치는 패턴을 리뷰할 때도 놓칩니다. AI의 OK는 "하나의 참고 의견"일 뿐입니다. 결정적인 도구를 통한 검증(裏取り, verification)과 인간의 눈을 반드시 겹쳐야 합니다. "AI가 괜찮다고 했으니까"라는 생각은 Stanford의 연구가 보여준 "과신" 그 자체입니다.

운영 DB에 대한 파괴적인 변경, 과금, 공개, 삭제, 배포. 이러한 "되돌릴 수 없는 조작"과 critical/high 등급의 취약점 지적은 AI나 자동화가 마음대로 통과시키게 해서는 안 됩니다. 반드시 인간이 "이것은 수용한다/이것은 중단한다"라고 판단하는 문을 구조적으로 배치해 두어야 합니다.

AI는 실제로 존재하지 않는 패키지 이름을 그럴듯하게 제안할 때가 있습니다(slopsquatting이라고 불리는 현상). 공격자가 그 "있을 법한 이름"을 선점하여 악성 패키지로 등록해 두었다면, 설치하는 순간 끝장입니다. AI가 npm install이나 pip install을 권할 때는 이름을 한 번 공식 레지스트리(registry)에서 확인하십시오. 의존성(dependency) 또한 외부에서 오는 것이라고 의심해야 합니다.

지금까지의 내용을 역할 분담 표로 정리하겠습니다. AI에게 맡겨 속도를 높일 부분과, 인간이 쥐고 놓치지 말아야 할 부분을 명확히 나누는 것이 요령입니다.

공정인간 (What/Why = 무엇을·왜)AI (How = 어떻게 구현할 것인가)
제약 설계어떤 위협을 방지할 것인가, 무엇을 허용할 것인가secure-by-prompt 문구의 초안 작성
...

구호는, "무엇을·왜"는 인간, "어떻게"는 AI 입니다. 이것은 보안에 국한되지 않고, AI 시대의 개발 전체에 적용되는 구분법이라고 생각합니다.

AI에게 "보안적으로 작성해줘"라고 부탁하고 만족한다… 이는 입구에 불과합니다. 결정적인 스캔으로 검증하십시오. -
동작 테스트를 통과했으니 안전하다고 생각한다… 동작하는 것과 안전한 것은 별개의 축입니다. SAST와 비밀 정보 스캔은 별개입니다. -
비밀 정보를 AI 프롬프트에 붙여넣는다… 더미로 교체하십시오. 로그에도 남기지 마십시오. -
AI의 리뷰를 최종 판정으로 삼는다… 자신의 사각지대는 스스로 볼 수 없습니다. 기계와 인간으로 끼워 넣으십시오. -
CI 게이트를 경고 수준에 머물게 한다… 고위험(high) 심각도는 차단(block)하지 않으면 결국 무시됩니다. -
AI가 권한 의존성을 그대로 설치한다… 존재 확인과 최소 권한 원칙을 지키십시오. -

  • 스캔 지적이 너무 많아 감당이 안 될 때는, 우선 critical/high만으로 좁혀서 근원(신규 커밋)부터 차단하십시오. 과거의 것은 탓하지 말고 점진적으로 진행하십시오. -
    AI에게 자동 수정을 맡겼을 때 동작이 변해버린다면 중단하십시오. 동작을 바꾸지 않고 수정하려면, 먼저 동작을 고정하는 테스트(명세화 테스트)를 배치해야 합니다. -
    비가역적 조작에 부딪히면 자동화를 멈추고 인간에게 넘기십시오. 이것은 속도보다 우선순위가 높습니다.

내용이 길어졌으니 마지막으로 중요한 점만 요약하겠습니다.

AI로 "동작하는 것"을 만드는 것은 이제 순식간입니다. 하지만 "동작함"과 "안전함"은 별개의 검증 축입니다. AI는 아무런 지시가 없으면 평균적으로 위험한 작성 방식을 섞어서 내놓고, 인간은 AI가 곁에 있으면 과신하여 주의를 게을리하게 됩니다. Veracode의 "구문(syntax) 95%·보안 55%로 2년간 정체"라는 결과와, Stanford의 "사용하면 안전하지 않은데도 안전하다고 믿는다"는 연구는 이 두 가지 사실을 냉정하게 뒷받침합니다.

그렇기에 대책은 심플합니다. "동작하는가"와 "안전한가"를 나누어 확인하는 메커니즘을 한 번 구축하십시오. 생성 전에 제약을 전달하고, 생성 직후에 기계로 스캔하며, 문맥은 AI가 찾아내게 하고, 마지막에는 인간이 수용 여부를 판단합니다. 결정적으로 없앨 수 있는 것은 결정적인 도구에, 문맥은 AI에게, 수용 판단은 인간에게 맡기십시오. 이 3레이어의 분류가 과신을 구조적으로 메워줄 것입니다.

제가 이러한 '방어'를 비난의 축이 아닌 배려의 축으로 파악하고 싶은 데에는 이유가 있습니다. 취약점(Vulnerability)이란, 공격을 받은 뒤에 "왜 몰랐을까"라며 과거의 자신을 자책해 봤자 이미 늦기 때문입니다. 그보다는 오늘 커밋(Commit) 전의 스캔을 하나 설정해 두는 것, pre-commit을 한 번 넣어두는 것. 그것이 반년 뒤의 자신과, 코드를 사용하는 사용자에게 **"미리 지켜줘서 고마워요"**라는 인사를 받는 일이 됩니다. 오늘의 문단속은 내일의 자신에게 주는 작은 선물입니다.

그리고, 안전하게 작성하는 능력과 검증하는 메커니즘은 AI에게 빼앗기지 않을 자산이라고 생각합니다. 코드를 작성하는 속도는 범용화(Commodity)되겠지만, "작동하는 것을 안전하게 내놓을 수 있다"는 판단과 메커니즘은 계속 쌓여갈 것입니다. 이것은 바로 코드를 자산(Capital)으로서 키워나간다는 이야기 그 자체입니다.

  • 현재 프로젝트에서 pre-commit install을 실행하여, Gitleaks + Semgrep을 도입한다 (문단속의 자동화).
  • pre-commit run --all-files기존 코드를 한 번 스캔해 본다 (우선 현황을 파악).
  • AI에게 전달하는 프롬프트(Prompt) 앞에 secure-by-prompt 제약 문구를 한 장 붙인다 (입구 단계의 수준을 높임).
  • CI에 security-gate.yml을 추가하여, 높은 심각도(High Severity)는 머지(Merge)를 차단하는 문을 세운다.

전부 다 할 필요는 없습니다. 오늘은 가장 첫 번째 것 하나만이라도 해보세요. 그 하나의 문단속이 내일의 당신을 도와줄 것이니까요.

참고한 주요 1차 정보:

  • Veracode 「Spring 2026 GenAI Code Security Update」 (보안 합격률 55%/구문 95%/언어별 성적)
  • Neil Perry, Megha Srivastava, Deepak Kumar, Dan Boneh 「Do Users Write More Insecure Code with AI Assistants?」 CCS 2023 (arXiv:2211.03622)
  • OWASP Top 10, Semgrep (p/owasp-top-ten・p/secrets), Gitleaks 공식 문서

AI 자동 생성 콘텐츠

본 콘텐츠는 Qiita AI의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.

원문 바로가기
0

댓글

0