(시큐어코딩)자바스크립트 프로젝트 보안설계 SDLC 단계별 가이드
웹 애플리케이션은 보안 없이는 절반의 완성입니다. 자바스크립트 프로젝트에서 보안을 고려하지 않은 설계는 곧 위협에 노출된 출입구와도 같습니다.
안녕하세요, 웹 개발자 여러분! ICT리더 리치입니다. 오늘은 프론트엔드 또는 풀스택 개발을 하면서 자칫 놓치기 쉬운 보안설계에 대해 다뤄보려 합니다.
자바스크립트는 클라이언트 측에서 많은 작업을 처리할 수 있지만, 동시에 보안 위협에 가장 많이 노출되는 언어이기도 하죠.
이번 글은 프로젝트 초기 기획부터 배포 및 유지보수 단계까지 각 단계별로 필요한 보안 고려사항과, 각 섹션별 실무 예시(주석 포함 코드)를 제공합니다. 안전한 소프트웨어를 개발하는데 많은 도움이 되기를 바랍니다.
📌 바로가기 목차
| 보안 중심 자바스크립트 설계 상황 – 대표 이미지 |
1. 요구사항 분석 단계의 보안 포인트
요구사항 분석 단계는 프로젝트 전체의 설계 방향을 결정짓는 출발점입니다. 이때 보안을 함께 고려하지 않는다면, 추후 구현단계에서 큰 기술적 부채로 이어질 수 있습니다. 자바스크립트 프로젝트에서는 특히 클라이언트 측 데이터 처리, API 호출 시 인증 및 무결성, 사용자 입력 검증을 사전에 명확히 요구사항에 반영해야 합니다.
요구사항 분석 단계에서 도출한 보안 요구사항을 바탕으로 데이터 흐름과 민감정보 처리 규칙을 코드/문서화 해두면 설계·구현 시 혼선이 줄어듭니다. 아래 예시는 간단한 DFD 기반 검증용 스크립트(데이터 분류, 민감도 레이블링)와 요구사항을 자동 검사하는 예시입니다.
// 요구사항 분석 기반 데이터 분류 및 체크 예시 (Node.js 스타일, JS)
/*
목적: 요구사항 분석 결과(민감정보 규칙 등)를 코드로 표현하여
- 데이터 요소별 민감도 검사
- 전송/저장 정책 자동 검증
- 설계 문서화 보조
*/
const fs = require('fs');
// 샘플 스키마(요구사항에서 정의된 민감도)
const dataSchema = {
user: {
id: { sensitive: false },
username: { sensitive: false },
email: { sensitive: true, storeEncrypted: true },
password: { sensitive: true, hash: true },
phone: { sensitive: true, maskInLogs: true }
},
order: {
id: { sensitive: false },
productId: { sensitive: false },
creditCard: { sensitive: true, storeEncrypted: true, pciScope: true }
}
};
// 샘플 데이터 흐름 정의 (DFD-like)
const dataFlow = [
{ from: 'client', to: 'api', fields: ['username', 'password', 'email'] },
{ from: 'api', to: 'db', fields: ['username', 'password_hash', 'email_encrypted'] },
{ from: 'api', to: 'third_party', fields: ['order_id', 'productId'] }
];
// 요구사항 위반 체크 함수
function checkDataFlowCompliance(schema, flows) {
const issues = [];
for (const flow of flows) {
for (const field of flow.fields) {
// 단순 매핑: password -> password or password_hash 등 매핑 규칙이 필요 (여기선 단순화)
const found = findFieldInSchema(schema, field);
if (!found) {
issues.push(`[WARN] 필드 미정의: ${field} in flow ${flow.from} -> ${flow.to}`);
continue;
}
// 민감데이터가 외부(third_party)로 전송될 경우 경고
if (found.sensitive && flow.to === 'third_party' && !found.sharedWithThirdParty) {
issues.push(`[SEC] 민감정보 전송 금지: ${field} is sensitive but sent to ${flow.to}`);
}
// DB 저장 시 암호화 요구 체크 예시
if (found.sensitive && flow.to === 'db' && !found.storeEncrypted && !found.hash) {
issues.push(`[SEC] 민감정보 저장시 암호화/해시 필요: ${field} stored in DB without encryption`);
}
}
}
return issues;
}
function findFieldInSchema(schema, fieldName) {
for (const [entity, fields] of Object.entries(schema)) {
if (fields[fieldName]) return fields[fieldName];
// 허용되는 다른 명명 규칙 (예: password_hash => password)
if (fieldName.endsWith('_hash')) {
const base = fieldName.replace('_hash', '');
if (fields[base]) return fields[base];
}
if (fieldName.endsWith('_encrypted')) {
const base = fieldName.replace('_encrypted', '');
if (fields[base]) return fields[base];
}
}
return null;
}
// 실행
const issues = checkDataFlowCompliance(dataSchema, dataFlow);
if (issues.length === 0) {
console.log('요구사항 기반 데이터 흐름 검증 통과');
} else {
console.log('검증 결과:');
issues.forEach(i => console.log(i));
}
// (실무) 결과를 설계 문서에 자동으로 주석/태그로 추가하거나, 설계 미비 항목을 추적 티켓으로 생성하도록 연동 가능
2. 보안 중심 설계 원칙과 아키텍처
설계 단계에서는 보안 위협을 최소화할 수 있는 아키텍처 구성이 핵심입니다. 특히 SPA 또는 CSR 기반 자바스크립트 앱은 CORS, XSS, CSRF에 노출되기 쉬우므로, 적절한 보안 정책을 설계에 반영해야 합니다.
설계 단계에서는 보안 헤더(CSP, HSTS 등), 인증·인가 흐름, 프록시/리버스 프록시 설계, 세션 보안 등을 코드 레벨(서버 설정)로 명확히 정의해야 합니다. 아래는 Express 기반 서버에서 보안 헤더와 CSP, CORS, 세션 설정을 예시로 보여줍니다.
// 보안 중심 Express 설정 예시 (Node.js / Express)
/*
포함:
- HTTPS 강제 (리버스 프록시 환경에서 X-Forwarded-Proto 체크)
- Helmet으로 보안 헤더 적용
- CSP 정책 설정
- 세션 (쿠키) 보안: HttpOnly, Secure, SameSite
- CORS 화이트리스트 적용
*/
const express = require('express');
const helmet = require('helmet');
const session = require('express-session');
const cors = require('cors');
const app = express();
// 기본 보안 헤더
app.use(helmet());
// Content Security Policy 예시 (간단)
app.use(
helmet.contentSecurityPolicy({
directives: {
defaultSrc: ["'self'"],
scriptSrc: ["'self'"],
styleSrc: ["'self'", 'https:'],
imgSrc: ["'self'", 'data:'],
connectSrc: ["'self'", 'https://api.myservice.com'],
objectSrc: ["'none'"],
upgradeInsecureRequests: []
}
})
);
// HTTPS 강제(리버스 프록시에서) - 프로덕션에서 리버스 프록시 사용시
app.use((req, res, next) => {
if (req.headers['x-forwarded-proto'] && req.headers['x-forwarded-proto'] !== 'https') {
return res.redirect(`https://${req.headers.host}${req.url}`);
}
next();
});
// CORS 화이트리스트
const whitelist = ['https://app.mydomain.com', 'https://admin.mydomain.com'];
app.use(
cors({
origin: function (origin, callback) {
if (!origin) return callback(null, true); // 모바일 앱 or curl 등
if (whitelist.indexOf(origin) !== -1) {
callback(null, true);
} else {
callback(new Error('Not allowed by CORS'));
}
},
credentials: true
})
);
// 세션/쿠키 설정 (HttpOnly, Secure, SameSite)
app.use(
session({
secret: process.env.SESSION_SECRET || 'change_me_in_prod',
resave: false,
saveUninitialized: false,
cookie: {
httpOnly: true,
secure: process.env.NODE_ENV === 'production', // HTTPS일 때만
sameSite: 'lax',
maxAge: 1000 * 60 * 60 // 1시간
}
})
);
// 예시 라우트
app.get('/health', (req, res) => {
res.json({ status: 'ok' });
});
// 서버 시작
const port = process.env.PORT || 3000;
app.listen(port, () => console.log(`Server listening on ${port}`));
3. 구현 단계에서 지켜야 할 시큐어 코딩
코드 구현은 개발자의 보안 역량이 직접 반영되는 단계입니다. 특히 자바스크립트는 동적 타입 언어로, 입력 검증 미흡, 민감정보 노출, DOM 기반 공격에 매우 취약하므로 시큐어 코딩이 필수입니다.
구현 단계에서는 입력 검증, 안전한 DOM 조작, 민감정보 취급 정책을 코드로 강제해야 합니다. 아래는 프론트엔드/백엔드에서 각각 적용 가능한 시큐어 코딩 예시(입력 스키마 검증, DOM 무해화, 안전한 인증 토큰 처리 등)입니다.
// 시큐어 코딩 예시 모음 (프론트엔드 + 백엔드)
// 1) 백엔드: 입력 스키마 검증 (Joi 사용 예시)
const Joi = require('joi');
const registerSchema = Joi.object({
username: Joi.string().alphanum().min(3).max(30).required(),
email: Joi.string().email().required(),
// 비밀번호는 최소 규칙만 확인, 해시는 서버에서 처리
password: Joi.string().min(8).max(128).required()
});
// 사용 예시
function validateRegister(payload) {
const { error, value } = registerSchema.validate(payload, { abortEarly: false });
if (error) {
// 적절한 에러 응답
return { ok: false, errors: error.details.map(d => d.message) };
}
return { ok: true, value };
}
// 2) 프론트엔드: 안전한 DOM 업데이트 (innerText / textContent 사용)
// (avoid) element.innerHTML = userInput;
// (safe)
function safeRender(element, userText) {
// 사용자 입력은 반드시 escape 또는 textContent로 출력
element.textContent = userText;
}
// 3) 프론트엔드: DOMPurify로 HTML 무해화 (허용된 HTML만 허용할 때)
//// 예시 (브라우저 환경) - DOMPurify는 외부 라이브러리로 설치 필요
// import DOMPurify from 'dompurify';
// const clean = DOMPurify.sanitize(userProvidedHtml, { ALLOWED_TAGS: ['b','i','strong','em','a'] });
// container.innerHTML = clean;
// 4) 토큰 저장: localStorage 대신 HttpOnly 쿠키 권장 (서버 측 설정 필요)
// 프론트엔드에서는 쿠키 기반 인증 요청만 수행 (fetch에 credentials 포함)
async function fetchWithAuth(url, options = {}) {
const res = await fetch(url, {
credentials: 'include', // 쿠키 전송
headers: {
'Accept': 'application/json'
},
...options
});
return res;
}
// 5) 외부 라이브러리 취약점 점검 (개발 프로세스 예시)
// > npm audit --json > audit-result.json
// 개발 파이프라인에서 자동으로 취약점 레포트를 생성하고, 보안 담당자에게 알림을 보냄.
// 6) 민감정보 마스킹 로그 예시
function mask(str, left = 2, right = 2) {
if (!str) return str;
if (str.length <= left + right) return '*'.repeat(str.length);
return str.slice(0, left) + '*'.repeat(str.length - left - right) + str.slice(-right);
}
// 사용
console.log('user phone:', mask('01012345678')); // 01*******78
// 7) 주의: 절대 클라이언트에서 비밀 키 하드코딩 금지 (예시 주석)
![]() |
| JS 프로젝트 보안설계를 소개하는 인포그래픽 – 보안은 설계부터 시작됩니다 |
4. 테스트 단계에서의 보안 검증 체크
보안 테스트는 기능 테스트와 병행되어야 하며, 자동화된 도구와 수동 점검을 함께 사용하는 것이 이상적입니다. 자바스크립트 프로젝트의 경우 다음 항목을 반드시 점검해야 합니다: XSS, CSRF, 인증 우회, 의존성 취약점 등.
테스트 단계에서는 자동화된 보안 스캔(정적/동적), 의존성 취약점 검사, 펜테스트 시나리오를 스크립트로 구성해 반복적으로 돌릴 수 있도록 해야 합니다. 아래는 CI에서 실행 가능한 예시 스크립트과 OWASP ZAP 자동 스캔, npm audit 자동화 예시입니다.
# CI용 보안 검사 스크립트 (bash) - 예시
# 1) 의존성 취약점 검사
echo "[STEP] npm audit start"
npm ci
npm audit --audit-level=moderate || true # audit 레벨에 따라 실패시 파이프라인 종료 조건 조정
# 2) 정적 분석 (ESLint + 보안 플러그인)
echo "[STEP] eslint linting"
npx eslint src --ext .js,.jsx,.ts,.tsx || true
# 3) OWASP ZAP 자동 스캔 (머신에 ZAP이 설치되어 있고, 데몬 모드로 실행 중일 때)
# 스캔 대상은 스테이징 URL을 지정
STAGING_URL="https://staging.myapp.com"
echo "[STEP] OWASP ZAP active scan start for $STAGING_URL"
# 간단 예시: ZAP API 호출 (실무에서는 더 정교한 스크립트 필요)
curl "http://localhost:8080/JSON/ascan/action/scan/?url=${STAGING_URL}&recurse=true" -s -o /dev/null
# 추후 스캔 결과를 API로 받아서 임계치 초과 시 실패 처리
# 4) SCA (Software Composition Analysis) 도구 연동 (예: Snyk)
if command -v snyk >/dev/null 2>&1; then
echo "[STEP] snyk test"
snyk test || true
else
echo "snyk not installed - skipping"
fi
# 5) 결과 아카이브화
mkdir -p security-reports
# (예) npm audit 결과 저장
npm audit --json > security-reports/npm-audit.json || true
echo "[DONE] security checks complete"
5. 배포 전 확인해야 할 보안 체크리스트
배포 직전은 보안적으로 매우 중요한 시점입니다. 프로젝트 전반의 보안 상태를 종합적으로 점검하고, 잠재적 취약점을 최대한 제거한 후 배포해야 합니다. 자동화된 체크리스트를 빌드 파이프라인에 포함시키는 것이 권장됩니다.
아래는 GitHub Actions 예시 워크플로로, 빌드·테스트·보안스캔·비밀값 유출 점검 단계를 포함한 예시입니다.
# .github/workflows/security-ci.yml (YAML 예시)
# 이 워크플로는 PR 및 main 브랜치 병합 시 보안 검사를 포함한 빌드 파이프라인을 실행합니다.
name: CI - Build & Security
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
jobs:
build-and-scan:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: '18'
- name: Install dependencies
run: npm ci
- name: Run tests
run: npm test --silent
- name: Run ESLint
run: npx eslint src || true
- name: Run npm audit and save report
run: npm audit --json > npm-audit.json || true
- name: Upload audit report
uses: actions/upload-artifact@v4
with:
name: npm-audit
path: npm-audit.json
- name: Secret scanning (simple grep for accidental keys)
run: |
# 민감 패턴 간단 검사(실무: 전문 스캐너 사용 권장)
if grep -R --line-number -E "AKIA|BEGIN PRIVATE KEY|ssh-rsa" .; then
echo "Potential secret found" && exit 1
else
echo "No obvious secrets"
fi
# 배포 전 수동 승인 등 정책을 추가 가능
6. 운영/유지보수 시 필수 보안 정책
운영 중에도 보안은 멈추지 않습니다. 정기적인 취약점 점검, 라이브러리 업데이트, 권한 관리 등은 서비스의 생명력을 유지하는 핵심입니다. 모니터링·로그·알림·자동 패치 워크플로를 수립하세요.
운영 중에는 로그·모니터링·자동 패치·권한 재검토·백업 정책이 필수입니다. 아래는 로그 전송(구조화 로그), 알림(간단한 Slack 알림) 및 자동 업데이트(주기적 의존성 체크) 예시입니다.
// 운영(Production) 예시 스크립트 조합 (Node.js 스타일)
/*
- 구조화된 로그(예: JSON) 전송
- 에러 발생 시 알림 전송 (Slack webhook 예시)
- 주기적 의존성 업데이트 트리거(스케줄)
*/
// 1) 구조화 로그 (winston 예시)
const winston = require('winston');
const logger = winston.createLogger({
level: process.env.LOG_LEVEL || 'info',
format: winston.format.json(),
transports: [
new winston.transports.Console(),
// 실무: 로그 집계 서비스(예: ELK, Datadog) 전송 설정 추가
]
});
// 2) 에러 핸들러 및 알림(간단한 Slack webhook)
const fetch = require('node-fetch');
const SLACK_WEBHOOK = process.env.SLACK_WEBHOOK;
async function notifySlack(message) {
if (!SLACK_WEBHOOK) return;
try {
await fetch(SLACK_WEBHOOK, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ text: message })
});
} catch (err) {
logger.error('Slack notify failed', { err: err.message });
}
}
// 3) 프로세스 수준 에러 캡처
process.on('uncaughtException', async (err) => {
logger.error('uncaughtException', { message: err.message, stack: err.stack });
await notifySlack(`uncaughtException: ${err.message}`);
process.exit(1);
});
process.on('unhandledRejection', async (reason) => {
logger.error('unhandledRejection', { reason });
await notifySlack(`unhandledRejection: ${String(reason)}`);
// 필요시 재시작 전략 적용
});
// 4) 주기적 패치 트리거 예시 (cron 스케줄을 통한 의존성 체크)
const { exec } = require('child_process');
function periodicDependencyCheck() {
exec('npm outdated --json', (err, stdout, stderr) => {
if (err && err.code !== 1) {
logger.error('npm outdated failed', { stderr });
return;
}
if (stdout) {
logger.info('Dependencies outdated', { data: stdout });
// 실무: 자동 PR 생성 또는 관리 팀 알림 프로세스 연동
}
});
}
// (예) 매일 한 번 실행되도록 시스템 crontab 또는 서버 스케줄러에 등록
// 0 3 * * * cd /srv/app && node scripts/dependency-check.js
// 5) 권한 재검토 수동/자동화 안내 (주석)
// - 관리자 계정 목록 추출 스크립트 실행, 90일간 사용하지 않은 계정 잠금 등 정책 적용
![]() |
| 자바스크립트 보안설계 핵심 체크리스트 – XSS 차단과 CSP 구성 |
7. 자주 묻는 질문 (FAQ)
자바스크립트는 클라이언트 측에서 실행되며, 코드가 사용자에게 그대로 노출되기 때문에 악의적인 조작이나 공격에 취약할 수 있습니다.
XSS는 악성 스크립트를 주입해 사용자 브라우저에서 실행시키는 공격이고, CSRF는 사용자의 인증 정보를 이용해 서버에 의도하지 않은 요청을 보내는 공격입니다.
기본적으로 서버에서 해시하는 것이 바람직하며, 프론트엔드에서의 해시는 중복 처리 용도로만 사용하고 반드시 HTTPS로 암호화된 전송이 이루어져야 합니다.
JWT는 서명이 유효한지 항상 검증해야 하며, 토큰 만료 시간을 짧게 설정하고 HttpOnly 쿠키를 사용하여 XSS 공격을 방지해야 합니다.
물론입니다. OWASP Top 10은 프론트엔드 개발자가 자주 마주치는 보안 문제들을 이해하고 예방하는 데 필수적인 지식입니다.
8. 마무리 요약
✅ 자바스크립트 프로젝트 보안설계는 선택이 아닌 필수입니다
자바스크립트 기반의 프론트엔드 프로젝트는 사용자와 가장 가까운 계층에서 실행되기에 다양한 보안 위협에 노출되어 있습니다.
요구분석 → 설계 → 구현 → 테스트 → 배포 → 운영까지 전 단계에서 보안을 고려하는 것이 진정한 ‘보안 설계’의 시작입니다.
이 가이드의 체크리스트와 예시 코드를 실무 프로세스(요구사항 문서, 설계 템플릿, CI 파이프라인)에 적용하면 보안 수준을 크게 향상시킬 수 있습니다. 꾸준한 점검과 자동화가 핵심입니다.


댓글
댓글 쓰기