로그 파싱·군집·이상탐지·추출·위생검사 — 외부 의존성 0, 한국 PII 우선, 에이전트 네이티브 JSON. Parse, cluster, anomaly-detect, extract, and audit log lines with zero external dependencies and Korean-first PII detection.
호환 플랫폼: any
✅ 보안 위험 항목이 발견되지 않았습니다.
AI 검수 단계
스킬 메타데이터와 코드 파일을 종합적으로 검토한 결과, 다음과 같은 판단 근거로 안전하다고 판단합니다. 1. **권한 일치**: 메타데이터에 `network: false`, `filesystem: false`, `subprocess: false`로 명확히 선언되어 있으며, 제공된 코드 파일(`main.py`, `lib/*.py`) 어디에서도 외부 네트워크 통신, 파일 시스템 접근, 외부 프로세스 실행을 시도하는 코드가 발견되지 않았습니다. 이는 선언된 권한과 실제 코드가 완벽하게 일치함을 의미합니다. 2. **악의적 코드 없음**: 코드 구조는 명확하고 모듈화되어 있으며, 난독화된 부분이 없습니다. 정적 분석 결과에서도 `red_flags_found`, `obfuscation_warnings`, `forbidden_exec_files_found`가 모두 비어 있어 악의적인 목적의 코드가 없음을 뒷받침합니다. 데이터 탈취나 시스템 파괴와 같은 행위를 할 수 있는 기능이 없습니다. 3. **외부 통신 없음**: `network: false` 선언과 코드 검토를 통해 외부 통신이 전혀 없음을 확인했습니다. 4. **데이터 무단 수집/전송 없음**: 스킬은 표준 입력을 통해 로그 데이터를 받아 처리하고, 결과를 표준 출력으로 반환하는 `stdin_stdout` 패턴을 따릅니다. 외부 통신이 없으므로 사용자 데이터를 무단으로 수집하거나 전송할 수 없습니다. 5. **코드 품질 및 목적 일치**: 스킬은 '외부 의존성 0'을 명시하고 있으며, `requirements.python_packages`가 비어 있어 이를 준수합니다. 모든 기능은 Python 표준 라이브러리 내에서 구현되었습니다. 로그 파싱, 군집화, 이상 탐지, 필드 추출, 한국 PII(주민등록번호, 사업자등록번호 등)를 포함한 위생 검사 등 스킬의 목적에 부합하는 기능들이 체계적으로 구현되어 있습니다. `MAX_LINES_DEFAULT` 및 `MAX_BYTES` 제한을 통해 잠재적인 리소스 고갈 공격에 대한 방어 메커니즘도 갖추고 있습니다. 결론적으로, 이 스킬은 선언된 보안 정책을 철저히 준수하며, 악의적인 행위를 할 가능성이 없어 안전하게 스토어에 공개될 수 있습니다.
이 스킬의 대표적인 입출력 예시입니다. 에이전트는 이 예시를 보고 스킬 호출 방법과 결과 형태를 이해할 수 있습니다.
nginx access log 라인을 구조화된 JSON 레코드로 변환합니다. 포맷 자동 감지.
{
"action": "parse",
"language": "ko",
"lines": [
"192.168.1.1 - alice [10/Apr/2025:13:55:36 +0900] \"GET /api/users HTTP/1.1\" 200 1234 \"-\" \"Mozilla/5.0\"",
"10.0.0.5 - - [10/Apr/2025:13:55:37 +0900] \"POST /api/login HTTP/1.1\" 401 89 \"-\" \"curl/7.64.1\""
]
}
{
"action": "parse",
"parsed": [
{
"fields": {
"body_bytes": 1234,
"method": "GET",
"path": "/api/users",
"protocol": "HTTP/1.1",
"referer": "-",
"remote_addr": "192.168.1.1",
"remote_user": "alice",
"status": 200,
"user_agent": "Mozilla/5.0"
},
"format_detected": "nginx",
"level": "INFO",
"line_no": 1,
"message": "GET /api/users HTTP/1.1",
"raw": "192.168.1.1 - alice [10/Apr/2025:13:55:36 +0900] \"GET /api/users HTTP/1.1\" 200 1234 \"-\" \"Mozilla/5.0\"",
"source": "192.168.1.1",
"timestamp": "2025-04-10T13:55:36+09:00"
},
{
"fields": {
"body_bytes": 89,
"method": "POST",
"path": "/api/login",
"protocol": "HTTP/1.1",
"referer": "-",
"remote_addr": "10.0.0.5",
"remote_user": null,
"status": 401,
"user_agent": "curl/7.64.1"
},
"format_detected": "nginx",
"level": "WARN",
"line_no": 2,
"message": "POST /api/login HTTP/1.1",
"raw": "10.0.0.5 - - [10/Apr/2025:13:55:37 +0900] \"POST /api/login HTTP/1.1\" 401 89 \"-\" \"curl/7.64.1\"",
"source": "10.0.0.5",
"timestamp": "2025-04-10T13:55:37+09:00"
}
],
"total_lines": 2
}
비슷한 로그 메시지를 군집화하고 템플릿을 추출합니다.
{
"action": "cluster",
"language": "ko",
"lines": [
"2025-04-10 13:00:01 INFO [app] User 1001 logged in from 192.168.1.1",
"2025-04-10 13:00:05 INFO [app] User 1002 logged in from 10.0.0.1",
"2025-04-10 13:00:10 INFO [app] User 9999 logged in from 172.16.0.1",
"2025-04-10 13:01:00 ERROR [db] Connection timeout after 30s retries=3",
"2025-04-10 13:01:05 ERROR [db] Connection timeout after 60s retries=5"
],
"min_cluster_size": 2
}
{
"action": "cluster",
"clusters": [
{
"cluster_id": 1,
"count": 3,
"examples": [
"User 1001 logged in from 192.168.1.1",
"User 1002 logged in from 10.0.0.1"
],
"first_seen": "2025-04-10T13:00:01",
"last_seen": "2025-04-10T13:00:10",
"template": "User \u003c*\u003e logged in from \u003c*\u003e",
"variability_score": 0.67
},
{
"cluster_id": 2,
"count": 2,
"examples": [
"Connection timeout after 30s retries=3"
],
"first_seen": "2025-04-10T13:01:00",
"last_seen": "2025-04-10T13:01:05",
"template": "Connection timeout after \u003c*\u003e retries=\u003c*\u003e",
"variability_score": 0.5
}
],
"total_lines": 5
}
시간대별 로그 빈도 분석으로 ERROR 급증과 신규 패턴을 탐지합니다.
{
"action": "anomaly",
"error_ratio_threshold": 0.5,
"language": "ko",
"lines": [
"2025-04-10 13:00:01 INFO [app] request processed",
"2025-04-10 13:00:02 INFO [app] request processed",
"2025-04-10 13:05:01 ERROR [db] disk full",
"2025-04-10 13:05:02 ERROR [db] disk full",
"2025-04-10 13:05:03 ERROR [db] disk full",
"2025-04-10 13:05:04 ERROR [db] disk full",
"2025-04-10 13:05:05 FATAL [sys] OOM killer activated pid=1234"
],
"window_minutes": 5,
"zscore_threshold": 2.0
}
{
"action": "anomaly",
"anomalies": [
{
"affected_lines": [
3,
4,
5,
6,
7
],
"description_en": "ERROR/FATAL ratio exceeds threshold (71.4% \u003e 50.0%)",
"description_ko": "ERROR/FATAL \ube44\uc728\uc774 \uc784\uacc4\uac12\uc744 \ucd08\uacfc\ud588\uc2b5\ub2c8\ub2e4 (71.4% \u003e 50.0%)",
"severity": "critical",
"threshold": 0.5,
"type": "high_error_ratio",
"value": 0.714,
"window_end": "2025-04-10T13:05:05",
"window_start": "2025-04-10T13:05:01"
},
{
"affected_lines": [
7
],
"description_en": "New log template appeared: \u0027OOM killer activated pid=\u003c*\u003e\u0027",
"description_ko": "\uc2e0\uaddc \ub85c\uadf8 \ud15c\ud50c\ub9bf \ucd9c\ud604: \u0027OOM killer activated pid=\u003c*\u003e\u0027",
"severity": "high",
"threshold": 0.0,
"type": "new_template",
"value": 1.0,
"window_end": "2025-04-10T13:05:05",
"window_start": "2025-04-10T13:05:05"
}
],
"total_lines": 7
}
로그에서 IP 주소, URL, 레이턴시(ms) 값을 추출합니다.
{
"action": "extract",
"extract_fields": [
"ip_addresses",
"urls",
"latency_ms"
],
"language": "ko",
"lines": [
"2025-04-10 13:00:01 INFO request from 192.168.1.1 to https://api.example.com/v1/users took 152ms",
"2025-04-10 13:00:02 WARN slow query from 10.0.0.5, latency=3200ms, url=http://db.internal/query"
]
}
{
"action": "extract",
"extracted": {
"ip_addresses": [
{
"line_no": 1,
"value": "192.168.1.1"
},
{
"line_no": 2,
"value": "10.0.0.5"
}
],
"latency_ms": [
{
"line_no": 1,
"raw": "152ms",
"value": 152
},
{
"line_no": 2,
"raw": "3200ms",
"value": 3200
}
],
"urls": [
{
"line_no": 1,
"value": "https://api.example.com/v1/users"
},
{
"line_no": 2,
"value": "http://db.internal/query"
}
]
},
"total_lines": 2
}
로그에서 한국 주민번호 패턴을 탐지하고 수정 힌트를 제공합니다.
{
"action": "audit",
"language": "ko",
"lines": [
"2025-04-10 INFO user registered: name=\ud64d\uae38\ub3d9 ssn=900101-1234567 email=hong@example.com",
"2025-04-10 INFO payment: card=4111-1111-1111-1111 amount=50000"
]
}
{
"action": "audit",
"audit_findings": [
{
"affected_lines": [
1
],
"category": "SEC",
"fix_hint": {
"action": "\ub9c8\uc2a4\ud0b9 \ucc98\ub9ac",
"location": "\ub77c\uc778 1, ssn \ud544\ub4dc",
"reference": "https://www.pipc.go.kr/np/default/page.do?mCode=C020010000",
"suggested_replacement": "900101-*******"
},
"message_en": "Korean national ID number detected in log. Immediate masking required.",
"message_ko": "\ub85c\uadf8\uc5d0 \uc8fc\ubbfc\ub4f1\ub85d\ubc88\ud638 \ud328\ud134\uc774 \uac10\uc9c0\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \uc989\uc2dc \ub9c8\uc2a4\ud0b9 \ud544\uc694.",
"rule_id": "SEC-KR-SSN",
"sample_value": "900101-1234567",
"severity": "critical",
"title_en": "Korean SSN (\uc8fc\ubbfc\ubc88\ud638) Exposure",
"title_ko": "\ud55c\uad6d \uc8fc\ubbfc\ub4f1\ub85d\ubc88\ud638 \ub178\ucd9c"
},
{
"affected_lines": [
2
],
"category": "SEC",
"fix_hint": {
"action": "\ub9c8\uc2a4\ud0b9 \ucc98\ub9ac",
"location": "\ub77c\uc778 2, card \ud544\ub4dc",
"reference": "https://www.pci-sss.org/en/documents/pci-dss",
"suggested_replacement": "4111-****-****-1111"
},
"message_en": "Card number pattern passing Luhn check detected in log.",
"message_ko": "Luhn \uac80\uc99d\uc744 \ud1b5\uacfc\ud558\ub294 \uce74\ub4dc\ubc88\ud638 \ud328\ud134\uc774 \uac10\uc9c0\ub418\uc5c8\uc2b5\ub2c8\ub2e4.",
"rule_id": "SEC-CARD-NUMBER",
"sample_value": "4111-1111-1111-1111",
"severity": "critical",
"title_en": "Credit/Debit Card Number Exposure",
"title_ko": "\uce74\ub4dc\ubc88\ud638 \ub178\ucd9c"
}
],
"audit_summary": {
"critical_count": 2,
"high_count": 0,
"low_count": 0,
"medium_count": 0,
"passed": false,
"risk_level": "critical",
"total_findings": 2
},
"total_lines": 2
}
로그에서 API 키, 패스워드, JWT 토큰 노출을 탐지합니다.
{
"action": "audit",
"language": "ko",
"lines": [
"2025-04-10 DEBUG config loaded: OPENAI_API_KEY=sk-proj-AbCdEfGhIjKlMnOpQrStUvWx",
"2025-04-10 INFO login attempt: user=admin password=P@ssw0rd123!",
"2025-04-10 DEBUG auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0In0.abc"
]
}
{
"action": "audit",
"audit_findings": [
{
"affected_lines": [
1
],
"category": "SEC",
"fix_hint": {
"action": "\ud658\uacbd\ubcc0\uc218 \ub610\ub294 \uc2dc\ud06c\ub9bf \ub9e4\ub2c8\uc800\ub85c \uc774\ub3d9",
"location": "\ub77c\uc778 1, OPENAI_API_KEY",
"reference": "https://owasp.org/www-community/vulnerabilities/Exposed_Sensitive_Information",
"suggested_replacement": "OPENAI_API_KEY=sk-proj-***"
},
"message_en": "Known API key pattern (sk-proj-...) exposed in log.",
"message_ko": "\uc54c\ub824\uc9c4 API \ud0a4 \ud328\ud134(sk-proj-...)\uc774 \ub85c\uadf8\uc5d0 \ub178\ucd9c\ub418\uc5c8\uc2b5\ub2c8\ub2e4.",
"rule_id": "SEC-API-KEY",
"sample_value": "sk-proj-AbCdEfGhI...",
"severity": "critical",
"title_en": "API Key Exposure",
"title_ko": "API \ud0a4 \ub178\ucd9c"
},
{
"affected_lines": [
2
],
"category": "SEC",
"fix_hint": {
"action": "\ub85c\uae45 \uc804 \ub9c8\uc2a4\ud0b9",
"location": "\ub77c\uc778 2",
"reference": "https://cheatsheetseries.owasp.org/cheatsheets/Logging_Cheat_Sheet.html",
"suggested_replacement": "password=***"
},
"message_en": "Plaintext value after password= field found in log.",
"message_ko": "password= \ud328\ud134 \ub4a4\uc5d0 \ud3c9\ubb38 \uac12\uc774 \ub85c\uadf8\uc5d0 \ucd9c\ub825\ub418\uc5c8\uc2b5\ub2c8\ub2e4.",
"rule_id": "SEC-PASSWORD-FIELD",
"sample_value": "password=P@ssw0rd123!",
"severity": "high",
"title_en": "Password Field Exposure",
"title_ko": "\ud328\uc2a4\uc6cc\ub4dc \ud544\ub4dc \ub178\ucd9c"
},
{
"affected_lines": [
3
],
"category": "SEC",
"fix_hint": {
"action": "\ud1a0\ud070 \ub9c8\uc2a4\ud0b9 \ub610\ub294 \ub85c\uae45 \uc81c\uac70",
"location": "\ub77c\uc778 3, auth header",
"reference": "https://cheatsheetseries.owasp.org/cheatsheets/JSON_Web_Token_for_Java_Cheat_Sheet.html",
"suggested_replacement": "Bearer eyJhbGci...***"
},
"message_en": "JWT token found in Bearer header or Authorization field.",
"message_ko": "Bearer \ud5e4\ub354 \ub610\ub294 Authorization \ud544\ub4dc\uc5d0 JWT \ud1a0\ud070\uc774 \ud3ec\ud568\ub418\uc5b4 \uc788\uc2b5\ub2c8\ub2e4.",
"rule_id": "SEC-JWT",
"sample_value": "eyJhbGci...",
"severity": "high",
"title_en": "JWT Token Exposure",
"title_ko": "JWT \ud1a0\ud070 \ub178\ucd9c"
}
],
"audit_summary": {
"critical_count": 1,
"high_count": 2,
"low_count": 0,
"medium_count": 0,
"passed": false,
"risk_level": "critical",
"total_findings": 3
},
"total_lines": 3
}
Java/Python 예외 스택 트레이스와 request ID를 추출합니다.
{
"action": "extract",
"extract_fields": [
"request_ids",
"stack_traces",
"latency_ms",
"status_codes"
],
"language": "ko",
"lines": [
"2025-04-10 13:00:01 ERROR [req-abc123] NullPointerException at com.example.UserService.getUser(UserService.java:42)",
"\tat com.example.UserService.getUser(UserService.java:42)",
"\tat com.example.ApiController.handle(ApiController.java:18)",
"2025-04-10 13:00:02 INFO [req-def456] request completed in 250ms status=200"
]
}
{
"action": "extract",
"extracted": {
"latency_ms": [
{
"line_no": 4,
"raw": "250ms",
"value": 250
}
],
"request_ids": [
{
"line_no": 1,
"value": "req-abc123"
},
{
"line_no": 4,
"value": "req-def456"
}
],
"stack_traces": [
{
"exception_type": "NullPointerException",
"frame_count": 2,
"line_nos": [
1,
2,
3
],
"value": "NullPointerException at com.example.UserService.getUser(UserService.java:42)"
}
],
"status_codes": [
{
"line_no": 4,
"value": 200
}
]
},
"total_lines": 4
}
QUAL 규칙으로 타임스탬프 누락, 줄 길이 초과, 레벨 표기 불일치를 감지합니다.
{
"action": "audit",
"audit_rules": [
"QUAL-NO-TIMESTAMP",
"QUAL-LEVEL-INCONSISTENT"
],
"language": "ko",
"lines": [
"INFO [app] server started",
"2025-04-10 13:00:01 info [app] request received",
"2025-04-10 13:00:02 WARNING [app] slow query detected"
]
}
{
"action": "audit",
"audit_findings": [
{
"affected_lines": [
1
],
"category": "QUAL",
"fix_hint": {
"action": "\ud0c0\uc784\uc2a4\ud0ec\ud504 \ucd94\uac00",
"location": "\ub77c\uc778 1",
"reference": "https://www.rfc-editor.org/rfc/rfc3339",
"suggested_replacement": "2025-04-10T13:00:00Z INFO [app] server started"
},
"message_en": "Log line has no recognizable timestamp.",
"message_ko": "\ub85c\uadf8 \ub77c\uc778\uc5d0 \uc778\uc2dd \uac00\ub2a5\ud55c \ud0c0\uc784\uc2a4\ud0ec\ud504\uac00 \uc5c6\uc2b5\ub2c8\ub2e4.",
"rule_id": "QUAL-NO-TIMESTAMP",
"sample_value": "INFO [app] server started",
"severity": "medium",
"title_en": "Missing Timestamp",
"title_ko": "\ud0c0\uc784\uc2a4\ud0ec\ud504 \ub204\ub77d"
},
{
"affected_lines": [
2,
3
],
"category": "QUAL",
"fix_hint": {
"action": "\ub808\ubca8 \ud45c\uae30 \ud1b5\uc77c",
"location": "\ub77c\uc778 2, 3",
"reference": "https://www.rfc-editor.org/rfc/rfc5424#section-6.2.1",
"suggested_replacement": "INFO, WARN"
},
"message_en": "Log level casing is inconsistent across lines (INFO, info, WARNING, etc.).",
"message_ko": "\ub85c\uadf8 \ub808\ubca8 \ud45c\uae30\uac00 \ub300\uc18c\ubb38\uc790 \ud63c\uc6a9\ub418\uc5c8\uc2b5\ub2c8\ub2e4 (INFO, info, WARNING \ub4f1).",
"rule_id": "QUAL-LEVEL-INCONSISTENT",
"sample_value": "info, WARNING",
"severity": "low",
"title_en": "Inconsistent Log Level Casing",
"title_ko": "\ub808\ubca8 \ud45c\uae30 \ubd88\uc77c\uce58"
}
],
"audit_summary": {
"critical_count": 0,
"high_count": 0,
"low_count": 1,
"medium_count": 1,
"passed": true,
"risk_level": "low",
"total_findings": 2
},
"total_lines": 3
}
모든 예시는 에이전트 API로도 조회 가능:
/v1/agent/skills/690f5ed4-c931-4fa7-98e9-0d1143cd8b37/schema
아직 리뷰가 없습니다. 첫 번째 리뷰를 남겨보세요!