sumulige-claude 1.3.0 → 1.3.2
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/.claude/MEMORY.md +9 -0
- package/.claude/commands/audit.md +147 -0
- package/.claude/commands/gha.md +136 -0
- package/.claude/commands/handoff.md +93 -0
- package/.claude/handoffs/INDEX.md +21 -0
- package/.claude/handoffs/LATEST.md +76 -0
- package/.claude/handoffs/handoff_2026-01-22T13-07-04-757Z.md +76 -0
- package/.claude/hooks/auto-handoff.cjs +353 -0
- package/.claude/hooks/memory-loader.cjs +208 -0
- package/.claude/hooks/memory-saver.cjs +268 -0
- package/.claude/rag/skill-index.json +15 -34
- package/.claude/sessions/session_2026-01-22T13-07-26-625Z.json +23 -0
- package/.claude/settings.json +40 -0
- package/.claude/settings.local.json +10 -1
- package/.claude/skills/api-tester/SKILL.md +61 -0
- package/.claude/skills/api-tester/examples/basic.md +3 -0
- package/.claude/skills/api-tester/metadata.yaml +30 -0
- package/.claude/skills/api-tester/templates/default.md +3 -0
- package/.claude/skills/code-reviewer-123/SKILL.md +61 -0
- package/.claude/skills/code-reviewer-123/examples/basic.md +3 -0
- package/.claude/skills/code-reviewer-123/metadata.yaml +30 -0
- package/.claude/skills/code-reviewer-123/templates/default.md +3 -0
- package/.claude/skills/my-skill/SKILL.md +61 -0
- package/.claude/skills/my-skill/examples/basic.md +3 -0
- package/.claude/skills/my-skill/metadata.yaml +30 -0
- package/.claude/skills/my-skill/templates/default.md +3 -0
- package/.claude/skills/template/SKILL.md +6 -0
- package/.claude/skills/template/metadata.yaml +30 -0
- package/.claude/skills/test-skill-name/SKILL.md +61 -0
- package/.claude/skills/test-skill-name/examples/basic.md +3 -0
- package/.claude/skills/test-skill-name/metadata.yaml +30 -0
- package/.claude/skills/test-skill-name/templates/default.md +3 -0
- package/CHANGELOG.md +45 -0
- package/cli.js +4 -0
- package/development/todos/.state.json +3 -1
- package/lib/commands.js +51 -0
- package/lib/permission-audit.js +255 -0
- package/package.json +1 -1
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# My Skill
|
|
2
|
+
|
|
3
|
+
> 简短描述这个技能的作用(一句话)
|
|
4
|
+
|
|
5
|
+
**版本**: 1.0.0
|
|
6
|
+
**作者**: @username
|
|
7
|
+
**标签**: [category1, category2]
|
|
8
|
+
**难度**: 初级/中级/高级
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## 概述
|
|
13
|
+
|
|
14
|
+
详细描述这个技能的功能和用途。
|
|
15
|
+
|
|
16
|
+
## 适用场景
|
|
17
|
+
|
|
18
|
+
- 场景 1
|
|
19
|
+
- 场景 2
|
|
20
|
+
- 场景 3
|
|
21
|
+
|
|
22
|
+
## 触发关键词
|
|
23
|
+
|
|
24
|
+
```
|
|
25
|
+
keyword1, keyword2, "exact phrase"
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## 使用方法
|
|
29
|
+
|
|
30
|
+
### 基础用法
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
# 示例命令
|
|
34
|
+
your-command-here
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
### 高级用法
|
|
38
|
+
|
|
39
|
+
```yaml
|
|
40
|
+
# 配置示例
|
|
41
|
+
key: value
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## 输出格式
|
|
45
|
+
|
|
46
|
+
描述这个技能的输出结果格式。
|
|
47
|
+
|
|
48
|
+
## 注意事项
|
|
49
|
+
|
|
50
|
+
- 注意事项 1
|
|
51
|
+
- 注意事项 2
|
|
52
|
+
|
|
53
|
+
## 相关技能
|
|
54
|
+
|
|
55
|
+
- [related-skill](../related-skill/)
|
|
56
|
+
- [another-skill](../another-skill/)
|
|
57
|
+
|
|
58
|
+
## 更新日志
|
|
59
|
+
|
|
60
|
+
### 1.0.0 (YYYY-MM-DD)
|
|
61
|
+
- 初始版本
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# Skill Metadata
|
|
2
|
+
# 这个文件定义技能的基本信息,用于自动发现和索引
|
|
3
|
+
|
|
4
|
+
name: my-skill
|
|
5
|
+
version: 1.0.0
|
|
6
|
+
author: @username
|
|
7
|
+
description: 简短描述技能功能
|
|
8
|
+
|
|
9
|
+
tags:
|
|
10
|
+
- category1
|
|
11
|
+
- category2
|
|
12
|
+
|
|
13
|
+
triggers:
|
|
14
|
+
- keyword1
|
|
15
|
+
- keyword2
|
|
16
|
+
- "exact phrase"
|
|
17
|
+
|
|
18
|
+
dependencies: [] # 依赖的其他技能
|
|
19
|
+
|
|
20
|
+
difficulty: beginner # beginner | intermediate | advanced
|
|
21
|
+
|
|
22
|
+
# 模板文件
|
|
23
|
+
templates:
|
|
24
|
+
- name: default
|
|
25
|
+
file: templates/default.md
|
|
26
|
+
|
|
27
|
+
# 示例文件
|
|
28
|
+
examples:
|
|
29
|
+
- name: basic
|
|
30
|
+
file: examples/basic.md
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# Skill Metadata
|
|
2
|
+
# 这个文件定义技能的基本信息,用于自动发现和索引
|
|
3
|
+
|
|
4
|
+
name: skill-name
|
|
5
|
+
version: 1.0.0
|
|
6
|
+
author: @username
|
|
7
|
+
description: 简短描述技能功能
|
|
8
|
+
|
|
9
|
+
tags:
|
|
10
|
+
- category1
|
|
11
|
+
- category2
|
|
12
|
+
|
|
13
|
+
triggers:
|
|
14
|
+
- keyword1
|
|
15
|
+
- keyword2
|
|
16
|
+
- "exact phrase"
|
|
17
|
+
|
|
18
|
+
dependencies: [] # 依赖的其他技能
|
|
19
|
+
|
|
20
|
+
difficulty: beginner # beginner | intermediate | advanced
|
|
21
|
+
|
|
22
|
+
# 模板文件
|
|
23
|
+
templates:
|
|
24
|
+
- name: default
|
|
25
|
+
file: templates/default.md
|
|
26
|
+
|
|
27
|
+
# 示例文件
|
|
28
|
+
examples:
|
|
29
|
+
- name: basic
|
|
30
|
+
file: examples/basic.md
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# Test Skill Name
|
|
2
|
+
|
|
3
|
+
> 简短描述这个技能的作用(一句话)
|
|
4
|
+
|
|
5
|
+
**版本**: 1.0.0
|
|
6
|
+
**作者**: @username
|
|
7
|
+
**标签**: [category1, category2]
|
|
8
|
+
**难度**: 初级/中级/高级
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## 概述
|
|
13
|
+
|
|
14
|
+
详细描述这个技能的功能和用途。
|
|
15
|
+
|
|
16
|
+
## 适用场景
|
|
17
|
+
|
|
18
|
+
- 场景 1
|
|
19
|
+
- 场景 2
|
|
20
|
+
- 场景 3
|
|
21
|
+
|
|
22
|
+
## 触发关键词
|
|
23
|
+
|
|
24
|
+
```
|
|
25
|
+
keyword1, keyword2, "exact phrase"
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## 使用方法
|
|
29
|
+
|
|
30
|
+
### 基础用法
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
# 示例命令
|
|
34
|
+
your-command-here
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
### 高级用法
|
|
38
|
+
|
|
39
|
+
```yaml
|
|
40
|
+
# 配置示例
|
|
41
|
+
key: value
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## 输出格式
|
|
45
|
+
|
|
46
|
+
描述这个技能的输出结果格式。
|
|
47
|
+
|
|
48
|
+
## 注意事项
|
|
49
|
+
|
|
50
|
+
- 注意事项 1
|
|
51
|
+
- 注意事项 2
|
|
52
|
+
|
|
53
|
+
## 相关技能
|
|
54
|
+
|
|
55
|
+
- [related-skill](../related-skill/)
|
|
56
|
+
- [another-skill](../another-skill/)
|
|
57
|
+
|
|
58
|
+
## 更新日志
|
|
59
|
+
|
|
60
|
+
### 1.0.0 (YYYY-MM-DD)
|
|
61
|
+
- 初始版本
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# Skill Metadata
|
|
2
|
+
# 这个文件定义技能的基本信息,用于自动发现和索引
|
|
3
|
+
|
|
4
|
+
name: test-skill-name
|
|
5
|
+
version: 1.0.0
|
|
6
|
+
author: @username
|
|
7
|
+
description: 简短描述技能功能
|
|
8
|
+
|
|
9
|
+
tags:
|
|
10
|
+
- category1
|
|
11
|
+
- category2
|
|
12
|
+
|
|
13
|
+
triggers:
|
|
14
|
+
- keyword1
|
|
15
|
+
- keyword2
|
|
16
|
+
- "exact phrase"
|
|
17
|
+
|
|
18
|
+
dependencies: [] # 依赖的其他技能
|
|
19
|
+
|
|
20
|
+
difficulty: beginner # beginner | intermediate | advanced
|
|
21
|
+
|
|
22
|
+
# 模板文件
|
|
23
|
+
templates:
|
|
24
|
+
- name: default
|
|
25
|
+
file: templates/default.md
|
|
26
|
+
|
|
27
|
+
# 示例文件
|
|
28
|
+
examples:
|
|
29
|
+
- name: basic
|
|
30
|
+
file: examples/basic.md
|
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,48 @@
|
|
|
1
|
+
## [1.3.2](https://github.com/sumulige/sumulige-claude/compare/v1.3.1...v1.3.2) (2026-01-22)
|
|
2
|
+
|
|
3
|
+
### ✨ New Features
|
|
4
|
+
|
|
5
|
+
- **Official Hooks Integration**: Claude Code lifecycle auto-sync
|
|
6
|
+
- `SessionStart` → `memory-loader.cjs`: Auto-load MEMORY.md, ANCHORS.md, restore TODO state
|
|
7
|
+
- `SessionEnd` → `memory-saver.cjs`: Auto-save session summary, archive session, sync TODO
|
|
8
|
+
- `PreCompact` → `auto-handoff.cjs`: Auto-generate handoff before context compression
|
|
9
|
+
- **Context Preservation**: Automatic handoff documents in `.claude/handoffs/`
|
|
10
|
+
- Includes active TODOs, recently modified files, recovery commands
|
|
11
|
+
- `LATEST.md` always points to most recent handoff
|
|
12
|
+
|
|
13
|
+
### 🔧 Improvements
|
|
14
|
+
|
|
15
|
+
- Session state tracking via `.session-state.json`
|
|
16
|
+
- Automatic session archiving to `.claude/sessions/`
|
|
17
|
+
- Memory entries kept for 7 days with auto-cleanup
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## [1.3.1](https://github.com/sumulige/sumulige-claude/compare/v1.3.0...v1.3.1) (2026-01-22)
|
|
22
|
+
|
|
23
|
+
### ✨ New Features
|
|
24
|
+
|
|
25
|
+
- **System Prompt Optimization**: Token savings via MCP lazy-loading
|
|
26
|
+
- `ENABLE_TOOL_SEARCH=true` - MCP tools loaded on demand
|
|
27
|
+
- `DISABLE_AUTOUPDATER=1` - Reduce startup overhead
|
|
28
|
+
- ~50% token reduction for system prompts
|
|
29
|
+
- **New Commands**:
|
|
30
|
+
- `/handoff` - Generate context handoff documents for session continuity
|
|
31
|
+
- `/gha` - Analyze GitHub Actions CI failures
|
|
32
|
+
- `/audit` - Security audit for approved commands (cc-safe style)
|
|
33
|
+
- **Permission Audit**: Detect dangerous patterns in approved commands
|
|
34
|
+
- Critical: `rm -rf /`, disk overwrite, fork bombs
|
|
35
|
+
- High: `sudo`, `chmod 777`, privileged containers
|
|
36
|
+
- Medium: global installs, force push
|
|
37
|
+
|
|
38
|
+
### 📝 Documentation
|
|
39
|
+
|
|
40
|
+
- Add `/handoff` command guide
|
|
41
|
+
- Add `/gha` CI analysis guide
|
|
42
|
+
- Add `/audit` security audit guide
|
|
43
|
+
|
|
44
|
+
---
|
|
45
|
+
|
|
1
46
|
## [1.3.0](https://github.com/sumulige/sumulige-claude/compare/v1.2.1...v1.3.0) (2026-01-22)
|
|
2
47
|
|
|
3
48
|
### ✨ New Features
|
package/cli.js
CHANGED
|
@@ -175,6 +175,10 @@ const COMMANDS = {
|
|
|
175
175
|
notebooklm: {
|
|
176
176
|
help: 'NotebookLM browser automation',
|
|
177
177
|
args: '<auth|ask|status|clear> [args...]'
|
|
178
|
+
},
|
|
179
|
+
audit: {
|
|
180
|
+
help: 'Audit approved commands for security risks',
|
|
181
|
+
args: '[--global] [--ci] [--report]'
|
|
178
182
|
}
|
|
179
183
|
};
|
|
180
184
|
|
package/lib/commands.js
CHANGED
|
@@ -3332,6 +3332,57 @@ All notable changes to this project will be documented in this file.
|
|
|
3332
3332
|
} = require("../.claude/workflow/notebooklm/browser");
|
|
3333
3333
|
await handleNotebookLMCommand(args);
|
|
3334
3334
|
},
|
|
3335
|
+
|
|
3336
|
+
// ==========================================================================
|
|
3337
|
+
// Security Audit Commands
|
|
3338
|
+
// ==========================================================================
|
|
3339
|
+
|
|
3340
|
+
audit: async (...args) => {
|
|
3341
|
+
const { audit, generateReport, passes } = require("./permission-audit");
|
|
3342
|
+
|
|
3343
|
+
const isGlobal = args.includes("--global");
|
|
3344
|
+
const isCi = args.includes("--ci");
|
|
3345
|
+
const isReport = args.includes("--report");
|
|
3346
|
+
|
|
3347
|
+
console.log("🔍 Running permission audit...\n");
|
|
3348
|
+
|
|
3349
|
+
const results = audit({ global: isGlobal });
|
|
3350
|
+
|
|
3351
|
+
if (isReport) {
|
|
3352
|
+
console.log(generateReport(results));
|
|
3353
|
+
} else {
|
|
3354
|
+
const { issues } = results;
|
|
3355
|
+
const total = issues.critical.length + issues.high.length + issues.medium.length;
|
|
3356
|
+
|
|
3357
|
+
if (total === 0) {
|
|
3358
|
+
console.log("✅ No security issues found!\n");
|
|
3359
|
+
console.log(`Scanned ${results.scanned} settings file(s)`);
|
|
3360
|
+
} else {
|
|
3361
|
+
console.log(`Found ${total} potential issue(s):\n`);
|
|
3362
|
+
|
|
3363
|
+
if (issues.critical.length > 0) {
|
|
3364
|
+
console.log(`🔴 Critical: ${issues.critical.length}`);
|
|
3365
|
+
issues.critical.forEach(i => console.log(` - ${i.desc}: ${i.permission}`));
|
|
3366
|
+
}
|
|
3367
|
+
|
|
3368
|
+
if (issues.high.length > 0) {
|
|
3369
|
+
console.log(`🟠 High: ${issues.high.length}`);
|
|
3370
|
+
issues.high.forEach(i => console.log(` - ${i.desc}: ${i.permission}`));
|
|
3371
|
+
}
|
|
3372
|
+
|
|
3373
|
+
if (issues.medium.length > 0) {
|
|
3374
|
+
console.log(`🟡 Medium: ${issues.medium.length}`);
|
|
3375
|
+
issues.medium.forEach(i => console.log(` - ${i.desc}: ${i.permission}`));
|
|
3376
|
+
}
|
|
3377
|
+
|
|
3378
|
+
console.log("\nRun 'smc audit --report' for detailed analysis.");
|
|
3379
|
+
}
|
|
3380
|
+
}
|
|
3381
|
+
|
|
3382
|
+
if (isCi && !passes(results)) {
|
|
3383
|
+
process.exit(1);
|
|
3384
|
+
}
|
|
3385
|
+
},
|
|
3335
3386
|
};
|
|
3336
3387
|
|
|
3337
3388
|
// ============================================================================
|
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Permission Audit - Security scanner for approved commands
|
|
3
|
+
* Inspired by cc-safe from ykdojo/claude-code-tips
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const fs = require('fs');
|
|
7
|
+
const path = require('path');
|
|
8
|
+
const os = require('os');
|
|
9
|
+
|
|
10
|
+
// Dangerous patterns with severity levels
|
|
11
|
+
const DANGEROUS_PATTERNS = [
|
|
12
|
+
// Critical - Must remove
|
|
13
|
+
{ pattern: /rm\s+-rf\s+\/(?!\w)/, level: 'critical', desc: 'Delete root directory' },
|
|
14
|
+
{ pattern: />\s*\/dev\/sd[a-z]/, level: 'critical', desc: 'Overwrite disk device' },
|
|
15
|
+
{ pattern: /mkfs/, level: 'critical', desc: 'Format disk' },
|
|
16
|
+
{ pattern: /dd\s+if=\/dev\/zero/, level: 'critical', desc: 'Overwrite with zeros' },
|
|
17
|
+
{ pattern: /:\(\)\s*\{\s*:\|:&\s*\}\s*;:/, level: 'critical', desc: 'Fork bomb' },
|
|
18
|
+
|
|
19
|
+
// High - Should review
|
|
20
|
+
{ pattern: /sudo/, level: 'high', desc: 'Privilege escalation' },
|
|
21
|
+
{ pattern: /rm\s+-rf/, level: 'high', desc: 'Recursive force delete' },
|
|
22
|
+
{ pattern: /chmod\s+777/, level: 'high', desc: 'World-writable permissions' },
|
|
23
|
+
{ pattern: /--privileged/, level: 'high', desc: 'Privileged container' },
|
|
24
|
+
{ pattern: /curl.*\|\s*(sh|bash)/, level: 'high', desc: 'Remote script execution' },
|
|
25
|
+
{ pattern: /wget.*\|\s*(sh|bash)/, level: 'high', desc: 'Remote script execution' },
|
|
26
|
+
{ pattern: /eval\s/, level: 'high', desc: 'Dynamic code execution' },
|
|
27
|
+
{ pattern: /--no-verify/, level: 'high', desc: 'Skip verification hooks' },
|
|
28
|
+
|
|
29
|
+
// Medium - Optional review
|
|
30
|
+
{ pattern: /npm\s+install\s+-g/, level: 'medium', desc: 'Global npm install' },
|
|
31
|
+
{ pattern: /pip\s+install/, level: 'medium', desc: 'Python package install' },
|
|
32
|
+
{ pattern: /git\s+push\s+--force/, level: 'medium', desc: 'Force push' },
|
|
33
|
+
{ pattern: /git\s+reset\s+--hard/, level: 'medium', desc: 'Hard reset' },
|
|
34
|
+
{ pattern: /DROP\s+(TABLE|DATABASE)/i, level: 'medium', desc: 'Drop database objects' },
|
|
35
|
+
{ pattern: /TRUNCATE/i, level: 'medium', desc: 'Truncate table' },
|
|
36
|
+
];
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Find all settings files to scan
|
|
40
|
+
*/
|
|
41
|
+
function findSettingsFiles(projectDir) {
|
|
42
|
+
const files = [];
|
|
43
|
+
const homeDir = os.homedir();
|
|
44
|
+
|
|
45
|
+
// Global settings
|
|
46
|
+
const globalSettings = path.join(homeDir, '.claude', 'settings.local.json');
|
|
47
|
+
if (fs.existsSync(globalSettings)) {
|
|
48
|
+
files.push({ path: globalSettings, scope: 'global' });
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Project settings
|
|
52
|
+
if (projectDir) {
|
|
53
|
+
const projectSettings = path.join(projectDir, '.claude', 'settings.local.json');
|
|
54
|
+
if (fs.existsSync(projectSettings)) {
|
|
55
|
+
files.push({ path: projectSettings, scope: 'project' });
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// All project settings in ~/.claude/projects/
|
|
60
|
+
const projectsDir = path.join(homeDir, '.claude', 'projects');
|
|
61
|
+
if (fs.existsSync(projectsDir)) {
|
|
62
|
+
try {
|
|
63
|
+
const projects = fs.readdirSync(projectsDir);
|
|
64
|
+
for (const proj of projects) {
|
|
65
|
+
const projSettings = path.join(projectsDir, proj, 'settings.local.json');
|
|
66
|
+
if (fs.existsSync(projSettings)) {
|
|
67
|
+
files.push({ path: projSettings, scope: `project:${proj}` });
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
} catch (e) {
|
|
71
|
+
// Ignore read errors
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return files;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Extract permissions from settings file
|
|
80
|
+
*/
|
|
81
|
+
function extractPermissions(settingsPath) {
|
|
82
|
+
try {
|
|
83
|
+
const content = fs.readFileSync(settingsPath, 'utf-8');
|
|
84
|
+
const settings = JSON.parse(content);
|
|
85
|
+
|
|
86
|
+
const permissions = [];
|
|
87
|
+
|
|
88
|
+
// Check permissions.allow array
|
|
89
|
+
if (settings.permissions?.allow) {
|
|
90
|
+
permissions.push(...settings.permissions.allow);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Check allowedTools (older format)
|
|
94
|
+
if (settings.allowedTools) {
|
|
95
|
+
permissions.push(...settings.allowedTools);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return permissions;
|
|
99
|
+
} catch (e) {
|
|
100
|
+
return [];
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Scan a permission string for dangerous patterns
|
|
106
|
+
*/
|
|
107
|
+
function scanPermission(permission) {
|
|
108
|
+
const issues = [];
|
|
109
|
+
|
|
110
|
+
for (const { pattern, level, desc } of DANGEROUS_PATTERNS) {
|
|
111
|
+
if (pattern.test(permission)) {
|
|
112
|
+
issues.push({ level, desc, pattern: pattern.toString(), match: permission });
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
return issues;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Run full audit
|
|
121
|
+
*/
|
|
122
|
+
function audit(options = {}) {
|
|
123
|
+
const { projectDir = process.cwd(), global: scanGlobal = true } = options;
|
|
124
|
+
|
|
125
|
+
const results = {
|
|
126
|
+
scanned: 0,
|
|
127
|
+
issues: {
|
|
128
|
+
critical: [],
|
|
129
|
+
high: [],
|
|
130
|
+
medium: [],
|
|
131
|
+
},
|
|
132
|
+
files: [],
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
const files = findSettingsFiles(scanGlobal ? projectDir : null);
|
|
136
|
+
results.scanned = files.length;
|
|
137
|
+
results.files = files.map(f => f.path);
|
|
138
|
+
|
|
139
|
+
for (const { path: filePath, scope } of files) {
|
|
140
|
+
const permissions = extractPermissions(filePath);
|
|
141
|
+
|
|
142
|
+
for (const perm of permissions) {
|
|
143
|
+
const issues = scanPermission(perm);
|
|
144
|
+
|
|
145
|
+
for (const issue of issues) {
|
|
146
|
+
const entry = {
|
|
147
|
+
...issue,
|
|
148
|
+
file: filePath,
|
|
149
|
+
scope,
|
|
150
|
+
permission: perm,
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
results.issues[issue.level].push(entry);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
return results;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Generate markdown report
|
|
163
|
+
*/
|
|
164
|
+
function generateReport(results) {
|
|
165
|
+
const { scanned, issues, files } = results;
|
|
166
|
+
const totalIssues = issues.critical.length + issues.high.length + issues.medium.length;
|
|
167
|
+
|
|
168
|
+
let report = `# Permission Audit Report
|
|
169
|
+
|
|
170
|
+
**Date**: ${new Date().toISOString().split('T')[0]}
|
|
171
|
+
**Scanned**: ${scanned} files
|
|
172
|
+
**Issues**: ${totalIssues} found
|
|
173
|
+
|
|
174
|
+
`;
|
|
175
|
+
|
|
176
|
+
// Critical issues
|
|
177
|
+
report += `## 🔴 Critical Issues (${issues.critical.length})\n\n`;
|
|
178
|
+
if (issues.critical.length === 0) {
|
|
179
|
+
report += 'None found.\n\n';
|
|
180
|
+
} else {
|
|
181
|
+
issues.critical.forEach((issue, i) => {
|
|
182
|
+
report += `### ${i + 1}. ${issue.desc}
|
|
183
|
+
**Location**: ${issue.file}
|
|
184
|
+
**Pattern**: \`${issue.permission}\`
|
|
185
|
+
**Risk**: ${issue.desc}
|
|
186
|
+
|
|
187
|
+
`;
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// High risk
|
|
192
|
+
report += `## 🟠 High Risk (${issues.high.length})\n\n`;
|
|
193
|
+
if (issues.high.length === 0) {
|
|
194
|
+
report += 'None found.\n\n';
|
|
195
|
+
} else {
|
|
196
|
+
issues.high.forEach((issue, i) => {
|
|
197
|
+
report += `### ${i + 1}. ${issue.desc}
|
|
198
|
+
**Location**: ${issue.file}
|
|
199
|
+
**Pattern**: \`${issue.permission}\`
|
|
200
|
+
**Risk**: ${issue.desc}
|
|
201
|
+
|
|
202
|
+
`;
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// Medium risk
|
|
207
|
+
report += `## 🟡 Medium Risk (${issues.medium.length})\n\n`;
|
|
208
|
+
if (issues.medium.length === 0) {
|
|
209
|
+
report += 'None found.\n\n';
|
|
210
|
+
} else {
|
|
211
|
+
issues.medium.forEach((issue, i) => {
|
|
212
|
+
report += `### ${i + 1}. ${issue.desc}
|
|
213
|
+
**Location**: ${issue.file}
|
|
214
|
+
**Pattern**: \`${issue.permission}\`
|
|
215
|
+
|
|
216
|
+
`;
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// Summary
|
|
221
|
+
report += `## Summary
|
|
222
|
+
|
|
223
|
+
| Level | Count | Action |
|
|
224
|
+
|-------|-------|--------|
|
|
225
|
+
| 🔴 Critical | ${issues.critical.length} | Must remove |
|
|
226
|
+
| 🟠 High | ${issues.high.length} | Should review |
|
|
227
|
+
| 🟡 Medium | ${issues.medium.length} | Optional review |
|
|
228
|
+
|
|
229
|
+
`;
|
|
230
|
+
|
|
231
|
+
return report;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Check if audit passes (for CI)
|
|
236
|
+
*/
|
|
237
|
+
function passes(results, options = {}) {
|
|
238
|
+
const { allowMedium = true, allowHigh = false } = options;
|
|
239
|
+
|
|
240
|
+
if (results.issues.critical.length > 0) return false;
|
|
241
|
+
if (!allowHigh && results.issues.high.length > 0) return false;
|
|
242
|
+
if (!allowMedium && results.issues.medium.length > 0) return false;
|
|
243
|
+
|
|
244
|
+
return true;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
module.exports = {
|
|
248
|
+
audit,
|
|
249
|
+
generateReport,
|
|
250
|
+
passes,
|
|
251
|
+
DANGEROUS_PATTERNS,
|
|
252
|
+
findSettingsFiles,
|
|
253
|
+
extractPermissions,
|
|
254
|
+
scanPermission,
|
|
255
|
+
};
|