memento-mcp-server 1.16.2-a → 1.16.3-a
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/domains/anchor/services/anchor/n-hop-search-service.d.ts.map +1 -1
- package/dist/domains/anchor/services/anchor/n-hop-search-service.js +4 -5
- package/dist/domains/anchor/services/anchor/n-hop-search-service.js.map +1 -1
- package/dist/domains/embedding/services/embedding-service.d.ts.map +1 -1
- package/dist/domains/embedding/services/embedding-service.js +5 -2
- package/dist/domains/embedding/services/embedding-service.js.map +1 -1
- package/dist/domains/embedding/services/gemini-embedding-service.d.ts.map +1 -1
- package/dist/domains/embedding/services/gemini-embedding-service.js +6 -3
- package/dist/domains/embedding/services/gemini-embedding-service.js.map +1 -1
- package/dist/domains/embedding/services/lightweight-embedding-service.d.ts.map +1 -1
- package/dist/domains/embedding/services/lightweight-embedding-service.js +4 -2
- package/dist/domains/embedding/services/lightweight-embedding-service.js.map +1 -1
- package/dist/domains/embedding/services/minilm-embedding-service.d.ts.map +1 -1
- package/dist/domains/embedding/services/minilm-embedding-service.js +6 -3
- package/dist/domains/embedding/services/minilm-embedding-service.js.map +1 -1
- package/dist/domains/embedding/services/openai-embedding-service.d.ts.map +1 -1
- package/dist/domains/embedding/services/openai-embedding-service.js +5 -2
- package/dist/domains/embedding/services/openai-embedding-service.js.map +1 -1
- package/dist/domains/embedding/services/unified-embedding-service.d.ts.map +1 -1
- package/dist/domains/embedding/services/unified-embedding-service.js +6 -3
- package/dist/domains/embedding/services/unified-embedding-service.js.map +1 -1
- package/dist/domains/forgetting/services/forgetting-policy-service.d.ts.map +1 -1
- package/dist/domains/forgetting/services/forgetting-policy-service.js +3 -1
- package/dist/domains/forgetting/services/forgetting-policy-service.js.map +1 -1
- package/dist/domains/memory/services/memory-embedding-service.d.ts +1 -0
- package/dist/domains/memory/services/memory-embedding-service.d.ts.map +1 -1
- package/dist/domains/memory/services/memory-embedding-service.js +43 -39
- package/dist/domains/memory/services/memory-embedding-service.js.map +1 -1
- package/dist/domains/memory/services/memory-neighbor-service.d.ts.map +1 -1
- package/dist/domains/memory/services/memory-neighbor-service.js +13 -4
- package/dist/domains/memory/services/memory-neighbor-service.js.map +1 -1
- package/dist/domains/memory/tools/convert-episodic-to-semantic-tool.js +5 -6
- package/dist/domains/memory/tools/convert-episodic-to-semantic-tool.js.map +1 -1
- package/dist/domains/memory/tools/forget-tool.d.ts.map +1 -1
- package/dist/domains/memory/tools/forget-tool.js +5 -2
- package/dist/domains/memory/tools/forget-tool.js.map +1 -1
- package/dist/domains/memory/tools/memory-injection-prompt.d.ts.map +1 -1
- package/dist/domains/memory/tools/memory-injection-prompt.js +3 -1
- package/dist/domains/memory/tools/memory-injection-prompt.js.map +1 -1
- package/dist/domains/memory/tools/pin-tool.d.ts.map +1 -1
- package/dist/domains/memory/tools/pin-tool.js +5 -2
- package/dist/domains/memory/tools/pin-tool.js.map +1 -1
- package/dist/domains/memory/tools/unpin-tool.d.ts.map +1 -1
- package/dist/domains/memory/tools/unpin-tool.js +5 -2
- package/dist/domains/memory/tools/unpin-tool.js.map +1 -1
- package/dist/domains/monitoring/services/error-logging-service.d.ts +5 -0
- package/dist/domains/monitoring/services/error-logging-service.d.ts.map +1 -1
- package/dist/domains/monitoring/services/error-logging-service.js +28 -10
- package/dist/domains/monitoring/services/error-logging-service.js.map +1 -1
- package/dist/domains/monitoring/services/performance-alert-service.d.ts.map +1 -1
- package/dist/domains/monitoring/services/performance-alert-service.js +5 -2
- package/dist/domains/monitoring/services/performance-alert-service.js.map +1 -1
- package/dist/domains/relation/services/relation-graph.d.ts.map +1 -1
- package/dist/domains/relation/services/relation-graph.js +3 -4
- package/dist/domains/relation/services/relation-graph.js.map +1 -1
- package/dist/domains/search/algorithms/hybrid-search-engine.d.ts.map +1 -1
- package/dist/domains/search/algorithms/hybrid-search-engine.js +15 -10
- package/dist/domains/search/algorithms/hybrid-search-engine.js.map +1 -1
- package/dist/domains/search/algorithms/search-engine.d.ts.map +1 -1
- package/dist/domains/search/algorithms/search-engine.js +8 -4
- package/dist/domains/search/algorithms/search-engine.js.map +1 -1
- package/dist/domains/search/algorithms/vector-search-engine-migration.d.ts.map +1 -1
- package/dist/domains/search/algorithms/vector-search-engine-migration.js +9 -4
- package/dist/domains/search/algorithms/vector-search-engine-migration.js.map +1 -1
- package/dist/domains/search/algorithms/vector-search-engine.d.ts +1 -0
- package/dist/domains/search/algorithms/vector-search-engine.d.ts.map +1 -1
- package/dist/domains/search/algorithms/vector-search-engine.js +25 -22
- package/dist/domains/search/algorithms/vector-search-engine.js.map +1 -1
- package/dist/domains/search/repositories/vector-performance.repository.d.ts.map +1 -1
- package/dist/domains/search/repositories/vector-performance.repository.js +3 -1
- package/dist/domains/search/repositories/vector-performance.repository.js.map +1 -1
- package/dist/domains/search/repositories/vector-search.repository.d.ts +1 -0
- package/dist/domains/search/repositories/vector-search.repository.d.ts.map +1 -1
- package/dist/domains/search/repositories/vector-search.repository.js +154 -149
- package/dist/domains/search/repositories/vector-search.repository.js.map +1 -1
- package/dist/domains/search/services/vector-search/vector-index-manager.d.ts.map +1 -1
- package/dist/domains/search/services/vector-search/vector-index-manager.js +7 -3
- package/dist/domains/search/services/vector-search/vector-index-manager.js.map +1 -1
- package/dist/domains/search/services/vector-search/vector-performance-tester.d.ts.map +1 -1
- package/dist/domains/search/services/vector-search/vector-performance-tester.js +3 -1
- package/dist/domains/search/services/vector-search/vector-performance-tester.js.map +1 -1
- package/dist/infrastructure/database/database/init.d.ts.map +1 -1
- package/dist/infrastructure/database/database/init.js +29 -7
- package/dist/infrastructure/database/database/init.js.map +1 -1
- package/dist/infrastructure/database/database/migrate.d.ts.map +1 -1
- package/dist/infrastructure/database/database/migrate.js +5 -2
- package/dist/infrastructure/database/database/migrate.js.map +1 -1
- package/dist/infrastructure/database/database/migration/backup-manager.d.ts.map +1 -1
- package/dist/infrastructure/database/database/migration/backup-manager.js +11 -5
- package/dist/infrastructure/database/database/migration/backup-manager.js.map +1 -1
- package/dist/infrastructure/database/database/migration/migration-detector.d.ts.map +1 -1
- package/dist/infrastructure/database/database/migration/migration-detector.js +3 -1
- package/dist/infrastructure/database/database/migration/migration-detector.js.map +1 -1
- package/dist/infrastructure/database/database/migration/migration-logger.d.ts.map +1 -1
- package/dist/infrastructure/database/database/migration/migration-logger.js +5 -2
- package/dist/infrastructure/database/database/migration/migration-logger.js.map +1 -1
- package/dist/infrastructure/database/database/migration/migration-runner.d.ts.map +1 -1
- package/dist/infrastructure/database/database/migration/migration-runner.js +24 -13
- package/dist/infrastructure/database/database/migration/migration-runner.js.map +1 -1
- package/dist/infrastructure/database/database/migration/schema-version-manager.d.ts.map +1 -1
- package/dist/infrastructure/database/database/migration/schema-version-manager.js +7 -3
- package/dist/infrastructure/database/database/migration/schema-version-manager.js.map +1 -1
- package/dist/infrastructure/database/database-optimizer.d.ts.map +1 -1
- package/dist/infrastructure/database/database-optimizer.js +7 -6
- package/dist/infrastructure/database/database-optimizer.js.map +1 -1
- package/dist/infrastructure/database/migration-history-service.d.ts.map +1 -1
- package/dist/infrastructure/database/migration-history-service.js +3 -1
- package/dist/infrastructure/database/migration-history-service.js.map +1 -1
- package/dist/infrastructure/logging/triple-extraction-logger.d.ts +6 -0
- package/dist/infrastructure/logging/triple-extraction-logger.d.ts.map +1 -1
- package/dist/infrastructure/logging/triple-extraction-logger.js +50 -12
- package/dist/infrastructure/logging/triple-extraction-logger.js.map +1 -1
- package/dist/infrastructure/scheduler/batch-scheduler.d.ts.map +1 -1
- package/dist/infrastructure/scheduler/batch-scheduler.js +5 -2
- package/dist/infrastructure/scheduler/batch-scheduler.js.map +1 -1
- package/dist/infrastructure/scheduler/file-logger.d.ts +4 -1
- package/dist/infrastructure/scheduler/file-logger.d.ts.map +1 -1
- package/dist/infrastructure/scheduler/file-logger.js +46 -16
- package/dist/infrastructure/scheduler/file-logger.js.map +1 -1
- package/dist/scripts/check-migration-status.d.ts.map +1 -1
- package/dist/scripts/check-migration-status.js +19 -6
- package/dist/scripts/check-migration-status.js.map +1 -1
- package/dist/services/quality-assurance/quality-recorder.js +2 -2
- package/dist/services/quality-assurance/quality-recorder.js.map +1 -1
- package/dist/services/quality-assurance/quality-threshold-manager.js +1 -1
- package/dist/services/quality-assurance/quality-threshold-manager.js.map +1 -1
- package/dist/shared/config/environment.d.ts.map +1 -1
- package/dist/shared/config/environment.js +3 -1
- package/dist/shared/config/environment.js.map +1 -1
- package/dist/shared/utils/fts5-migration-status.d.ts.map +1 -1
- package/dist/shared/utils/fts5-migration-status.js +6 -2
- package/dist/shared/utils/fts5-migration-status.js.map +1 -1
- package/dist/shared/utils/logger.d.ts.map +1 -1
- package/dist/shared/utils/logger.js +15 -2
- package/dist/shared/utils/logger.js.map +1 -1
- package/dist/shared/utils/path-validator.d.ts +27 -0
- package/dist/shared/utils/path-validator.d.ts.map +1 -0
- package/dist/shared/utils/path-validator.js +166 -0
- package/dist/shared/utils/path-validator.js.map +1 -0
- package/dist/shared/utils/pii-masker.d.ts +31 -0
- package/dist/shared/utils/pii-masker.d.ts.map +1 -1
- package/dist/shared/utils/pii-masker.js +99 -0
- package/dist/shared/utils/pii-masker.js.map +1 -1
- package/dist/shared/utils/procedural-memory-extractor.d.ts.map +1 -1
- package/dist/shared/utils/procedural-memory-extractor.js +39 -33
- package/dist/shared/utils/procedural-memory-extractor.js.map +1 -1
- package/dist/shared/utils/prompt-template-loader.d.ts +6 -0
- package/dist/shared/utils/prompt-template-loader.d.ts.map +1 -1
- package/dist/shared/utils/prompt-template-loader.js +20 -8
- package/dist/shared/utils/prompt-template-loader.js.map +1 -1
- package/dist/shared/utils/reflection-notes-merge.d.ts.map +1 -1
- package/dist/shared/utils/reflection-notes-merge.js +5 -4
- package/dist/shared/utils/reflection-notes-merge.js.map +1 -1
- package/dist/shared/utils/sql-security-validator.d.ts +25 -0
- package/dist/shared/utils/sql-security-validator.d.ts.map +1 -0
- package/dist/shared/utils/sql-security-validator.js +67 -0
- package/dist/shared/utils/sql-security-validator.js.map +1 -0
- package/dist/shared/utils/write-coalescing.d.ts.map +1 -1
- package/dist/shared/utils/write-coalescing.js +7 -3
- package/dist/shared/utils/write-coalescing.js.map +1 -1
- package/package.json +2 -1
- package/scripts/backup-daily.bat +10 -2
- package/scripts/backup-embeddings.js +22 -2
- package/scripts/check-path-traversal.ts +370 -0
- package/scripts/check-pii-masking.ts +360 -0
- package/scripts/check-sql-injection.ts +610 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "memento-mcp-server",
|
|
3
|
-
"version": "1.16.
|
|
3
|
+
"version": "1.16.3-a",
|
|
4
4
|
"description": "AI Agent 기억 보조 MCP 서버 - 사람의 기억 구조를 모사한 스토리지+검색+요약+망각 메커니즘",
|
|
5
5
|
"main": "dist/server/index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -127,6 +127,7 @@
|
|
|
127
127
|
"@typescript-eslint/parser": "^6.13.0",
|
|
128
128
|
"@vitest/coverage-v8": "^1.0.0",
|
|
129
129
|
"eslint": "^8.54.0",
|
|
130
|
+
"eslint-plugin-security": "^3.0.1",
|
|
130
131
|
"eventsource": "^4.0.0",
|
|
131
132
|
"tsx": "^4.6.0",
|
|
132
133
|
"typescript": "^5.3.0",
|
package/scripts/backup-daily.bat
CHANGED
|
@@ -1,17 +1,25 @@
|
|
|
1
1
|
@echo off
|
|
2
2
|
REM Memento 데이터베이스 일일 백업 스크립트
|
|
3
3
|
REM 실행 시간: 매일 오전 2시
|
|
4
|
+
REM PRD 0019: 보안 강화 (Phase 1) - Path Traversal 방지
|
|
4
5
|
|
|
5
6
|
set timestamp=%date:~0,4%%date:~5,2%%date:~8,2%-%time:~0,2%%time:~3,2%%time:~6,2%
|
|
6
7
|
set timestamp=%timestamp: =0%
|
|
7
8
|
|
|
8
9
|
echo [%date% %time%] 데이터베이스 백업 시작...
|
|
9
10
|
|
|
10
|
-
REM 백업 디렉토리 생성
|
|
11
|
+
REM 백업 디렉토리 생성 (PRD 0019: Path Traversal 방지 - 허용된 디렉토리만 사용)
|
|
11
12
|
if not exist "backup" mkdir backup
|
|
12
13
|
|
|
14
|
+
REM 백업 파일명 정제 (PRD 0019: Path Traversal 방지)
|
|
15
|
+
REM timestamp는 날짜/시간에서 생성되므로 상대적으로 안전하지만,
|
|
16
|
+
REM 추가 보안을 위해 Node.js 유틸리티로 파일명 정제 (선택사항)
|
|
17
|
+
set backupFileName=memory-backup-%timestamp%.db
|
|
18
|
+
|
|
13
19
|
REM 메인 데이터베이스 백업
|
|
14
|
-
|
|
20
|
+
REM PRD 0019: Path Traversal 방지 - 경로는 하드코딩되어 있어 상대적으로 안전
|
|
21
|
+
REM data\memory.db와 backup\ 디렉토리는 기본 허용 디렉토리 목록에 포함됨
|
|
22
|
+
copy "data\memory.db" "backup\%backupFileName%"
|
|
15
23
|
|
|
16
24
|
if %errorlevel% equ 0 (
|
|
17
25
|
echo [%date% %time%] 백업 완료: memory-backup-%timestamp%.db
|
|
@@ -9,19 +9,39 @@ import Database from 'better-sqlite3';
|
|
|
9
9
|
import fs from 'fs';
|
|
10
10
|
import path from 'path';
|
|
11
11
|
import { fileURLToPath } from 'url';
|
|
12
|
+
import { validateFilePath, sanitizeFileName } from '../src/shared/utils/path-validator.js';
|
|
12
13
|
|
|
13
14
|
const __filename = fileURLToPath(import.meta.url);
|
|
14
15
|
const __dirname = path.dirname(__filename);
|
|
15
16
|
|
|
16
17
|
// 데이터베이스 경로 설정
|
|
18
|
+
// PRD 0019: 보안 강화 (Phase 1) - Path Traversal 방지
|
|
17
19
|
const dbPath = process.env.DB_PATH || path.join(__dirname, '..', 'data', 'memory.db');
|
|
20
|
+
if (!validateFilePath(dbPath, 'data')) {
|
|
21
|
+
throw new Error(
|
|
22
|
+
`Path Traversal 방지: 허용되지 않은 데이터베이스 경로입니다. ` +
|
|
23
|
+
`경로: ${dbPath}`
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
|
|
18
27
|
const backupDir = path.join(__dirname, '..', 'backup');
|
|
19
|
-
|
|
28
|
+
if (!validateFilePath(backupDir, 'backup')) {
|
|
29
|
+
throw new Error(
|
|
30
|
+
`Path Traversal 방지: 허용되지 않은 백업 디렉토리 경로입니다. ` +
|
|
31
|
+
`경로: ${backupDir}`
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// 백업 파일명 정제
|
|
36
|
+
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
|
|
37
|
+
const sanitizedFileName = sanitizeFileName(`embeddings-backup-${timestamp}.json`);
|
|
38
|
+
const backupFile = path.join(backupDir, sanitizedFileName);
|
|
20
39
|
|
|
21
40
|
async function backupEmbeddings() {
|
|
22
41
|
console.log('🔄 임베딩 백업 시작...');
|
|
23
42
|
|
|
24
|
-
// 백업 디렉토리 생성
|
|
43
|
+
// 백업 디렉토리 생성 (PRD 0019: Path Traversal 방지)
|
|
44
|
+
// 경로 검증은 이미 위에서 수행됨
|
|
25
45
|
if (!fs.existsSync(backupDir)) {
|
|
26
46
|
fs.mkdirSync(backupDir, { recursive: true });
|
|
27
47
|
}
|
|
@@ -0,0 +1,370 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Path Traversal 취약점 검사 스크립트
|
|
4
|
+
*
|
|
5
|
+
* PRD 0019: 보안 강화 (Phase 1) - Path Traversal 방지
|
|
6
|
+
*
|
|
7
|
+
* 사용법:
|
|
8
|
+
* tsx scripts/check-path-traversal.ts
|
|
9
|
+
* tsx scripts/check-path-traversal.ts --ci
|
|
10
|
+
* tsx scripts/check-path-traversal.ts --directory src/
|
|
11
|
+
*
|
|
12
|
+
* 목표:
|
|
13
|
+
* - 모든 파일 경로 처리 코드에서 경로 검증 적용 확인
|
|
14
|
+
* - Path Traversal 취약점 0개
|
|
15
|
+
* - CI/CD 통합 가능
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
/* eslint-disable security/detect-unsafe-regex */
|
|
19
|
+
// 정규식 패턴은 안전한 패턴임
|
|
20
|
+
|
|
21
|
+
import { readFileSync } from 'fs';
|
|
22
|
+
import { readdir } from 'fs/promises';
|
|
23
|
+
import { join, relative } from 'path';
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* CLI 옵션
|
|
27
|
+
*/
|
|
28
|
+
interface CliOptions {
|
|
29
|
+
ci?: boolean;
|
|
30
|
+
directory?: string;
|
|
31
|
+
exclude?: string[];
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Path Traversal 취약점 발견 위치
|
|
36
|
+
*/
|
|
37
|
+
interface PathTraversalLocation {
|
|
38
|
+
file: string;
|
|
39
|
+
line: number;
|
|
40
|
+
column: number;
|
|
41
|
+
pattern: string; // 발견된 패턴 종류
|
|
42
|
+
context: string; // 해당 라인 내용
|
|
43
|
+
severity: 'high' | 'medium' | 'low'; // 심각도
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* 검사 결과
|
|
48
|
+
*/
|
|
49
|
+
interface CheckResult {
|
|
50
|
+
total: number;
|
|
51
|
+
locations: PathTraversalLocation[];
|
|
52
|
+
byFile: Map<string, PathTraversalLocation[]>;
|
|
53
|
+
byPattern: Map<string, number>;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* 명령줄 인자 파싱
|
|
58
|
+
*/
|
|
59
|
+
function parseArgs(): CliOptions {
|
|
60
|
+
const args = process.argv.slice(2);
|
|
61
|
+
const options: CliOptions = {
|
|
62
|
+
exclude: ['**/node_modules/**', '**/dist/**', '**/*.d.ts', '**/*.spec.ts', '**/__tests__/**']
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
for (let i = 0; i < args.length; i++) {
|
|
66
|
+
const arg = args[i];
|
|
67
|
+
if (arg === '--ci') {
|
|
68
|
+
options.ci = true;
|
|
69
|
+
} else if (arg === '--directory' && args[i + 1]) {
|
|
70
|
+
options.directory = args[i + 1];
|
|
71
|
+
i++;
|
|
72
|
+
} else if (arg === '--exclude' && args[i + 1]) {
|
|
73
|
+
if (!options.exclude) {
|
|
74
|
+
options.exclude = [];
|
|
75
|
+
}
|
|
76
|
+
options.exclude.push(args[i + 1]);
|
|
77
|
+
i++;
|
|
78
|
+
} else if (arg === '--help' || arg === '-h') {
|
|
79
|
+
printHelp();
|
|
80
|
+
process.exit(0);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return options;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* 도움말 출력
|
|
89
|
+
*/
|
|
90
|
+
function printHelp(): void {
|
|
91
|
+
console.log(`
|
|
92
|
+
Path Traversal 취약점 검사 스크립트
|
|
93
|
+
|
|
94
|
+
사용법:
|
|
95
|
+
tsx scripts/check-path-traversal.ts [options]
|
|
96
|
+
|
|
97
|
+
옵션:
|
|
98
|
+
--ci CI 모드 (취약점 발견 시 exit code 1 반환)
|
|
99
|
+
--directory <path> 검사할 디렉토리 (기본값: src/)
|
|
100
|
+
--exclude <pattern> 제외할 파일 패턴 (여러 번 사용 가능)
|
|
101
|
+
--help, -h 도움말 출력
|
|
102
|
+
|
|
103
|
+
예제:
|
|
104
|
+
tsx scripts/check-path-traversal.ts
|
|
105
|
+
tsx scripts/check-path-traversal.ts --ci
|
|
106
|
+
tsx scripts/check-path-traversal.ts --directory src/infrastructure
|
|
107
|
+
`);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* 파일이 제외 패턴에 해당하는지 확인
|
|
112
|
+
*/
|
|
113
|
+
function shouldExclude(file: string, excludePatterns: string[]): boolean {
|
|
114
|
+
for (const pattern of excludePatterns) {
|
|
115
|
+
// 간단한 패턴 매칭 (glob 패턴은 복잡하므로 기본적인 것만 지원)
|
|
116
|
+
if (pattern.includes('**')) {
|
|
117
|
+
const regex = new RegExp(pattern.replace(/\*\*/g, '.*').replace(/\*/g, '[^/]*'));
|
|
118
|
+
if (regex.test(file)) {
|
|
119
|
+
return true;
|
|
120
|
+
}
|
|
121
|
+
} else if (file.includes(pattern)) {
|
|
122
|
+
return true;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
return false;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* 재귀적으로 디렉토리 탐색
|
|
130
|
+
*/
|
|
131
|
+
async function findFiles(
|
|
132
|
+
dir: string,
|
|
133
|
+
excludePatterns: string[],
|
|
134
|
+
fileList: string[] = []
|
|
135
|
+
): Promise<string[]> {
|
|
136
|
+
try {
|
|
137
|
+
const entries = await readdir(dir, { withFileTypes: true });
|
|
138
|
+
|
|
139
|
+
for (const entry of entries) {
|
|
140
|
+
const fullPath = join(dir, entry.name);
|
|
141
|
+
const relativePath = relative(process.cwd(), fullPath);
|
|
142
|
+
|
|
143
|
+
if (shouldExclude(relativePath, excludePatterns)) {
|
|
144
|
+
continue;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
if (entry.isDirectory()) {
|
|
148
|
+
await findFiles(fullPath, excludePatterns, fileList);
|
|
149
|
+
} else if (entry.isFile() && entry.name.endsWith('.ts')) {
|
|
150
|
+
fileList.push(fullPath);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
} catch (error) {
|
|
154
|
+
// 디렉토리 읽기 실패는 무시
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
return fileList;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* 파일 내용에서 Path Traversal 취약점 검색
|
|
162
|
+
*/
|
|
163
|
+
function checkFile(filePath: string): PathTraversalLocation[] {
|
|
164
|
+
const locations: PathTraversalLocation[] = [];
|
|
165
|
+
|
|
166
|
+
try {
|
|
167
|
+
const content = readFileSync(filePath, 'utf-8');
|
|
168
|
+
const lines = content.split('\n');
|
|
169
|
+
const relativePath = relative(process.cwd(), filePath);
|
|
170
|
+
|
|
171
|
+
// 파일 경로를 다루는 코드 패턴 검색
|
|
172
|
+
const pathPatterns = [
|
|
173
|
+
{
|
|
174
|
+
pattern: /(readFile|writeFile|appendFile|createReadStream|createWriteStream|unlink|rmdir|mkdir|access|stat|readdir)\s*\([^)]*['"`]([^'"`]*(?:\.\.\/|\.\.\\\\)[^'"`]*)['"`]/g,
|
|
175
|
+
type: 'file-operation-with-traversal',
|
|
176
|
+
severity: 'high' as const
|
|
177
|
+
},
|
|
178
|
+
{
|
|
179
|
+
pattern: /path\.(join|resolve)\s*\([^)]*['"`]([^'"`]*(?:\.\.\/|\.\.\\\\)[^'"`]*)['"`]/g,
|
|
180
|
+
type: 'path-join-with-traversal',
|
|
181
|
+
severity: 'high' as const
|
|
182
|
+
},
|
|
183
|
+
{
|
|
184
|
+
pattern: /fs\.(readFile|writeFile|appendFile|createReadStream|createWriteStream|unlink|rmdir|mkdir|access|stat|readdir)Sync\s*\([^)]*['"`]([^'"`]*(?:\.\.\/|\.\.\\\\)[^'"`]*)['"`]/g,
|
|
185
|
+
type: 'fs-sync-with-traversal',
|
|
186
|
+
severity: 'high' as const
|
|
187
|
+
}
|
|
188
|
+
];
|
|
189
|
+
|
|
190
|
+
// 각 패턴 검색
|
|
191
|
+
for (const pathPattern of pathPatterns) {
|
|
192
|
+
let match;
|
|
193
|
+
while ((match = pathPattern.pattern.exec(content)) !== null) {
|
|
194
|
+
const lineNumber = content.substring(0, match.index).split('\n').length;
|
|
195
|
+
const line = lines[lineNumber - 1];
|
|
196
|
+
|
|
197
|
+
// validateFilePath 또는 sanitizeFileName 사용 여부 확인
|
|
198
|
+
const beforeContext = content.substring(Math.max(0, match.index - 500), match.index);
|
|
199
|
+
const afterContext = content.substring(match.index, Math.min(content.length, match.index + 500));
|
|
200
|
+
|
|
201
|
+
// 경로 검증이 적용되지 않은 경우
|
|
202
|
+
if (!beforeContext.includes('validateFilePath') &&
|
|
203
|
+
!beforeContext.includes('sanitizeFileName') &&
|
|
204
|
+
!afterContext.includes('validateFilePath') &&
|
|
205
|
+
!afterContext.includes('sanitizeFileName')) {
|
|
206
|
+
// path-validator.ts 파일 자체는 제외
|
|
207
|
+
if (!relativePath.includes('path-validator.ts')) {
|
|
208
|
+
locations.push({
|
|
209
|
+
file: relativePath,
|
|
210
|
+
line: lineNumber,
|
|
211
|
+
column: match.index - content.substring(0, match.index).lastIndexOf('\n') - 1,
|
|
212
|
+
pattern: pathPattern.type,
|
|
213
|
+
context: line.trim(),
|
|
214
|
+
severity: pathPattern.severity
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// 사용자 입력을 받는 파일 경로 처리 코드 검색
|
|
222
|
+
const userInputPatterns = [
|
|
223
|
+
{
|
|
224
|
+
pattern: /(readFile|writeFile|appendFile|createReadStream|createWriteStream|unlink|rmdir|mkdir|access|stat|readdir)\s*\([^)]*(\w+)\s*[,)]/g,
|
|
225
|
+
type: 'file-operation-with-user-input',
|
|
226
|
+
severity: 'medium' as const
|
|
227
|
+
},
|
|
228
|
+
{
|
|
229
|
+
pattern: /path\.(join|resolve)\s*\([^)]*(\w+)\s*[,)]/g,
|
|
230
|
+
type: 'path-join-with-user-input',
|
|
231
|
+
severity: 'medium' as const
|
|
232
|
+
}
|
|
233
|
+
];
|
|
234
|
+
|
|
235
|
+
// 사용자 입력 패턴 검색 (함수 파라미터로 받는 경우)
|
|
236
|
+
for (const userInputPattern of userInputPatterns) {
|
|
237
|
+
let match;
|
|
238
|
+
while ((match = userInputPattern.pattern.exec(content)) !== null) {
|
|
239
|
+
const lineNumber = content.substring(0, match.index).split('\n').length;
|
|
240
|
+
const line = lines[lineNumber - 1];
|
|
241
|
+
|
|
242
|
+
// 함수 파라미터인지 확인 (함수 시그니처에서 파라미터로 받는 경우)
|
|
243
|
+
const beforeContext = content.substring(Math.max(0, match.index - 1000), match.index);
|
|
244
|
+
const functionMatch = beforeContext.match(/(?:function|const|let|var)\s+\w+\s*\([^)]*(\w+)\s*[,)]/);
|
|
245
|
+
|
|
246
|
+
if (functionMatch) {
|
|
247
|
+
const paramName = functionMatch[1];
|
|
248
|
+
// 해당 파라미터가 경로 검증 없이 사용되는지 확인
|
|
249
|
+
const paramUsagePattern = new RegExp(`\\b${paramName}\\b[^,)]*[,)]`, 'g');
|
|
250
|
+
const paramUsageMatch = paramUsagePattern.exec(line);
|
|
251
|
+
|
|
252
|
+
if (paramUsageMatch) {
|
|
253
|
+
// validateFilePath 또는 sanitizeFileName 사용 여부 확인
|
|
254
|
+
if (!beforeContext.includes('validateFilePath') &&
|
|
255
|
+
!beforeContext.includes('sanitizeFileName') &&
|
|
256
|
+
!line.includes('validateFilePath') &&
|
|
257
|
+
!line.includes('sanitizeFileName')) {
|
|
258
|
+
// path-validator.ts 파일 자체는 제외
|
|
259
|
+
if (!relativePath.includes('path-validator.ts')) {
|
|
260
|
+
locations.push({
|
|
261
|
+
file: relativePath,
|
|
262
|
+
line: lineNumber,
|
|
263
|
+
column: match.index - content.substring(0, match.index).lastIndexOf('\n') - 1,
|
|
264
|
+
pattern: userInputPattern.type,
|
|
265
|
+
context: line.trim(),
|
|
266
|
+
severity: userInputPattern.severity
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
} catch (error) {
|
|
276
|
+
// 파일 읽기 실패는 무시
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
return locations;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
/**
|
|
283
|
+
* 모든 파일 검사
|
|
284
|
+
*/
|
|
285
|
+
async function checkAllFiles(options: CliOptions): Promise<CheckResult> {
|
|
286
|
+
const directory = options.directory || 'src';
|
|
287
|
+
const excludePatterns = options.exclude || [];
|
|
288
|
+
|
|
289
|
+
const files = await findFiles(directory, excludePatterns);
|
|
290
|
+
const locations: PathTraversalLocation[] = [];
|
|
291
|
+
const byFile = new Map<string, PathTraversalLocation[]>();
|
|
292
|
+
const byPattern = new Map<string, number>();
|
|
293
|
+
|
|
294
|
+
for (const file of files) {
|
|
295
|
+
const fileLocations = checkFile(file);
|
|
296
|
+
if (fileLocations.length > 0) {
|
|
297
|
+
const relativePath = relative(process.cwd(), file);
|
|
298
|
+
locations.push(...fileLocations);
|
|
299
|
+
byFile.set(relativePath, fileLocations);
|
|
300
|
+
|
|
301
|
+
for (const loc of fileLocations) {
|
|
302
|
+
byPattern.set(loc.pattern, (byPattern.get(loc.pattern) || 0) + 1);
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
return {
|
|
308
|
+
total: locations.length,
|
|
309
|
+
locations,
|
|
310
|
+
byFile,
|
|
311
|
+
byPattern
|
|
312
|
+
};
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
/**
|
|
316
|
+
* 결과 출력
|
|
317
|
+
*/
|
|
318
|
+
function printResults(result: CheckResult): void {
|
|
319
|
+
console.log('\n⚠️ 발견된 Path Traversal 취약점:', result.total, '개');
|
|
320
|
+
|
|
321
|
+
if (result.total === 0) {
|
|
322
|
+
console.log('✅ 모든 파일 경로 처리 코드에서 경로 검증이 적용되어 있습니다.');
|
|
323
|
+
return;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
console.log('📁 파일별 취약점 목록:');
|
|
327
|
+
|
|
328
|
+
for (const [file, locations] of result.byFile.entries()) {
|
|
329
|
+
console.log(`\n ${file} (${locations.length}개):`);
|
|
330
|
+
|
|
331
|
+
for (const loc of locations) {
|
|
332
|
+
const severityIcon = loc.severity === 'high' ? '🔴' : loc.severity === 'medium' ? '🟡' : '🟢';
|
|
333
|
+
console.log(` ${severityIcon} 라인 ${loc.line}:${loc.column} - ${loc.pattern}`);
|
|
334
|
+
console.log(` ${loc.context}`);
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
console.log('\n📊 패턴별 통계:');
|
|
339
|
+
for (const [pattern, count] of result.byPattern.entries()) {
|
|
340
|
+
console.log(` ${pattern}: ${count}개`);
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
/**
|
|
345
|
+
* 메인 함수
|
|
346
|
+
*/
|
|
347
|
+
async function main(): Promise<void> {
|
|
348
|
+
const options = parseArgs();
|
|
349
|
+
|
|
350
|
+
console.log('🔍 Path Traversal 취약점 검사 시작...\n');
|
|
351
|
+
|
|
352
|
+
const result = await checkAllFiles(options);
|
|
353
|
+
printResults(result);
|
|
354
|
+
|
|
355
|
+
if (options.ci && result.total > 0) {
|
|
356
|
+
console.error('\n❌ CI 실패: Path Traversal 취약점이 발견되었습니다.');
|
|
357
|
+
process.exit(1);
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
if (result.total === 0) {
|
|
361
|
+
console.log('\n✅ 검사 완료: 모든 파일 경로 처리 코드에서 경로 검증이 적용되어 있습니다.');
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
// 스크립트 실행
|
|
366
|
+
main().catch(error => {
|
|
367
|
+
console.error('❌ 스크립트 실행 실패:', error);
|
|
368
|
+
process.exit(1);
|
|
369
|
+
});
|
|
370
|
+
|