aws-security-mcp 0.7.3 → 0.7.5
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.
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// src/commands/dashboard.ts
|
|
2
2
|
import { createServer } from "http";
|
|
3
3
|
import { readFile } from "fs/promises";
|
|
4
|
-
import { join, extname, resolve } from "path";
|
|
4
|
+
import { join, extname, resolve, sep } from "path";
|
|
5
5
|
import { existsSync, copyFileSync } from "fs";
|
|
6
6
|
import { fileURLToPath } from "url";
|
|
7
7
|
import { exec } from "child_process";
|
|
@@ -46,7 +46,7 @@ Expected: ${dashboardDir}`
|
|
|
46
46
|
let filePath = resolve(
|
|
47
47
|
join(dashboardDir, url === "/" ? "index.html" : url)
|
|
48
48
|
);
|
|
49
|
-
if (!filePath.startsWith(resolvedBase +
|
|
49
|
+
if (!filePath.startsWith(resolvedBase + sep) && filePath !== resolvedBase) {
|
|
50
50
|
res.writeHead(403);
|
|
51
51
|
res.end("Forbidden");
|
|
52
52
|
return;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/commands/dashboard.ts"],"sourcesContent":["import { createServer } from \"node:http\";\nimport { readFile } from \"node:fs/promises\";\nimport { join, extname, resolve } from \"node:path\";\nimport { existsSync, copyFileSync } from \"node:fs\";\nimport { fileURLToPath } from \"node:url\";\nimport { exec } from \"node:child_process\";\n\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = join(__filename, \"..\");\n\nconst MIME_TYPES: Record<string, string> = {\n \".html\": \"text/html\",\n \".js\": \"text/javascript\",\n \".css\": \"text/css\",\n \".json\": \"application/json\",\n \".svg\": \"image/svg+xml\",\n \".png\": \"image/png\",\n \".ico\": \"image/x-icon\",\n \".woff\": \"font/woff\",\n \".woff2\": \"font/woff2\",\n};\n\nexport function startDashboard(port = 3000): void {\n // dashboard/dist is two levels up from dist/src/commands/\n const dashboardDir = join(__dirname, \"../../dashboard/dist\");\n\n if (!existsSync(dashboardDir)) {\n console.error(\n `Dashboard not built. Run \"npm run build:dashboard\" first.\\n` +\n `Expected: ${dashboardDir}`,\n );\n process.exit(1);\n }\n\n // Copy real data.json if available\n const dataSource = join(\n process.env.HOME || process.env.USERPROFILE || \"~\",\n \".aws-security/dashboard/data.json\",\n );\n const dataDest = join(dashboardDir, \"data.json\");\n if (existsSync(dataSource)) {\n copyFileSync(dataSource, dataDest);\n console.log(`Loaded scan data from ${dataSource}`);\n } else {\n console.log(\n \"No scan data found at ~/.aws-security/dashboard/data.json — using bundled sample data\",\n );\n }\n\n const resolvedBase = resolve(dashboardDir);\n\n const server = createServer(async (req, res) => {\n const url = req.url?.split(\"?\")[0] ?? \"/\";\n let filePath = resolve(\n join(dashboardDir, url === \"/\" ? \"index.html\" : url),\n );\n\n // Ensure the resolved path is within dashboardDir\n if (!filePath.startsWith(resolvedBase +
|
|
1
|
+
{"version":3,"sources":["../../../src/commands/dashboard.ts"],"sourcesContent":["import { createServer } from \"node:http\";\nimport { readFile } from \"node:fs/promises\";\nimport { join, extname, resolve, sep } from \"node:path\";\nimport { existsSync, copyFileSync } from \"node:fs\";\nimport { fileURLToPath } from \"node:url\";\nimport { exec } from \"node:child_process\";\n\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = join(__filename, \"..\");\n\nconst MIME_TYPES: Record<string, string> = {\n \".html\": \"text/html\",\n \".js\": \"text/javascript\",\n \".css\": \"text/css\",\n \".json\": \"application/json\",\n \".svg\": \"image/svg+xml\",\n \".png\": \"image/png\",\n \".ico\": \"image/x-icon\",\n \".woff\": \"font/woff\",\n \".woff2\": \"font/woff2\",\n};\n\nexport function startDashboard(port = 3000): void {\n // dashboard/dist is two levels up from dist/src/commands/\n const dashboardDir = join(__dirname, \"../../dashboard/dist\");\n\n if (!existsSync(dashboardDir)) {\n console.error(\n `Dashboard not built. Run \"npm run build:dashboard\" first.\\n` +\n `Expected: ${dashboardDir}`,\n );\n process.exit(1);\n }\n\n // Copy real data.json if available\n const dataSource = join(\n process.env.HOME || process.env.USERPROFILE || \"~\",\n \".aws-security/dashboard/data.json\",\n );\n const dataDest = join(dashboardDir, \"data.json\");\n if (existsSync(dataSource)) {\n copyFileSync(dataSource, dataDest);\n console.log(`Loaded scan data from ${dataSource}`);\n } else {\n console.log(\n \"No scan data found at ~/.aws-security/dashboard/data.json — using bundled sample data\",\n );\n }\n\n const resolvedBase = resolve(dashboardDir);\n\n const server = createServer(async (req, res) => {\n const url = req.url?.split(\"?\")[0] ?? \"/\";\n let filePath = resolve(\n join(dashboardDir, url === \"/\" ? \"index.html\" : url),\n );\n\n // Ensure the resolved path is within dashboardDir (handle both / and \\ separators)\n if (!filePath.startsWith(resolvedBase + sep) && filePath !== resolvedBase) {\n res.writeHead(403);\n res.end(\"Forbidden\");\n return;\n }\n\n // SPA fallback: if file doesn't exist and isn't a static asset, serve index.html\n if (!existsSync(filePath)) {\n filePath = join(dashboardDir, \"index.html\");\n }\n\n try {\n const content = await readFile(filePath);\n const ext = extname(filePath);\n res.writeHead(200, {\n \"Content-Type\": MIME_TYPES[ext] || \"application/octet-stream\",\n });\n res.end(content);\n } catch {\n res.writeHead(404);\n res.end(\"Not found\");\n }\n });\n\n server.listen(port, () => {\n const url = `http://localhost:${port}`;\n console.log(`\\nAWS Security Dashboard: ${url}\\n`);\n console.log(\"Press Ctrl+C to stop.\\n\");\n\n // Try to open browser\n const cmd =\n process.platform === \"darwin\"\n ? \"open\"\n : process.platform === \"win32\"\n ? \"start\"\n : \"xdg-open\";\n exec(`${cmd} ${url}`, () => {\n // ignore errors — browser open is best-effort\n });\n });\n\n server.on(\"error\", (err: NodeJS.ErrnoException) => {\n if (err.code === \"EADDRINUSE\") {\n console.error(`Port ${port} is already in use. Try --port <other>`);\n process.exit(1);\n }\n throw err;\n });\n}\n"],"mappings":";AAAA,SAAS,oBAAoB;AAC7B,SAAS,gBAAgB;AACzB,SAAS,MAAM,SAAS,SAAS,WAAW;AAC5C,SAAS,YAAY,oBAAoB;AACzC,SAAS,qBAAqB;AAC9B,SAAS,YAAY;AAErB,IAAM,aAAa,cAAc,YAAY,GAAG;AAChD,IAAM,YAAY,KAAK,YAAY,IAAI;AAEvC,IAAM,aAAqC;AAAA,EACzC,SAAS;AAAA,EACT,OAAO;AAAA,EACP,QAAQ;AAAA,EACR,SAAS;AAAA,EACT,QAAQ;AAAA,EACR,QAAQ;AAAA,EACR,QAAQ;AAAA,EACR,SAAS;AAAA,EACT,UAAU;AACZ;AAEO,SAAS,eAAe,OAAO,KAAY;AAEhD,QAAM,eAAe,KAAK,WAAW,sBAAsB;AAE3D,MAAI,CAAC,WAAW,YAAY,GAAG;AAC7B,YAAQ;AAAA,MACN;AAAA,YACe,YAAY;AAAA,IAC7B;AACA,YAAQ,KAAK,CAAC;AAAA,EAChB;AAGA,QAAM,aAAa;AAAA,IACjB,QAAQ,IAAI,QAAQ,QAAQ,IAAI,eAAe;AAAA,IAC/C;AAAA,EACF;AACA,QAAM,WAAW,KAAK,cAAc,WAAW;AAC/C,MAAI,WAAW,UAAU,GAAG;AAC1B,iBAAa,YAAY,QAAQ;AACjC,YAAQ,IAAI,yBAAyB,UAAU,EAAE;AAAA,EACnD,OAAO;AACL,YAAQ;AAAA,MACN;AAAA,IACF;AAAA,EACF;AAEA,QAAM,eAAe,QAAQ,YAAY;AAEzC,QAAM,SAAS,aAAa,OAAO,KAAK,QAAQ;AAC9C,UAAM,MAAM,IAAI,KAAK,MAAM,GAAG,EAAE,CAAC,KAAK;AACtC,QAAI,WAAW;AAAA,MACb,KAAK,cAAc,QAAQ,MAAM,eAAe,GAAG;AAAA,IACrD;AAGA,QAAI,CAAC,SAAS,WAAW,eAAe,GAAG,KAAK,aAAa,cAAc;AACzE,UAAI,UAAU,GAAG;AACjB,UAAI,IAAI,WAAW;AACnB;AAAA,IACF;AAGA,QAAI,CAAC,WAAW,QAAQ,GAAG;AACzB,iBAAW,KAAK,cAAc,YAAY;AAAA,IAC5C;AAEA,QAAI;AACF,YAAM,UAAU,MAAM,SAAS,QAAQ;AACvC,YAAM,MAAM,QAAQ,QAAQ;AAC5B,UAAI,UAAU,KAAK;AAAA,QACjB,gBAAgB,WAAW,GAAG,KAAK;AAAA,MACrC,CAAC;AACD,UAAI,IAAI,OAAO;AAAA,IACjB,QAAQ;AACN,UAAI,UAAU,GAAG;AACjB,UAAI,IAAI,WAAW;AAAA,IACrB;AAAA,EACF,CAAC;AAED,SAAO,OAAO,MAAM,MAAM;AACxB,UAAM,MAAM,oBAAoB,IAAI;AACpC,YAAQ,IAAI;AAAA,0BAA6B,GAAG;AAAA,CAAI;AAChD,YAAQ,IAAI,yBAAyB;AAGrC,UAAM,MACJ,QAAQ,aAAa,WACjB,SACA,QAAQ,aAAa,UACnB,UACA;AACR,SAAK,GAAG,GAAG,IAAI,GAAG,IAAI,MAAM;AAAA,IAE5B,CAAC;AAAA,EACH,CAAC;AAED,SAAO,GAAG,SAAS,CAAC,QAA+B;AACjD,QAAI,IAAI,SAAS,cAAc;AAC7B,cAAQ,MAAM,QAAQ,IAAI,wCAAwC;AAClE,cAAQ,KAAK,CAAC;AAAA,IAChB;AACA,UAAM;AAAA,EACR,CAAC;AACH;","names":[]}
|
package/dist/src/index.js
CHANGED
|
@@ -4,7 +4,7 @@ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
|
|
|
4
4
|
import { z } from "zod";
|
|
5
5
|
|
|
6
6
|
// src/version.ts
|
|
7
|
-
var VERSION = "0.7.
|
|
7
|
+
var VERSION = "0.7.5";
|
|
8
8
|
|
|
9
9
|
// src/utils/aws-client.ts
|
|
10
10
|
import { STSClient, GetCallerIdentityCommand } from "@aws-sdk/client-sts";
|
|
@@ -9060,8 +9060,9 @@ LOW \u2192 P3 (Low)
|
|
|
9060
9060
|
`;
|
|
9061
9061
|
|
|
9062
9062
|
// src/index.ts
|
|
9063
|
-
import { readFileSync as readFileSync2 } from "fs";
|
|
9063
|
+
import { readFileSync as readFileSync2, mkdirSync as mkdirSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
9064
9064
|
import { join as join2, dirname } from "path";
|
|
9065
|
+
import { homedir as homedir2 } from "os";
|
|
9065
9066
|
import { fileURLToPath } from "url";
|
|
9066
9067
|
var MODULE_DESCRIPTIONS = {
|
|
9067
9068
|
service_detection: "Detects which AWS security services (Security Hub, GuardDuty, Inspector, Config) are enabled and assesses security maturity.",
|
|
@@ -9702,6 +9703,79 @@ Deploy this as a StackSet from your Management Account to all member accounts.`
|
|
|
9702
9703
|
}
|
|
9703
9704
|
}
|
|
9704
9705
|
);
|
|
9706
|
+
server.tool(
|
|
9707
|
+
"scan_and_report",
|
|
9708
|
+
"Run a full security scan AND generate reports in one step. Avoids large data transfer between tools. Reports are saved to ~/.aws-security/reports/",
|
|
9709
|
+
{
|
|
9710
|
+
region: z.string().optional().describe("AWS region (default: server region)"),
|
|
9711
|
+
org_mode: z.boolean().optional().describe("Enable multi-account org scanning"),
|
|
9712
|
+
role_name: z.string().optional().describe("IAM role name for cross-account scanning"),
|
|
9713
|
+
account_ids: z.array(z.string()).optional().describe("Filter to specific account IDs"),
|
|
9714
|
+
reports: z.array(z.enum(["html", "hw_defense", "mlps3", "markdown", "all"])).optional().describe("Report types to generate (default: all)"),
|
|
9715
|
+
lang: z.enum(["zh", "en"]).optional().describe("Language: zh or en (default: zh)")
|
|
9716
|
+
},
|
|
9717
|
+
async ({ region, org_mode, role_name, account_ids, reports, lang }) => {
|
|
9718
|
+
try {
|
|
9719
|
+
const r = region ?? defaultRegion;
|
|
9720
|
+
const l = lang ?? "zh";
|
|
9721
|
+
const reportTypes = reports ?? ["all"];
|
|
9722
|
+
const wantAll = reportTypes.includes("all");
|
|
9723
|
+
let result;
|
|
9724
|
+
if (org_mode) {
|
|
9725
|
+
result = await runMultiAccountScanners(allScanners, r, {
|
|
9726
|
+
orgMode: true,
|
|
9727
|
+
roleName: role_name ?? "AWSSecurityMCPAudit",
|
|
9728
|
+
accountIds: account_ids
|
|
9729
|
+
});
|
|
9730
|
+
} else {
|
|
9731
|
+
result = await runAllScanners(allScanners, r);
|
|
9732
|
+
}
|
|
9733
|
+
const baseDir = join2(homedir2(), ".aws-security", "reports", (/* @__PURE__ */ new Date()).toISOString().slice(0, 10));
|
|
9734
|
+
mkdirSync2(baseDir, { recursive: true });
|
|
9735
|
+
const savedFiles = [];
|
|
9736
|
+
if (wantAll || reportTypes.includes("html")) {
|
|
9737
|
+
const html = generateHtmlReport(result, void 0, l);
|
|
9738
|
+
const p = join2(baseDir, "security-report.html");
|
|
9739
|
+
writeFileSync2(p, html);
|
|
9740
|
+
savedFiles.push(p);
|
|
9741
|
+
}
|
|
9742
|
+
if (wantAll || reportTypes.includes("hw_defense")) {
|
|
9743
|
+
const html = generateHwDefenseHtmlReport(result, l);
|
|
9744
|
+
const p = join2(baseDir, "hw-defense-report.html");
|
|
9745
|
+
writeFileSync2(p, html);
|
|
9746
|
+
savedFiles.push(p);
|
|
9747
|
+
}
|
|
9748
|
+
if (wantAll || reportTypes.includes("mlps3")) {
|
|
9749
|
+
const html = generateMlps3HtmlReport(result, void 0, l);
|
|
9750
|
+
const p = join2(baseDir, "mlps3-report.html");
|
|
9751
|
+
writeFileSync2(p, html);
|
|
9752
|
+
savedFiles.push(p);
|
|
9753
|
+
}
|
|
9754
|
+
if (wantAll || reportTypes.includes("markdown")) {
|
|
9755
|
+
const md = generateMarkdownReport(result, l);
|
|
9756
|
+
const p = join2(baseDir, "security-report.md");
|
|
9757
|
+
writeFileSync2(p, md);
|
|
9758
|
+
savedFiles.push(p);
|
|
9759
|
+
}
|
|
9760
|
+
saveResults(result);
|
|
9761
|
+
const summary = summarizeResult(result, l);
|
|
9762
|
+
const fileList = savedFiles.map((f) => ` ${f}`).join("\n");
|
|
9763
|
+
return {
|
|
9764
|
+
content: [{
|
|
9765
|
+
type: "text",
|
|
9766
|
+
text: `${summary}
|
|
9767
|
+
|
|
9768
|
+
Reports saved:
|
|
9769
|
+
${fileList}
|
|
9770
|
+
|
|
9771
|
+
Dashboard data updated.`
|
|
9772
|
+
}]
|
|
9773
|
+
};
|
|
9774
|
+
} catch (err) {
|
|
9775
|
+
return { content: [{ type: "text", text: `Error: ${err instanceof Error ? err.message : String(err)}` }], isError: true };
|
|
9776
|
+
}
|
|
9777
|
+
}
|
|
9778
|
+
);
|
|
9705
9779
|
server.resource(
|
|
9706
9780
|
"security-rules",
|
|
9707
9781
|
"security://rules",
|