Skip to main content

클라이언트 앱 보안 가이드

작성일: 2026-04-06 Last Updated: 2026-04-06 대상: Browser Extension, Tauri Desktop App, Flutter Mobile App


1. 코드 서명

플랫폼별 코드 서명 요구사항

플랫폼서명 필수 여부인증서 유형비용
Chrome Web StoreO (자동)Google 서명무료 (등록비 $5)
Firefox Add-onsO (자동)Mozilla 서명무료
macOS (Tauri)O (필수)Apple Developer ID$99/년
Windows (Tauri)권장 (SmartScreen)EV Code Signing$200-400/년
Linux (Tauri)선택적GPG 서명무료
iOS (Flutter)O (필수)Apple Distribution$99/년
Android (Flutter)O (필수)Keystore (자체 서명)무료 (Play 등록비 $25)

macOS 코드 서명 + Notarization

#!/bin/bash
# scripts/sign-macos.sh

# 1. 코드 서명
codesign --deep --force --verify --verbose \
--sign "Developer ID Application: Company Name (TEAM_ID)" \
--options runtime \
--entitlements entitlements.plist \
target/release/bundle/macos/MyApp.app

# 2. 공증(Notarization)
xcrun notarytool submit target/release/bundle/macos/MyApp.dmg \
--apple-id "$APPLE_ID" \
--password "$APP_SPECIFIC_PASSWORD" \
--team-id "$TEAM_ID" \
--wait

# 3. Staple (오프라인 검증용)
xcrun stapler staple target/release/bundle/macos/MyApp.dmg

Windows 코드 서명

#!/bin/bash
# scripts/sign-windows.sh

# EV 인증서 (USB 토큰) 또는 Azure Key Vault 사용
signtool sign /tr http://timestamp.digicert.com /td sha256 \
/fd sha256 /a \
target/release/bundle/nsis/MyApp_x64-setup.exe

# Azure Trusted Signing (CI/CD 환경 권장)
# azure-cli + Trusted Signing 서비스 사용

Android 앱 서명

# Android App Bundle 서명
# Play App Signing 사용 시 (권장): Google이 배포용 서명 관리
# 업로드 키만 로컬에서 관리

# key.properties (Git 제외!)
storePassword=***
keyPassword=***
keyAlias=upload
storeFile=../keys/upload-keystore.jks

iOS 앱 서명

# Xcode Managed Signing (권장) 또는 Manual
# CI/CD: fastlane match 사용

# Fastlane Matchfile
git_url("https://github.com/company/certificates")
storage_mode("git")
type("appstore")
app_identifier("com.example.app")

2. CSP (Content Security Policy)

Browser Extension CSP

// manifest.json (Manifest V3)
{
"content_security_policy": {
"extension_pages": "script-src 'self'; object-src 'self'; style-src 'self' 'unsafe-inline'",
"sandbox": "sandbox allow-scripts; script-src 'self'"
}
}

Manifest V3 제한사항

제한설명
eval() 금지동적 코드 실행 불가
new Function() 금지문자열에서 함수 생성 불가
원격 스크립트 금지<script src="https://..."> 사용 불가
인라인 스크립트 금지<script>...</script> 사용 불가
'unsafe-eval' 금지CSP에서 허용 불가

Tauri CSP

// src-tauri/tauri.conf.json
{
"app": {
"security": {
"csp": "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' https://api.example.com; connect-src 'self' https://api.example.com wss://ws.example.com; font-src 'self'"
}
}
}

Tauri v2 Capabilities (권한 시스템)

// src-tauri/capabilities/default.json
{
"identifier": "default",
"description": "기본 권한",
"windows": ["main"],
"permissions": [
"core:default",
"shell:allow-open",
"dialog:allow-open",
"notification:default",
"clipboard-manager:allow-write",
{
"identifier": "http:default",
"allow": [
{ "url": "https://api.example.com/**" }
]
},
{
"identifier": "fs:allow-read",
"allow": [
{ "path": "$APPDATA/**" }
]
}
]
}

CSP 설정 체크리스트

디렉티브권장 값설명
default-src'self'기본 제한
script-src'self'스크립트 소스
style-src'self' 'unsafe-inline'스타일 (프레임워크 요구 시)
img-src'self' https: data:이미지 소스
connect-src'self' https://api.example.comAPI 엔드포인트
font-src'self'폰트 소스
object-src'none'플러그인 (Flash 등) 차단
frame-src'none'iframe 차단
base-uri'self'base 태그 제한

3. 난독화 / 소스 보호

플랫폼별 난독화 전략

플랫폼도구수준비고
Browser Ext (JS)Terser (기본 minify)낮음CWS 정책상 과도한 난독화 금지
Browser Ext (WASM)Rust -> WASM높음핵심 로직 WASM으로 이동
Tauri (Rust)네이티브 컴파일높음바이너리로 배포, 리버스 엔지니어링 난이도 높음
Tauri (JS)Terser + 소스맵 제거중간프론트엔드 코드
Flutter (iOS)AOT 컴파일높음네이티브 바이너리
Flutter (Android)R8/ProGuard + 난독화높음--obfuscate --split-debug-info

Chrome Web Store 난독화 정책

허용금지
Minification (공백/주석 제거)변수명 의미 없는 치환 (과도)
Tree-shaking코드 흐름 변형
번들링문자열 암호화
타입 제거 (TypeScript)안티 디버깅 코드

Chrome Web Store는 "코드를 읽을 수 있어야 한다"는 정책. 소스맵을 함께 제출하거나, 원본 코드 제출 요구 시 제공 가능해야 함.

Flutter 난독화 빌드

# Android: 난독화 + 디버그 심볼 분리
flutter build appbundle \
--obfuscate \
--split-debug-info=build/debug-info/

# iOS: AOT 컴파일 (자동 난독화 수준 높음)
flutter build ipa \
--obfuscate \
--split-debug-info=build/debug-info/

# 디버그 심볼은 크래시 리포팅 서비스에 업로드
# Firebase Crashlytics, Sentry 등

4. API 키 보호

핵심 원칙: 클라이언트에 시크릿 키를 절대 포함하지 않는다

키 유형클라이언트 포함 가능보호 방법
OAuth Client IDO공개 식별자 (시크릿 아님)
OAuth Client SecretX서버 사이드만
API Secret KeyX프록시 서버 경유
Firebase ConfigO (제한적)보안 규칙으로 접근 제어
서드파티 API 키X백엔드 프록시
암호화 키X서버 사이드 또는 KMS

안전한 API 키 사용 패턴

클라이언트 앱              백엔드 서버              외부 API
| | |
| --- 요청 (Bearer 토큰) --> | |
| | --- 요청 (API Key) --> |
| | <-- 응답 ------------ |
| <-- 응답 (가공된 데이터) -- | |

환경 변수 관리

// 빌드 타임에 주입 (번들에 포함되므로 공개 가능한 값만)
// vite.config.ts
export default defineConfig({
define: {
__API_BASE_URL__: JSON.stringify(process.env.VITE_API_BASE_URL),
__OAUTH_CLIENT_ID__: JSON.stringify(process.env.VITE_OAUTH_CLIENT_ID),
// 절대 시크릿 키를 여기에 포함하지 않음
},
});
// Flutter: --dart-define 또는 .env
// 빌드 시 주입 (공개 가능한 값만)
flutter build apk \
--dart-define=API_BASE_URL=https://api.example.com \
--dart-define=OAUTH_CLIENT_ID=public_client_id

5. 안전한 통신

TLS/HTTPS 요구사항

요구사항설명
HTTPS 필수모든 API 통신은 HTTPS만 허용
TLS 1.2+TLS 1.0/1.1 비활성화
인증서 검증시스템 인증서 저장소 사용
평문 HTTP 금지개발 환경도 HTTPS 권장

Certificate Pinning

플랫폼구현 방법권장도
Browser Ext불가 (브라우저 관리)-
TauriRust reqwest + 커스텀 인증서 검증선택
Flutter (Android)network_security_config.xml권장
Flutter (iOS)TrustKit 또는 ssl_pinning_plugin권장

Android Network Security Config

<!-- android/app/src/main/res/xml/network_security_config.xml -->
<network-security-config>
<!-- 프로덕션: 핀 고정 -->
<domain-config>
<domain includeSubdomains="true">api.example.com</domain>
<pin-set expiration="2027-01-01">
<pin digest="SHA-256">AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=</pin>
<pin digest="SHA-256">BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB=</pin>
</pin-set>
</domain-config>

<!-- 개발: 평문 허용 (로컬만) -->
<domain-config cleartextTrafficPermitted="true">
<domain includeSubdomains="false">localhost</domain>
<domain includeSubdomains="false">10.0.2.2</domain>
</domain-config>
</network-security-config>

CORS 설정 (Laravel 백엔드)

// config/cors.php
return [
'paths' => ['api/*', 'oauth/*'],
'allowed_methods' => ['GET', 'POST', 'PUT', 'PATCH', 'DELETE'],
'allowed_origins' => [
'chrome-extension://<extension-id>',
'tauri://localhost', // Tauri v1
'https://tauri.localhost', // Tauri v2
'http://localhost:*', // 개발용
],
'allowed_headers' => ['*'],
'exposed_headers' => ['X-Request-Id'],
'max_age' => 86400,
'supports_credentials' => true,
];

6. 데이터 암호화 (로컬 저장소)

저장소별 암호화 전략

저장소Browser ExtTauriFlutter
인증 토큰chrome.storage.sessionOS Keychainflutter_secure_storage
사용자 설정chrome.storage.localtauri-plugin-store (암호화 옵션)shared_preferences (비민감)
캐시 데이터IndexedDBSQLite (sqlcipher)sqflite + 암호화
파일-파일 시스템 암호화파일 시스템 암호화

암호화 대상 분류

민감도데이터 예시저장소암호화
높음토큰, 비밀번호, 개인정보보안 저장소 (OS)필수
중간사용자 설정, 검색 기록암호화 DB권장
낮음UI 상태, 테마 설정일반 저장소불필요
캐시API 응답 캐시임시 저장소선택

SQLCipher (Tauri/Flutter)

// Tauri - better-sqlite3 + sqlcipher
import Database from 'better-sqlite3';

const db = new Database('app.db');
db.pragma(`key='${encryptionKey}'`); // 암호화 키 설정
db.pragma('cipher_compatibility = 4');
// Flutter - sqflite_sqlcipher
import 'package:sqflite_sqlcipher/sqflite_sqlcipher.dart';

final db = await openDatabase(
'app.db',
password: encryptionKey, // 암호화 키
version: 1,
onCreate: (db, version) async {
// 테이블 생성
},
);

7. 권한 최소화 원칙

Browser Extension 권한

// manifest.json - 최소 권한
{
"permissions": [
"storage", // 데이터 저장 (거의 필수)
"identity" // OAuth 인증 (필요 시)
],
"optional_permissions": [
"tabs", // 탭 정보 (사용자 동의 후)
"notifications", // 알림 (사용자 동의 후)
"clipboardWrite" // 클립보드 (사용자 동의 후)
],
"host_permissions": [
"https://api.example.com/*" // 특정 도메인만
],
"optional_host_permissions": [
"https://*/*" // 추가 사이트 (사용자 동의 후)
]
}

Browser Extension 권한 심사 영향

권한심사 영향대안
<all_urls>매우 높음 (심사 지연)특정 도메인만 지정
tabs높음activeTab으로 대체
webRequest높음declarativeNetRequest으로 대체
cookies높음세션 스토리지 활용
storage낮음-
notifications낮음-
activeTab낮음권장

Flutter 권한 (iOS/Android)

<!-- Android: AndroidManifest.xml - 필요한 것만 -->
<uses-permission android:name="android.permission.INTERNET" />
<!-- 아래는 필요할 때만 추가 -->
<!-- <uses-permission android:name="android.permission.CAMERA" /> -->
<!-- <uses-permission android:name="android.permission.READ_CONTACTS" /> -->
<!-- iOS: Info.plist - 사용 사유 필수 기재 -->
<key>NSCameraUsageDescription</key>
<string>프로필 사진 촬영을 위해 카메라 접근이 필요합니다</string>
<key>NSPhotoLibraryUsageDescription</key>
<string>프로필 사진 선택을 위해 사진 라이브러리 접근이 필요합니다</string>

Tauri v2 권한 (Capabilities)

// 최소 권한 원칙 적용
{
"identifier": "main-window",
"description": "메인 윈도우 권한",
"windows": ["main"],
"permissions": [
"core:default",
// 네트워크: 특정 도메인만
{
"identifier": "http:default",
"allow": [
{ "url": "https://api.example.com/**" }
],
"deny": [
{ "url": "http://**" }
]
},
// 파일시스템: 앱 데이터 폴더만
{
"identifier": "fs:allow-read",
"allow": [{ "path": "$APPDATA/**" }]
},
// 쉘: 특정 명령만
{
"identifier": "shell:allow-open",
"allow": [
{ "cmd": "open", "args": ["https://*"] }
]
}
]
}

8. 보안 감사 체크리스트

인증/인가

  • OAuth PKCE 사용 (Client Secret 클라이언트 미포함)
  • 토큰이 보안 저장소에만 저장됨
  • 토큰 만료/갱신 정상 동작
  • 로그아웃 시 모든 토큰 삭제
  • CSRF(State) 파라미터 검증
  • Redirect URI 정확한 매칭 (와일드카드 금지)

데이터 보호

  • HTTPS 통신만 허용
  • 민감 데이터 암호화 저장
  • 로그에 토큰/개인정보 미포함
  • 에러 메시지에 내부 정보 미노출
  • 클립보드 복사 시 자동 삭제 (30초)
  • 스크린샷 보호 (모바일, 민감 화면)

코드 보안

  • API 시크릿 키 클라이언트 미포함
  • 환경 변수에 민감 정보 분리
  • 소스맵 프로덕션 빌드에서 제거
  • 디버그 로그 프로덕션 비활성화
  • eval() / Function() 미사용
  • innerHTML 직접 사용 금지 (XSS)

네트워크

  • CSP 설정 완료
  • CORS 허용 origin 최소화
  • Certificate Pinning 적용 (모바일)
  • API Rate Limiting 대응
  • 네트워크 에러 시 민감 정보 미노출

플랫폼 고유

  • Browser Ext: Manifest V3 CSP 준수
  • Browser Ext: 권한 최소화 (optional_permissions 활용)
  • Browser Ext: Content Script 격리 (world: ISOLATED)
  • Tauri: Capabilities 최소 권한 설정
  • Tauri: IPC 커맨드 입력값 검증 (Rust 측)
  • Tauri: allowlist 최소 설정
  • Flutter: ProGuard/R8 난독화 활성화
  • Flutter: 루팅/탈옥 감지 (선택)
  • Flutter: 네트워크 보안 설정 (Android)
  • Flutter: ATS 설정 (iOS)

코드 서명/배포

  • 코드 서명 인증서 유효 기간 확인
  • 서명 키 안전한 보관 (HSM/KMS)
  • CI/CD 파이프라인에서 자동 서명
  • 자동 업데이트 서명 검증
  • 릴리즈 바이너리 해시 검증

서드파티 의존성

  • 알려진 취약점 스캔 (npm audit, cargo audit, pub outdated)
  • 의존성 라이선스 검토
  • 의존성 최소화 (불필요 패키지 제거)
  • Lock 파일 커밋 (package-lock.json, Cargo.lock, pubspec.lock)
  • 정기 의존성 업데이트 (Dependabot/Renovate)

9. 보안 사고 대응

토큰 유출 시

1. 서버에서 해당 토큰 즉시 무효화
2. 영향받는 사용자에게 비밀번호 변경 안내
3. 유출 경로 분석 및 차단
4. 클라이언트 긴급 업데이트 배포
5. 인시던트 리포트 작성

취약점 발견 시

1. 심각도 평가 (Critical/High/Medium/Low)
2. Critical/High: 24시간 내 긴급 패치
3. Medium: 다음 정기 릴리즈에 포함
4. Low: 백로그 등록
5. CVE 발급 필요 여부 판단