mcp-audit-server 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 (50) hide show
  1. package/PUBLISH_GUIDE.md +67 -0
  2. package/bin/cli.js +5 -0
  3. package/package.json +26 -0
  4. package/src/audit/currentAudit.js +50 -0
  5. package/src/audit/getDepChain.js +47 -0
  6. package/src/audit/index.js +28 -0
  7. package/src/audit/normalizeAuditResult.js +47 -0
  8. package/src/audit/npmAudit.js +10 -0
  9. package/src/audit/remoteAudit.js +24 -0
  10. package/src/audit/test/test-currentAudit.js +15 -0
  11. package/src/audit/test/test-getDepChain.js +13 -0
  12. package/src/audit/test/test-index.js +17 -0
  13. package/src/audit/test/test-normalizeAuditResult.js +18 -0
  14. package/src/audit/test/test-npmAudit.js +15 -0
  15. package/src/audit/test/test-remoteAudit.js +15 -0
  16. package/src/audit/test/workdir/audit.json +2130 -0
  17. package/src/audit/test/workdir/current.json +10 -0
  18. package/src/audit/test/workdir/index.json +2398 -0
  19. package/src/audit/test/workdir/normalized.json +2581 -0
  20. package/src/audit/test/workdir/package-lock.json +16137 -0
  21. package/src/audit/test/workdir/package.json +1 -0
  22. package/src/audit/test/workdir/remote.json +75 -0
  23. package/src/common/utils.js +35 -0
  24. package/src/entry/index.js +28 -0
  25. package/src/entry/test/result/result-local.md +1177 -0
  26. package/src/entry/test/result/result-remote.md +151 -0
  27. package/src/entry/test/test-index.js +15 -0
  28. package/src/generateLock/generateLock.js +27 -0
  29. package/src/generateLock/index.js +1 -0
  30. package/src/generateLock/test/1.json +1 -0
  31. package/src/generateLock/test/test.js +15 -0
  32. package/src/generateLock/test/workdir/package-lock.json +16137 -0
  33. package/src/generateLock/test/workdir/package.json +1 -0
  34. package/src/main/index.js +23 -0
  35. package/src/mcpServer.js +43 -0
  36. package/src/parseProject/index.js +18 -0
  37. package/src/parseProject/parseLocalProject.js +8 -0
  38. package/src/parseProject/parseRemoteProject.js +65 -0
  39. package/src/parseProject/test/test.js +26 -0
  40. package/src/render/index.js +24 -0
  41. package/src/render/markdown.js +17 -0
  42. package/src/render/template/audit.ejs +30 -0
  43. package/src/render/template/detail-item.ejs +32 -0
  44. package/src/render/template/detail.ejs +7 -0
  45. package/src/render/template/index.ejs +8 -0
  46. package/src/render/test/test-index.js +27 -0
  47. package/src/render/test/workdir/auditResult.json +2101 -0
  48. package/src/render/test/workdir/index.md +1221 -0
  49. package/src/render/test/workdir/package.json +38 -0
  50. package/src/workDir/index.js +21 -0
@@ -0,0 +1,67 @@
1
+ # npm 发布指南:解决双因素认证问题
2
+
3
+ ## 问题描述
4
+
5
+ 在执行 `npm publish --access public` 时遇到以下错误:
6
+
7
+ ```
8
+ npm error 403 403 Forbidden - PUT https://registry.npmjs.org/mcp-audit - Two-factor authentication or granular access token with bypass 2fa enabled is required to publish packages.
9
+ ```
10
+
11
+ ## 解决方案:创建细粒度访问令牌
12
+
13
+ ### 步骤 1:登录 npm 账户
14
+
15
+ 访问 [npm 官网](https://www.npmjs.com/) 并登录您的账户。
16
+
17
+ ### 步骤 2:进入访问令牌设置
18
+
19
+ 1. 点击右上角的用户头像,选择 "Account"
20
+ 2. 在左侧导航栏中选择 "Access Tokens"
21
+ 3. 点击 "Generate New Token" 按钮,选择 "Granular Access Token"
22
+
23
+ ### 步骤 3:配置令牌权限
24
+
25
+ 1. **Token Name**:输入一个描述性名称,例如 "mcp-audit-publish"
26
+ 2. **Expiration**:选择合适的过期时间
27
+ 3. **Package Access**:
28
+ - 选择 "Read and Write"
29
+ - 在 "Packages" 部分,选择 "All packages in your account"
30
+ 4. **Enable "Bypass 2FA"**:确保勾选此选项
31
+
32
+ ### 步骤 4:生成并保存令牌
33
+
34
+ 1. 点击 "Generate Token" 按钮
35
+ 2. **重要**:复制生成的令牌,因为它只会显示一次
36
+ 3. 保存令牌到安全的地方
37
+
38
+ ### 步骤 5:使用新令牌发布
39
+
40
+ 在终端中执行以下命令:
41
+
42
+ ```bash
43
+ # 方法 1:直接在命令中使用令牌
44
+ npm publish --access public --registry=https://registry.npmjs.org/ --//registry.npmjs.org/:_authToken=<YOUR_TOKEN>
45
+
46
+ # 方法 2:更新 npm 配置
47
+ npm config set //registry.npmjs.org/:_authToken <YOUR_TOKEN>
48
+ npm publish --access public
49
+ ```
50
+
51
+ ## 替代方案:启用双因素认证
52
+
53
+ 如果您更倾向于使用双因素认证,可以:
54
+
55
+ 1. 在 npm 账户设置中启用双因素认证
56
+ 2. 使用 `npm publish --access public --otp` 命令
57
+ 3. 当提示输入验证码时,输入您的认证应用生成的代码
58
+
59
+ ## 注意事项
60
+
61
+ - 细粒度访问令牌具有更高的安全性,因为您可以精确控制其权限范围
62
+ - 请确保保护好您的访问令牌,不要在代码或公开场合中泄露
63
+ - 如果令牌泄露,请立即在 npm 账户中撤销它
64
+
65
+ ## 验证发布
66
+
67
+ 发布成功后,您可以在 [npm 官网](https://www.npmjs.com/) 上查看您的包是否已成功发布。
package/bin/cli.js ADDED
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env node
2
+
3
+ // CLI 入口:通过 npx 或全局安装启动 MCP 服务
4
+ import '../src/mcpServer.js';
5
+
package/package.json ADDED
@@ -0,0 +1,26 @@
1
+ {
2
+ "name": "mcp-audit-server",
3
+ "version": "1.0.0",
4
+ "description": "前端工程依赖安全审计的 MCP 服务",
5
+ "main": "src/main/index.js",
6
+ "type": "module",
7
+ "bin": {
8
+ "mcp-audit-server": "bin/cli.js"
9
+ },
10
+ "scripts": {
11
+ "start": "node bin/cli.js",
12
+ "test": "echo \"Error: no test specified\" && exit 1"
13
+ },
14
+ "keywords": [
15
+ "mcp",
16
+ "audit",
17
+ "security"
18
+ ],
19
+ "author": "",
20
+ "license": "ISC",
21
+ "dependencies": {
22
+ "@modelcontextprotocol/sdk": "^1.17.1",
23
+ "ejs": "^3.1.10",
24
+ "zod": "^3.25.76"
25
+ }
26
+ }
@@ -0,0 +1,50 @@
1
+ import { remoteAudit } from './remoteAudit.js';
2
+ const severityLevelsMap = {
3
+ info: 0,
4
+ low: 1,
5
+ moderate: 2,
6
+ high: 3,
7
+ critical: 4,
8
+ };
9
+
10
+ // 添加当前工程的审计结果
11
+ export async function currentAudit(name, version) {
12
+ // 1. 调用 remoteAudit 函数获取审计结果
13
+ const auditResult = await remoteAudit(name, version);
14
+
15
+ // 2. 规格化审计结果
16
+ if (
17
+ !auditResult.advisories ||
18
+ Object.keys(auditResult.advisories).length === 0
19
+ ) {
20
+ return null;
21
+ }
22
+ const result = {
23
+ name,
24
+ range: version,
25
+ nodes: ['.'],
26
+ depChains: [],
27
+ };
28
+ const advisories = Object.values(auditResult.advisories);
29
+ let maxSeverity = 'info';
30
+ result.problems = advisories.map((advisory) => {
31
+ const problem = {
32
+ source: advisory.id,
33
+ name,
34
+ dependency: name,
35
+ title: advisory.title,
36
+ url: advisory.url,
37
+ severity: advisory.severity,
38
+ cwe: advisory.cwe,
39
+ cvss: advisory.cvss,
40
+ range: advisory.vulnerable_versions,
41
+ };
42
+ // 更新最大严重性
43
+ if (severityLevelsMap[problem.severity] > severityLevelsMap[maxSeverity]) {
44
+ maxSeverity = problem.severity;
45
+ }
46
+ return problem;
47
+ });
48
+ result.severity = maxSeverity;
49
+ return result;
50
+ }
@@ -0,0 +1,47 @@
1
+ /**
2
+ * 给定图结构中的一个节点,获取从该节点的依赖节点出发一直走到终点,一共走出的所有链条
3
+ * 注意:图结构中可能存在环,遇到环时,环所在的节点直接作为终点即可
4
+ * @param {Node} node
5
+ * @returns {Array<Set<string>>} 返回所有依赖链,每个链是一个字符串集合,每个字符串是一个节点名称
6
+ */
7
+ export function getDepChains(node, globalNodeMap) {
8
+ // 存储所有找到的依赖链
9
+ const chains = [];
10
+
11
+ // 当前DFS路径(用于检测环)
12
+ const currentPath = [];
13
+
14
+ /**
15
+ * 深度优先搜索函数
16
+ * @param {Node} currentNode - 当前处理的节点
17
+ */
18
+ function dfs(currentNode) {
19
+ if (!currentNode) return;
20
+
21
+ // 检查是否形成环(当前节点已在路径中)
22
+ if (currentPath.includes(currentNode.name)) {
23
+ chains.push([...currentPath]);
24
+ return;
25
+ }
26
+
27
+ // 将当前节点加入路径
28
+ currentPath.unshift(currentNode.name);
29
+
30
+ // 如果没有依赖节点,说明到达终点
31
+ if (!currentNode.effects || currentNode.effects.length === 0) {
32
+ chains.push([...currentPath]);
33
+ } else {
34
+ // 递归处理所有依赖节点
35
+ for (const effect of currentNode.effects) {
36
+ dfs(globalNodeMap[effect]);
37
+ }
38
+ }
39
+ // 回溯:移除当前节点
40
+ currentPath.shift();
41
+ }
42
+
43
+ // 从给定节点开始DFS
44
+ dfs(node);
45
+
46
+ return chains;
47
+ }
@@ -0,0 +1,28 @@
1
+ import { npmAudit } from './npmAudit.js';
2
+ import { normalizeAuditResult } from './normalizeAuditResult.js';
3
+ import { currentAudit } from './currentAudit.js';
4
+
5
+ export async function audit(workDir, packageJson) {
6
+ // 调用 npmAudit 获取审计结果
7
+ const auditResult = await npmAudit(workDir);
8
+ // 规范化审计结果
9
+ const normalizedResult = normalizeAuditResult(auditResult);
10
+
11
+ // 添加当前工程的审计结果
12
+ const current = await currentAudit(packageJson.name, packageJson.version);
13
+ if (current) {
14
+ normalizedResult.vulnerabilities[current.severity].unshift(current);
15
+ }
16
+ // 添加汇总信息
17
+ normalizedResult.summary = {
18
+ total: Object.values(normalizedResult.vulnerabilities).reduce(
19
+ (sum, arr) => sum + arr.length,
20
+ 0
21
+ ),
22
+ critical: normalizedResult.vulnerabilities.critical.length,
23
+ high: normalizedResult.vulnerabilities.high.length,
24
+ moderate: normalizedResult.vulnerabilities.moderate.length,
25
+ low: normalizedResult.vulnerabilities.low.length,
26
+ };
27
+ return normalizedResult;
28
+ }
@@ -0,0 +1,47 @@
1
+ import { getDepChains } from './getDepChain.js';
2
+
3
+ function _normalizeVulnerabilities(auditResult) {
4
+ const result = {
5
+ critical: [],
6
+ high: [],
7
+ moderate: [],
8
+ low: [],
9
+ };
10
+ for (const key in auditResult.vulnerabilities) {
11
+ const packageInfo = auditResult.vulnerabilities[key];
12
+ const normalizedPackage = _normalizePackage(packageInfo);
13
+ if (normalizedPackage) {
14
+ result[normalizedPackage.severity].push(normalizedPackage);
15
+ }
16
+ }
17
+ return result;
18
+
19
+ function _normalizePackage(packageInfo) {
20
+ const { via = [] } = packageInfo;
21
+ const validVia = via.filter((it) => typeof it === 'object');
22
+ if (validVia.length === 0) {
23
+ return null;
24
+ }
25
+ const info = {
26
+ name: packageInfo.name,
27
+ severity: packageInfo.severity,
28
+ problems: validVia,
29
+ nodes: packageInfo.nodes || [],
30
+ };
31
+ info.depChains = getDepChains(packageInfo, auditResult.vulnerabilities);
32
+ // info.depChains = info.depChains.filter(
33
+ // (chain) => !isInvalidChain(chain, packageInfo.name)
34
+ // );
35
+ return info;
36
+ }
37
+ }
38
+
39
+ function isInvalidChain(chain, packageName) {
40
+ return chain.length === 0 || (chain.length === 1 && chain[0] === packageName);
41
+ }
42
+
43
+ export function normalizeAuditResult(auditResult) {
44
+ return {
45
+ vulnerabilities: _normalizeVulnerabilities(auditResult),
46
+ };
47
+ }
@@ -0,0 +1,10 @@
1
+ import fs from 'fs';
2
+ import { join } from 'path';
3
+ import { runCommand } from '../common/utils.js';
4
+
5
+ export async function npmAudit(workDir) {
6
+ const cmd = `npm audit --json`;
7
+ const jsonResult = await runCommand(cmd, workDir); // 在工作目录中执行命令
8
+ const auditData = JSON.parse(jsonResult);
9
+ return auditData;
10
+ }
@@ -0,0 +1,24 @@
1
+ const URL = 'https://registry.npmjs.org/-/npm/v1/security/audits';
2
+
3
+ export async function remoteAudit(packageName, pacakgeVersion) {
4
+ const body = {
5
+ name: 'example-audit', // 项目名字随便写
6
+ version: '1.0.0', // 项目的版本,随便写
7
+ requires: {
8
+ [packageName]: pacakgeVersion,
9
+ },
10
+ dependencies: {
11
+ [packageName]: {
12
+ version: pacakgeVersion,
13
+ },
14
+ },
15
+ };
16
+ const resp = await fetch(URL, {
17
+ method: 'POST',
18
+ headers: {
19
+ 'Content-Type': 'application/json',
20
+ },
21
+ body: JSON.stringify(body),
22
+ });
23
+ return await resp.json();
24
+ }
@@ -0,0 +1,15 @@
1
+ import { currentAudit } from '../currentAudit.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 currentJson = join(workDir, './current.json');
8
+
9
+ async function test() {
10
+ const result = await currentAudit('my-site', '0.1.0');
11
+ fs.writeFileSync(currentJson, JSON.stringify(result, null, 2), 'utf8');
12
+ console.log('ok');
13
+ }
14
+
15
+ test();
@@ -0,0 +1,13 @@
1
+ import { getDepChains } from '../getDepChain.js';
2
+
3
+ const globalNodeMap = {
4
+ A: { name: 'A', effects: ['B', 'C'] },
5
+ B: { name: 'B', effects: ['C'] },
6
+ C: { name: 'C', effects: ['D'] },
7
+ D: { name: 'D', effects: ['B', 'E'] }, // recursive dependency
8
+ E: { name: 'E', effects: [] },
9
+ };
10
+ const nodeA = globalNodeMap['A'];
11
+
12
+ const chains = getDepChains(nodeA, globalNodeMap);
13
+ console.log(chains); // 输出所有依赖链
@@ -0,0 +1,17 @@
1
+ import { audit } 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 indexJson = join(workDir, './index.json');
8
+ const packageJsonPath = join(workDir, 'package.json');
9
+ const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
10
+
11
+ async function test() {
12
+ const result = await audit(workDir, packageJson);
13
+ fs.writeFileSync(indexJson, JSON.stringify(result, null, 2), 'utf8');
14
+ console.log('ok');
15
+ }
16
+
17
+ test();
@@ -0,0 +1,18 @@
1
+ import { normalizeAuditResult } from '../normalizeAuditResult.js';
2
+ import fs from 'fs';
3
+ import { join } from 'path';
4
+ import { getDirname } from '../../common/utils.js';
5
+
6
+ const auditJson = join(getDirname(import.meta.url), './workdir/audit.json');
7
+ const auditResult = JSON.parse(fs.readFileSync(auditJson, 'utf8'));
8
+ function test() {
9
+ const r = normalizeAuditResult(auditResult);
10
+ const normalizedPath = join(
11
+ getDirname(import.meta.url),
12
+ './workdir/normalized.json'
13
+ );
14
+ fs.writeFileSync(normalizedPath, JSON.stringify(r, null, 2), 'utf8');
15
+ console.log('ok');
16
+ }
17
+
18
+ test();
@@ -0,0 +1,15 @@
1
+ import { npmAudit } from '../npmAudit.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 auditJson = join(workDir, './audit.json');
8
+
9
+ async function test() {
10
+ const result = await npmAudit(workDir);
11
+ fs.writeFileSync(auditJson, JSON.stringify(result, null, 2), 'utf8');
12
+ console.log('ok');
13
+ }
14
+
15
+ test();
@@ -0,0 +1,15 @@
1
+ import { remoteAudit } from '../remoteAudit.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 remoteJson = join(workDir, './remote.json');
8
+
9
+ async function test() {
10
+ const result = await remoteAudit('ejs', '3.1.9');
11
+ fs.writeFileSync(remoteJson, JSON.stringify(result, null, 2), 'utf8');
12
+ console.log('ok');
13
+ }
14
+
15
+ test();