@sunchao116/mcp-audit 1.0.0

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.
Files changed (48) hide show
  1. package/package.json +36 -0
  2. package/src/audit/currentAudit.js +50 -0
  3. package/src/audit/getDepChain.js +47 -0
  4. package/src/audit/index.js +28 -0
  5. package/src/audit/normalizeAuditResult.js +47 -0
  6. package/src/audit/npmAudit.js +10 -0
  7. package/src/audit/remoteAudit.js +24 -0
  8. package/src/audit/test/test-currentAudit.js +15 -0
  9. package/src/audit/test/test-getDepChain.js +13 -0
  10. package/src/audit/test/test-index.js +17 -0
  11. package/src/audit/test/test-normalizeAuditResult.js +18 -0
  12. package/src/audit/test/test-npmAudit.js +15 -0
  13. package/src/audit/test/test-remoteAudit.js +15 -0
  14. package/src/audit/test/workdir/audit.json +2130 -0
  15. package/src/audit/test/workdir/current.json +10 -0
  16. package/src/audit/test/workdir/index.json +2398 -0
  17. package/src/audit/test/workdir/normalized.json +2581 -0
  18. package/src/audit/test/workdir/package-lock.json +16137 -0
  19. package/src/audit/test/workdir/package.json +1 -0
  20. package/src/audit/test/workdir/remote.json +75 -0
  21. package/src/common/utils.js +35 -0
  22. package/src/entry/index.js +28 -0
  23. package/src/entry/test/result/result-local.md +1177 -0
  24. package/src/entry/test/result/result-remote.md +151 -0
  25. package/src/entry/test/test-index.js +15 -0
  26. package/src/generateLock/generateLock.js +27 -0
  27. package/src/generateLock/index.js +1 -0
  28. package/src/generateLock/test/1.json +1 -0
  29. package/src/generateLock/test/test.js +15 -0
  30. package/src/generateLock/test/workdir/package-lock.json +16137 -0
  31. package/src/generateLock/test/workdir/package.json +1 -0
  32. package/src/main/index.js +23 -0
  33. package/src/mcpServer.js +43 -0
  34. package/src/parseProject/index.js +18 -0
  35. package/src/parseProject/parseLocalProject.js +8 -0
  36. package/src/parseProject/parseRemoteProject.js +65 -0
  37. package/src/parseProject/test/test.js +26 -0
  38. package/src/render/index.js +24 -0
  39. package/src/render/markdown.js +17 -0
  40. package/src/render/template/audit.ejs +30 -0
  41. package/src/render/template/detail-item.ejs +32 -0
  42. package/src/render/template/detail.ejs +7 -0
  43. package/src/render/template/index.ejs +8 -0
  44. package/src/render/test/test-index.js +27 -0
  45. package/src/render/test/workdir/auditResult.json +2101 -0
  46. package/src/render/test/workdir/index.md +1221 -0
  47. package/src/render/test/workdir/package.json +38 -0
  48. package/src/workDir/index.js +21 -0
@@ -0,0 +1 @@
1
+ {"name":"my-site","version":"0.1.0","private":true,"scripts":{"serve":"vue-cli-service serve","build":"vue-cli-service build --modern","test:Pager":"vue serve ./src/components/Pager/test.vue","test:Avatar":"vue serve ./src/components/Avatar/test.vue","test:Icon":"vue serve ./src/components/Icon/test.vue","test:Empty":"vue serve ./src/components/Empty/test.vue","test:ImageLoader":"vue serve ./src/components/ImageLoader/test.vue","test:Contact":"vue serve ./src/components/SiteAside/Contact/test.vue","test:Menu":"vue serve ./src/components/SiteAside/Menu/test.vue","test:SiteAside":"vue serve ./src/components/SiteAside/test.vue","test:Layout":"vue serve ./src/components/Layout/test.vue","test:RightList":"vue serve ./src/views/Blog/components/RightList-test.vue"},"dependencies":{"axios":"^0.21.0","core-js":"^3.6.5","highlight.js":"^10.5.0","mockjs":"^1.1.0","nprogress":"^0.2.0","querystring":"^0.2.0","vue":"^2.6.11","vue-router":"^3.4.9","vuex":"^3.6.2"},"devDependencies":{"@vue/cli-plugin-babel":"~4.5.0","@vue/cli-service":"~4.5.0","less":"^3.0.4","less-loader":"^5.0.0","vue-template-compiler":"^2.6.11","webpack-bundle-analyzer":"^4.4.0"}}
@@ -0,0 +1,23 @@
1
+ import { createWorkDir, deleteWorkDir } from '../workDir/index.js';
2
+ import { parseProject } from '../parseProject/index.js';
3
+
4
+ /**
5
+ * 对项目本身以及其所有直接和间接依赖进行安全审计
6
+ * @param {string} projectRoot 项目根目录,可以是本地项目的路径或远程项目的URL
7
+ */
8
+ export async function auditProject(projectRoot) {
9
+ // 1. 创建工作目录
10
+ const workDir = await createWorkDir();
11
+ // 2. 解析项目的package.json文件
12
+ const packageJSON = await parseProject(projectRoot);
13
+ // 3. 在工作目录中写入
14
+ const depTree = await generateDepTree(packageJSON);
15
+ // 3. 解析依赖树,获取每个包的依赖关系
16
+ const parsedTree = await parseTree(depTree);
17
+ // 4. 创建审计任务
18
+ const tasks = createTasks(parsedTree);
19
+ }
20
+
21
+ auditProject(
22
+ '/Users/yuanjin/工作/课/录播课/付费课/60 天任务式学习/08. vue从入门到实战/案例/my-site'
23
+ );
@@ -0,0 +1,43 @@
1
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
3
+ import { z } from 'zod';
4
+ import { auditPackage } from './entry/index.js';
5
+
6
+ const server = new McpServer({
7
+ name: 'audit-server',
8
+ title: '前端工程安全审计服务',
9
+ version: '0.1.0',
10
+ });
11
+
12
+ server.registerTool(
13
+ 'auditPackage',
14
+ {
15
+ title: '审计前端工程',
16
+ description:
17
+ '审计前端工程的所有直接和间接依赖,得到安全审计结果。支持本地工程的审计,也支持远程仓库的审计。审计结果为标准格式的markdown字符串,不用修改,直接用于展示即可。',
18
+ inputSchema: {
19
+ projectRoot: z
20
+ .string()
21
+ .describe('本地工程的根路径,或者远程仓库的URL地址'),
22
+ savePath: z
23
+ .string()
24
+ .describe(
25
+ '保存审计结果的路径,传递当前工程的根路径下的工程明audit.md,如果没有当前工程,则传递桌面路径下的audit.md(注意,桌面路径必须传入绝对路径)'
26
+ ),
27
+ },
28
+ },
29
+ async ({ projectRoot, savePath }) => {
30
+ await auditPackage(projectRoot, savePath);
31
+ return {
32
+ content: [
33
+ {
34
+ type: 'text',
35
+ text: `审计完成,结果已保存到: ${savePath}`,
36
+ },
37
+ ],
38
+ };
39
+ }
40
+ );
41
+
42
+ const transport = new StdioServerTransport();
43
+ server.connect(transport);
@@ -0,0 +1,18 @@
1
+ import { parseLocalProject } from './parseLocalProject.js';
2
+ import { parseRemoteProject } from './parseRemoteProject.js';
3
+
4
+ /**
5
+ * 解析工程根目录下的package.json文件
6
+ * @param {string} projectRoot 工程本地的根目录或远程仓库的URL
7
+ * @example
8
+ * parseProject('/path/to/local/project');
9
+ * parseProject('https://github.com/webpack/webpack');
10
+ * @returns {Promise<Object>} 返回解析后的package.json内容
11
+ * @throws {Error} 如果解析失败或文件不存在
12
+ */
13
+ export function parseProject(projectRoot) {
14
+ if (projectRoot.startsWith('http://') || projectRoot.startsWith('https://')) {
15
+ return parseRemoteProject(projectRoot);
16
+ }
17
+ return parseLocalProject(projectRoot);
18
+ }
@@ -0,0 +1,8 @@
1
+ import path from 'path';
2
+ import fs from 'fs';
3
+
4
+ export async function parseLocalProject(projectRoot) {
5
+ const packageJsonPath = path.join(projectRoot, 'package.json');
6
+ const json = await fs.promises.readFile(packageJsonPath, 'utf8');
7
+ return JSON.parse(json);
8
+ }
@@ -0,0 +1,65 @@
1
+ /**
2
+ * 解析 GitHub 仓库 URL,提取 owner、repo 和后续路径
3
+ * 支持格式:
4
+ * - https://github.com/owner/repo
5
+ * - https://github.com/owner/repo/tree/branch
6
+ * - https://github.com/owner/repo/blame/...
7
+ * 等等
8
+ *
9
+ * @param {string} url - GitHub 仓库 URL
10
+ * @returns {Object} { owner, repo, path }
11
+ * @throws {Error} 如果 URL 格式不合法或无法解析
12
+ */
13
+ function parseGithubUrl(url) {
14
+ try {
15
+ const parsedUrl = new URL(url);
16
+
17
+ // 确保是 github.com
18
+ if (parsedUrl.hostname !== 'github.com') {
19
+ throw new Error('Only github.com URLs are supported');
20
+ }
21
+
22
+ // 获取路径并去除空字符串(如开头的 /)
23
+ const parts = parsedUrl.pathname.split('/').filter(Boolean);
24
+
25
+ // 至少需要 owner 和 repo 两段
26
+ if (parts.length < 2) {
27
+ throw new Error(
28
+ 'Invalid GitHub repository URL: insufficient path segments'
29
+ );
30
+ }
31
+
32
+ const owner = parts[0];
33
+ const repo = parts[1];
34
+ const restPath = parts.slice(2); // 剩余路径,如 ['tree', 'v5.2.2']
35
+
36
+ // 构造 path:如果有后续路径,则以 '/' 开头拼接;否则为空字符串
37
+ const path = restPath.length > 0 ? '/' + restPath.join('/') : '';
38
+
39
+ return { owner, repo, path };
40
+ } catch (error) {
41
+ if (error instanceof TypeError) {
42
+ throw new Error('Invalid URL: malformed or missing');
43
+ }
44
+ throw error;
45
+ }
46
+ }
47
+
48
+ async function getPackageJsonUrl(gitInfo) {
49
+ let { owner, repo, path } = gitInfo;
50
+ if (path.startsWith('/tree/')) {
51
+ const pathParts = path.split('/').filter(Boolean);
52
+ path = `tags/${pathParts[1]}`;
53
+ } else {
54
+ const url = `https://api.github.com/repos/${owner}/${repo}`;
55
+ const info = await fetch(url).then((resp) => resp.json());
56
+ path = `heads/${info.default_branch}`;
57
+ }
58
+ return `https://raw.githubusercontent.com/${owner}/${repo}/${path}/package.json`;
59
+ }
60
+
61
+ export async function parseRemoteProject(githubUrl) {
62
+ const gitInfo = parseGithubUrl(githubUrl);
63
+ const packgeJsonUrl = await getPackageJsonUrl(gitInfo);
64
+ return await fetch(packgeJsonUrl).then((resp) => resp.json());
65
+ }
@@ -0,0 +1,26 @@
1
+ import { parseProject } from '../index.js';
2
+
3
+ // 测试本地项目解析
4
+ async function testLocalProject() {
5
+ const localProjectPath = '/Users/yuanjin/Desktop/mcp-audit';
6
+ try {
7
+ const packageJson = await parseProject(localProjectPath);
8
+ console.log('本地项目解析成功:', packageJson);
9
+ } catch (error) {
10
+ console.error('本地项目解析失败:', error);
11
+ }
12
+ }
13
+
14
+ // 测试远程项目解析
15
+ async function testRemoteProject() {
16
+ const remoteProjectUrl = 'https://github.com/webpack/webpack';
17
+ try {
18
+ const packageJson = await parseProject(remoteProjectUrl);
19
+ console.log('远程项目解析成功:', packageJson);
20
+ } catch (error) {
21
+ console.error('远程项目解析失败:', error);
22
+ }
23
+ }
24
+
25
+ testLocalProject();
26
+ testRemoteProject();
@@ -0,0 +1,24 @@
1
+ import { renderMarkdown } from './markdown.js';
2
+
3
+ const desc = {
4
+ severityLevels: {
5
+ low: '低危',
6
+ moderate: '中危',
7
+ high: '高危',
8
+ critical: '严重',
9
+ },
10
+ };
11
+
12
+ /**
13
+ * 讲auditResult渲染为markdown格式的字符串
14
+ * @param {object} auditResult 规范化的审计结果
15
+ * @param {object} packageJson 包的package.json内容
16
+ */
17
+ export async function render(auditResult, packageJson) {
18
+ const data = {
19
+ audit: auditResult,
20
+ desc,
21
+ packageJson,
22
+ };
23
+ return await renderMarkdown(data);
24
+ }
@@ -0,0 +1,17 @@
1
+ import ejs from 'ejs';
2
+ import { join } from 'path';
3
+ import { getDirname } from '../common/utils.js';
4
+
5
+ const templatePath = join(getDirname(import.meta.url), './template/index.ejs');
6
+
7
+ export function renderMarkdown(data) {
8
+ return new Promise((resolve, reject) => {
9
+ ejs.renderFile(templatePath, data, (err, str) => {
10
+ if (err) {
11
+ reject(err);
12
+ return;
13
+ }
14
+ resolve(str);
15
+ });
16
+ });
17
+ }
@@ -0,0 +1,30 @@
1
+ 您所审计的工程总共有 **<%- audit.summary.total %>** 个风险漏洞。
2
+
3
+ 其中:
4
+
5
+ - **<%- desc.severityLevels.critical %>漏洞**:共计 **<%- audit.summary.critical %>** 个
6
+ - **<%- desc.severityLevels.high %>漏洞**:共计 **<%- audit.summary.high %>** 个
7
+ - **<%- desc.severityLevels.moderate %>漏洞**:共计 **<%- audit.summary.moderate %>** 个
8
+ - **<%- desc.severityLevels.low %>漏洞**:共计 **<%- audit.summary.low %>** 个
9
+
10
+ > 说明:
11
+ >
12
+ > - **<%- desc.severityLevels.critical %>**漏洞被认为是极其严重的,应该立即修复。
13
+ > - **<%- desc.severityLevels.high %>**漏洞被认为是严重的,应该尽快修复。
14
+ > - **<%- desc.severityLevels.moderate %>**漏洞被认为是中等严重的,可以选择在时间允许时修复。
15
+ > - **<%- desc.severityLevels.low %>**漏洞被认为是轻微的,可以根据自行需要进行修复。
16
+
17
+ 下面是漏洞的详细信息
18
+
19
+ <% if (audit.summary.critical) { %>
20
+ <%- include('./detail.ejs', {type:'critical'}); %>
21
+ <% } %>
22
+ <% if (audit.summary.high) { %>
23
+ <%- include('./detail.ejs', {type:'high'}); %>
24
+ <% } %>
25
+ <% if (audit.summary.moderate) { %>
26
+ <%- include('./detail.ejs', {type:'moderate'}); %>
27
+ <% } %>
28
+ <% if (audit.summary.low) { %>
29
+ <%- include('./detail.ejs', {type:'low'}); %>
30
+ <% } %>
@@ -0,0 +1,32 @@
1
+ ### `<%- item.name -%>`
2
+
3
+ **漏洞描述**:
4
+ <% item.problems.forEach((problem) => { %>
5
+ - <%- problem.title %>
6
+ - npm漏洞编号:`<%- problem.source %>`
7
+ - 漏洞详细说明:<%- problem.url %>
8
+ - 漏洞等级:<%- desc.severityLevels[problem.severity] %>
9
+ - 受影响的版本:`<%- problem.range %>`
10
+ <% }); %>
11
+
12
+ **依赖关系**:
13
+ <% if(item.depChains.length === 0) { %>
14
+ <% if(item.name === packageJson.name) { %>
15
+ 当前工程
16
+ <% } else { %>
17
+ - `<%- packageJson.name %>` / <%- item.name %>
18
+ <% } %>
19
+ <% } else { %>
20
+ <% item.depChains.forEach((chain) => { %>
21
+ <% if(chain.length === 1 && chain[0] === packageJson.name) { %>
22
+ 当前工程
23
+ <% } else { %>
24
+ - `<%- packageJson.name %>` / <%- chain.map(c=>`\`${c}\``).join(' / ') %>
25
+ <% } %>
26
+ <% }); %>
27
+ <% } %>
28
+
29
+ **漏洞包所在目录**:
30
+ <% item.nodes.forEach((path) => { %>
31
+ - `<%- path %>`
32
+ <% }); %>
@@ -0,0 +1,7 @@
1
+ ## <%-desc.severityLevels[type] %>漏洞
2
+
3
+ 共计 **<%- audit.summary[type] %>** 个
4
+
5
+ <% audit.vulnerabilities[type].forEach(function(item){ %>
6
+ <%- include('./detail-item.ejs', {item: item}) %>
7
+ <% }); %>
@@ -0,0 +1,8 @@
1
+ # `<%- packageJson.name %>`审计结果
2
+
3
+ <% if(audit.summary.total) { %>
4
+ <%- include('./audit.ejs'); %>
5
+ <% } %>
6
+ <% if(audit.summary.total === 0) { %>
7
+ 你项目的所有直接依赖和间接依赖都没有发现任何风险漏洞。
8
+ <% } %>
@@ -0,0 +1,27 @@
1
+ import { render } from '../index.js';
2
+ import { getDirname } from '../../common/utils.js';
3
+ import { join } from 'path';
4
+ import fs from 'fs';
5
+
6
+ const workDir = join(getDirname(import.meta.url), './workdir');
7
+ const auditResultJson = join(workDir, './auditResult.json');
8
+ const indexJson = join(workDir, './index.md');
9
+ const packageJsonPath = join(workDir, './package.json');
10
+ const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
11
+ const auditResult = JSON.parse(fs.readFileSync(auditResultJson, 'utf8'));
12
+ const templatePath = join(getDirname(import.meta.url), '../template/index.ejs');
13
+ async function test() {
14
+ const result = await render(auditResult, packageJson);
15
+ fs.writeFileSync(indexJson, result, 'utf8');
16
+ console.log('ok');
17
+ }
18
+
19
+ // 监听文件变化
20
+ fs.watch(templatePath, (eventType) => {
21
+ if (eventType === 'change') {
22
+ console.log(`模板文件发生了变化,重新运行测试函数`);
23
+ test(); // 触发函数
24
+ }
25
+ });
26
+
27
+ console.log(`已开始监听文件: ${templatePath}`);