guardia-messenger/utils/security.ts

69 lines
2.3 KiB
TypeScript

/**
* GUARDiA Messenger — 보안 유틸 (현장서비스 모듈 공용)
*
* 보안 원칙 (불변):
* - 서버 IP(ip_addr), SSH 계정(ssh_user), 비밀번호(os_pw_enc)는 화면에 절대 노출 금지
* - 배치 SSH / 터미널 출력의 IP 패턴은 자동 마스킹
* - 파일 경로 traversal(`../`) 입력 차단
*/
/** IPv4 패턴 — 출력 텍스트에서 자동 마스킹 처리 */
const IPV4_RE = /\b(?:\d{1,3})\.(?:\d{1,3})\.(?:\d{1,3})\.(?:\d{1,3})\b/g
/** 텍스트 내 IPv4 주소를 ***.***.***.*** 로 마스킹 */
export function maskIPs(text: string): string {
if (!text) return text
return text.replace(IPV4_RE, '***.***.***.***')
}
/** 위험 명령어 블랙리스트 — 클라이언트단 1차 차단 */
export const BLOCKED_COMMANDS = [
'rm -rf /',
'mkfs',
'dd if=/dev/zero',
'shutdown -h',
'shutdown -r',
'reboot',
':(){:|:&};:',
'> /dev/sda',
'chmod -R 000 /',
'mv /home',
]
/** 명령어 안전성 검사 — 위험 패턴 포함 시 false */
export function validateCommand(cmd: string): boolean {
if (!cmd || !cmd.trim()) return false
const normalized = cmd.replace(/\s+/g, ' ').trim()
return !BLOCKED_COMMANDS.some((b) => normalized.includes(b))
}
/** 차단된 위험 패턴을 반환(있으면), 없으면 null */
export function blockedReason(cmd: string): string | null {
const normalized = cmd.replace(/\s+/g, ' ').trim()
const hit = BLOCKED_COMMANDS.find((b) => normalized.includes(b))
return hit ?? null
}
/** 경로 traversal 차단 — `../`, `..\`, 또는 null byte 포함 시 false */
export function isSafePath(path: string): boolean {
if (!path) return true
if (path.includes('..')) return false
if (path.includes('\0')) return false
return true
}
/**
* 자산/서버 객체에서 민감 필드를 제거한 안전 복사본 반환.
* API 응답에 혹시라도 ip_addr/ssh_user/os_pw_enc가 포함되어도 화면에 닿지 않게 한다.
*/
export function sanitizeAsset<T extends Record<string, any>>(obj: T): Partial<T> {
if (!obj || typeof obj !== 'object') return obj
const SENSITIVE = ['ip_addr', 'ip_address', 'ssh_user', 'ssh_pass', 'os_pw', 'os_pw_enc', 'password', 'private_key']
const out: Record<string, any> = {}
for (const [k, v] of Object.entries(obj)) {
if (SENSITIVE.includes(k)) continue
out[k] = v
}
return out as Partial<T>
}