@blacksandscyber/mcp-server-bursar 0.5.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 (68) hide show
  1. package/README.md +230 -0
  2. package/build/config.d.ts +45 -0
  3. package/build/config.js +177 -0
  4. package/build/http-transport.d.ts +16 -0
  5. package/build/http-transport.js +191 -0
  6. package/build/index.d.ts +16 -0
  7. package/build/index.js +31 -0
  8. package/build/server.d.ts +41 -0
  9. package/build/server.js +902 -0
  10. package/build/shared/errors.d.ts +50 -0
  11. package/build/shared/errors.js +69 -0
  12. package/build/shared/linkBuilder.d.ts +93 -0
  13. package/build/shared/linkBuilder.js +148 -0
  14. package/build/shared/logger.d.ts +10 -0
  15. package/build/shared/logger.js +28 -0
  16. package/build/shield/bootRole.d.ts +60 -0
  17. package/build/shield/bootRole.js +145 -0
  18. package/build/shield/client.d.ts +265 -0
  19. package/build/shield/client.js +656 -0
  20. package/build/shield/deploy/index.d.ts +69 -0
  21. package/build/shield/deploy/index.js +569 -0
  22. package/build/shield/discovery/dataStoreDetector.d.ts +3 -0
  23. package/build/shield/discovery/dataStoreDetector.js +125 -0
  24. package/build/shield/discovery/dockerScanner.d.ts +34 -0
  25. package/build/shield/discovery/dockerScanner.js +543 -0
  26. package/build/shield/discovery/endpointScanner.d.ts +3 -0
  27. package/build/shield/discovery/endpointScanner.js +306 -0
  28. package/build/shield/discovery/environmentScanner.d.ts +86 -0
  29. package/build/shield/discovery/environmentScanner.js +545 -0
  30. package/build/shield/discovery/externalServiceDetector.d.ts +3 -0
  31. package/build/shield/discovery/externalServiceDetector.js +98 -0
  32. package/build/shield/discovery/frameworkDetector.d.ts +3 -0
  33. package/build/shield/discovery/frameworkDetector.js +114 -0
  34. package/build/shield/discovery/manifestGenerator.d.ts +12 -0
  35. package/build/shield/discovery/manifestGenerator.js +124 -0
  36. package/build/shield/discovery/piiDetector.d.ts +5 -0
  37. package/build/shield/discovery/piiDetector.js +203 -0
  38. package/build/shield/discovery/severity.d.ts +47 -0
  39. package/build/shield/discovery/severity.js +138 -0
  40. package/build/shield/discovery/topologyNormalizer.d.ts +109 -0
  41. package/build/shield/discovery/topologyNormalizer.js +416 -0
  42. package/build/shield/identity.d.ts +53 -0
  43. package/build/shield/identity.js +70 -0
  44. package/build/shield/install/configMerge.d.ts +91 -0
  45. package/build/shield/install/configMerge.js +324 -0
  46. package/build/shield/install/keystore.d.ts +25 -0
  47. package/build/shield/install/keystore.js +156 -0
  48. package/build/shield/install/orchestrator.d.ts +33 -0
  49. package/build/shield/install/orchestrator.js +404 -0
  50. package/build/shield/install/transports/awsSsm.d.ts +43 -0
  51. package/build/shield/install/transports/awsSsm.js +378 -0
  52. package/build/shield/install/transports/bootstrapToken.d.ts +39 -0
  53. package/build/shield/install/transports/bootstrapToken.js +117 -0
  54. package/build/shield/install/transports/ssh.d.ts +50 -0
  55. package/build/shield/install/transports/ssh.js +569 -0
  56. package/build/shield/install/types.d.ts +139 -0
  57. package/build/shield/install/types.js +10 -0
  58. package/build/shield/protocol-walkthrough.d.ts +65 -0
  59. package/build/shield/protocol-walkthrough.js +392 -0
  60. package/build/shield/provision/appProvisioner.d.ts +15 -0
  61. package/build/shield/provision/appProvisioner.js +25 -0
  62. package/build/shield/types.d.ts +261 -0
  63. package/build/shield/types.js +4 -0
  64. package/build/shield/verify/postureReporter.d.ts +4 -0
  65. package/build/shield/verify/postureReporter.js +31 -0
  66. package/dxt/blacksands-ca.crt +67 -0
  67. package/dxt/scripts/setup.js +520 -0
  68. package/package.json +76 -0
@@ -0,0 +1,306 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.scanEndpoints = scanEndpoints;
37
+ /** Extract HTTP endpoints from application source code.
38
+ *
39
+ * Workstream-1 credibility upgrade (#13d, #13e):
40
+ * - HTTP method is derived from the actual route construct (decorator verb,
41
+ * Next.js named export, router call). Routes whose method cannot be
42
+ * determined are emitted as "unknown" — never silently defaulted to GET.
43
+ * - auth_method is classified per route from inline middleware where possible
44
+ * (high confidence) and from file-level signals otherwise (low confidence),
45
+ * using a defined enum: public | session | jwt | signature | scheduler |
46
+ * token | api_key | oauth | unknown. "unknown" no longer dominates output.
47
+ * - Every endpoint carries a line number, detection provenance, and confidence.
48
+ */
49
+ const fs = __importStar(require("fs"));
50
+ const path = __importStar(require("path"));
51
+ const glob_1 = require("glob");
52
+ const HTTP_VERBS = ["get", "post", "put", "delete", "patch", "head", "options", "all"];
53
+ /** Map a character offset to a 1-based line number. */
54
+ function lineOf(content, index) {
55
+ let line = 1;
56
+ for (let i = 0; i < index && i < content.length; i++)
57
+ if (content[i] === "\n")
58
+ line++;
59
+ return line;
60
+ }
61
+ /** Classify auth signals found in a snippet (a route's middleware args, or a file). */
62
+ function classifyAuthSignal(text) {
63
+ if (/getServerSession|next-auth|requireSession|express-session|\bsession\b|passport\.authenticate/i.test(text)) {
64
+ if (/passport/i.test(text))
65
+ return "oauth";
66
+ return "session";
67
+ }
68
+ if (/getToken|jsonwebtoken|\bjwt\b|verifyJwt|bearer|expressJwt|express-jwt/i.test(text))
69
+ return "jwt";
70
+ if (/verifySignature|svix|stripe\.webhooks|x-hub-signature|hmac|webhookSecret/i.test(text))
71
+ return "signature";
72
+ if (/x-vercel-cron|CRON_SECRET|isCron|cron/i.test(text))
73
+ return "scheduler";
74
+ if (/api[_-]?key|apiKey|x-api-key/i.test(text))
75
+ return "api_key";
76
+ if (/requireAuth|isAuthenticated|ensureAuth|authMiddleware|withApiAuth|auth\(\)|authGuard|protect\b/i.test(text))
77
+ return "token";
78
+ return null;
79
+ }
80
+ async function scanEndpoints(projectPath, frameworkInfo) {
81
+ if (frameworkInfo.framework === "next.js")
82
+ return scanNextJsRoutes(projectPath);
83
+ const ext = frameworkInfo.runtime === "python" ? "**/*.py" : "**/*.{js,ts,mjs}";
84
+ const files = await (0, glob_1.glob)(ext, {
85
+ cwd: projectPath,
86
+ ignore: ["node_modules/**", "venv/**", ".venv/**", "__pycache__/**", "dist/**", "build/**", "*.test.*", "*.spec.*"],
87
+ absolute: true,
88
+ });
89
+ const endpoints = [];
90
+ for (const file of files) {
91
+ const content = fs.readFileSync(file, "utf8");
92
+ const rel = path.relative(projectPath, file);
93
+ const fileAuth = classifyAuthSignal(content); // file-level fallback signal
94
+ if (frameworkInfo.runtime === "python") {
95
+ extractFlask(content, rel, fileAuth, endpoints);
96
+ extractFastApi(content, rel, fileAuth, endpoints);
97
+ extractDjango(content, rel, fileAuth, endpoints);
98
+ }
99
+ else {
100
+ extractExpressLike(content, rel, fileAuth, endpoints);
101
+ }
102
+ }
103
+ return dedup(endpoints);
104
+ }
105
+ /** Express / Fastify / Koa-router: app.get('/x', mwA, mwB, handler) */
106
+ function extractExpressLike(content, file, fileAuth, out) {
107
+ // Capture verb, path, and the rest of the call arguments (for inline-middleware auth detection).
108
+ const routeRe = /(?:app|router|fastify)\.(get|post|put|delete|patch|all)\s*\(\s*['"`]([^'"`]+)['"`]([^)]*)/gi;
109
+ for (const m of content.matchAll(routeRe)) {
110
+ const method = m[1].toLowerCase();
111
+ const routePath = m[2];
112
+ const argsTail = m[3] || "";
113
+ if (!routePath || routePath.startsWith("__"))
114
+ continue;
115
+ const inline = classifyAuthSignal(argsTail);
116
+ out.push({
117
+ type: routePath.startsWith("/api") ? "api" : "web",
118
+ method,
119
+ path: routePath,
120
+ protocol: "https",
121
+ file,
122
+ auth_method: inline ?? fileAuth ?? "unknown",
123
+ line: lineOf(content, m.index ?? 0),
124
+ detected_via: "router-call",
125
+ confidence: "high",
126
+ auth_confidence: inline ? "high" : fileAuth ? "low" : "low",
127
+ });
128
+ }
129
+ // Mount points (middleware sub-routers) — informational, method "all".
130
+ const useRe = /(?:app|router)\.use\s*\(\s*['"`]([^'"`]+)['"`]/gi;
131
+ for (const m of content.matchAll(useRe)) {
132
+ const routePath = m[1];
133
+ if (!routePath || !routePath.startsWith("/"))
134
+ continue;
135
+ out.push({
136
+ type: routePath.startsWith("/api") ? "api" : "web",
137
+ method: "all",
138
+ path: routePath,
139
+ protocol: "https",
140
+ file,
141
+ auth_method: fileAuth ?? "unknown",
142
+ line: lineOf(content, m.index ?? 0),
143
+ detected_via: "router-call",
144
+ confidence: "medium",
145
+ auth_confidence: fileAuth ? "low" : "low",
146
+ });
147
+ }
148
+ }
149
+ /** Flask: @app.route('/x', methods=['GET','POST']) — default GET when methods omitted. */
150
+ function extractFlask(content, file, fileAuth, out) {
151
+ const re = /@(?:app|blueprint|bp)\.route\s*\(\s*['"`]([^'"`]+)['"`](?:[^)]*?methods\s*=\s*\[([^\]]+)\])?/gi;
152
+ for (const m of content.matchAll(re)) {
153
+ const routePath = m[1];
154
+ if (!routePath)
155
+ continue;
156
+ const methodsRaw = m[2];
157
+ const methods = methodsRaw
158
+ ? methodsRaw.split(",").map(s => s.replace(/['"`\s]/g, "").toLowerCase()).filter(Boolean)
159
+ : ["get"];
160
+ for (const method of methods) {
161
+ out.push({
162
+ type: routePath.startsWith("/api") ? "api" : "web",
163
+ method: HTTP_VERBS.includes(method) ? method : "unknown",
164
+ path: routePath,
165
+ protocol: "https",
166
+ file,
167
+ auth_method: fileAuth ?? "unknown",
168
+ line: lineOf(content, m.index ?? 0),
169
+ detected_via: "decorator",
170
+ confidence: "high",
171
+ auth_confidence: fileAuth ? "low" : "low",
172
+ });
173
+ }
174
+ }
175
+ }
176
+ /** FastAPI: @app.get('/x') / @router.post('/x') */
177
+ function extractFastApi(content, file, fileAuth, out) {
178
+ const re = /@(?:app|router)\.(get|post|put|delete|patch|head|options)\s*\(\s*['"`]([^'"`]+)['"`]/gi;
179
+ for (const m of content.matchAll(re)) {
180
+ const method = m[1].toLowerCase();
181
+ const routePath = m[2];
182
+ if (!routePath)
183
+ continue;
184
+ // FastAPI auth often via Depends(...) on the function signature near the decorator.
185
+ const depends = /Depends\s*\(/.test(content.slice(m.index ?? 0, (m.index ?? 0) + 400));
186
+ out.push({
187
+ type: routePath.startsWith("/api") ? "api" : "web",
188
+ method,
189
+ path: routePath,
190
+ protocol: "https",
191
+ file,
192
+ auth_method: depends ? (fileAuth ?? "token") : (fileAuth ?? "unknown"),
193
+ line: lineOf(content, m.index ?? 0),
194
+ detected_via: "decorator",
195
+ confidence: "high",
196
+ auth_confidence: depends ? "medium" : fileAuth ? "low" : "low",
197
+ });
198
+ }
199
+ }
200
+ /** Django URLconf: path('x/', view) — method is not declared at the URL layer. */
201
+ function extractDjango(content, file, fileAuth, out) {
202
+ const re = /(?:re_)?path\s*\(\s*r?['"`]([^'"`]+)['"`]/gi;
203
+ for (const m of content.matchAll(re)) {
204
+ const routePath = m[1];
205
+ if (!routePath)
206
+ continue;
207
+ const norm = routePath.startsWith("/") ? routePath : "/" + routePath;
208
+ out.push({
209
+ type: norm.startsWith("/api") ? "api" : "web",
210
+ method: "unknown", // URLconf does not bind a method; the view decides
211
+ path: norm,
212
+ protocol: "https",
213
+ file,
214
+ auth_method: fileAuth ?? "unknown",
215
+ line: lineOf(content, m.index ?? 0),
216
+ detected_via: "url-conf",
217
+ confidence: "medium",
218
+ auth_confidence: fileAuth ? "low" : "low",
219
+ });
220
+ }
221
+ }
222
+ function scanNextJsRoutes(projectPath) {
223
+ const endpoints = [];
224
+ const dirs = ["app", "pages", "src/app", "src/pages"].map(d => path.join(projectPath, d));
225
+ const routeDir = dirs.find(d => fs.existsSync(d));
226
+ if (!routeDir)
227
+ return endpoints;
228
+ const apiDir = path.join(routeDir, "api");
229
+ if (fs.existsSync(apiDir))
230
+ walkDir(apiDir, routeDir, endpoints, "api", projectPath);
231
+ walkDir(routeDir, routeDir, endpoints, "web", projectPath);
232
+ return dedup(endpoints);
233
+ }
234
+ /** Next.js App Router route handlers export named functions per HTTP method. */
235
+ function parseRouteHandlerMethods(content) {
236
+ const methods = new Set();
237
+ // export async function GET(...) / export function POST(...)
238
+ for (const m of content.matchAll(/export\s+(?:async\s+)?function\s+(GET|POST|PUT|DELETE|PATCH|HEAD|OPTIONS)\b/g)) {
239
+ methods.add(m[1].toLowerCase());
240
+ }
241
+ // export const GET = ... / export { GET, POST }
242
+ for (const m of content.matchAll(/export\s+const\s+(GET|POST|PUT|DELETE|PATCH|HEAD|OPTIONS)\b/g)) {
243
+ methods.add(m[1].toLowerCase());
244
+ }
245
+ for (const m of content.matchAll(/export\s*\{([^}]*)\}/g)) {
246
+ for (const tok of m[1].split(",")) {
247
+ const name = tok.trim().split(/\s+as\s+/i)[0].trim().toUpperCase();
248
+ if (["GET", "POST", "PUT", "DELETE", "PATCH", "HEAD", "OPTIONS"].includes(name))
249
+ methods.add(name.toLowerCase());
250
+ }
251
+ }
252
+ return [...methods];
253
+ }
254
+ function walkDir(dir, baseDir, endpoints, type, projectPath) {
255
+ if (!fs.existsSync(dir))
256
+ return;
257
+ for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
258
+ if (entry.isDirectory() && !entry.name.startsWith(".") && entry.name !== "node_modules") {
259
+ walkDir(path.join(dir, entry.name), baseDir, endpoints, type, projectPath);
260
+ }
261
+ else if (entry.isFile() && /^(page|route|index)\.(js|ts|jsx|tsx)$/.test(entry.name)) {
262
+ const full = path.join(dir, entry.name);
263
+ const routePath = "/" + path.relative(baseDir, dir).replace(/\\/g, "/");
264
+ const rel = path.relative(projectPath, full);
265
+ const isRouteHandler = /^route\./.test(entry.name);
266
+ const content = fs.readFileSync(full, "utf8");
267
+ const fileAuth = classifyAuthSignal(content);
268
+ if (isRouteHandler) {
269
+ const methods = parseRouteHandlerMethods(content);
270
+ const list = methods.length > 0 ? methods : ["unknown"];
271
+ for (const method of list) {
272
+ endpoints.push({
273
+ type, method, path: routePath, protocol: "https", file: rel,
274
+ auth_method: fileAuth ?? "unknown",
275
+ line: 1,
276
+ detected_via: methods.length > 0 ? "named-export" : "fallback",
277
+ confidence: methods.length > 0 ? "high" : "low",
278
+ auth_confidence: fileAuth ? "low" : "low",
279
+ });
280
+ }
281
+ }
282
+ else {
283
+ // page/index files render a view → GET-equivalent web route.
284
+ endpoints.push({
285
+ type, method: "get", path: routePath, protocol: "https", file: rel,
286
+ auth_method: fileAuth ?? "unknown",
287
+ line: 1,
288
+ detected_via: "page-file",
289
+ confidence: "medium",
290
+ auth_confidence: fileAuth ? "low" : "low",
291
+ });
292
+ }
293
+ }
294
+ }
295
+ }
296
+ function dedup(endpoints) {
297
+ const seen = new Set();
298
+ return endpoints.filter(e => {
299
+ const key = `${e.method}:${e.path}:${e.file}`;
300
+ if (seen.has(key))
301
+ return false;
302
+ seen.add(key);
303
+ return true;
304
+ });
305
+ }
306
+ //# sourceMappingURL=endpointScanner.js.map
@@ -0,0 +1,86 @@
1
+ /**
2
+ * macOS environment scanner — Phase 1, READ-ONLY host inspection.
3
+ *
4
+ * Produces the raw infra-plane material (host + networks + containers + volumes
5
+ * + candidate service nodes) that {@link topologyNormalizer} folds into the
6
+ * frozen topology envelope. The vocabulary here is deliberately neutral; the
7
+ * normalizer is responsible for conforming to the viz contract.
8
+ *
9
+ * SECURITY MODEL (hard requirements):
10
+ * - Only the three allowlisted binaries below are ever executed.
11
+ * - Every spawn uses execFile with an ARGUMENT ARRAY — never a shell string,
12
+ * never string-interpolation of any value. There is nothing untrusted to
13
+ * interpolate (all args are literals), but we keep the discipline anyway.
14
+ * - Every spawn has a timeout. A missing binary, a non-zero exit, malformed
15
+ * output, or a timeout DEGRADES GRACEFULLY: we return whatever we have plus
16
+ * a human-readable note. The tool must never crash.
17
+ */
18
+ export interface RawHostFacts {
19
+ hostname: string;
20
+ arch: string;
21
+ platform: string;
22
+ release: string;
23
+ vcpu: number;
24
+ cpuModel: string;
25
+ totalMemBytes: number;
26
+ totalMemGB: string;
27
+ interfaces: Array<{
28
+ name: string;
29
+ address: string;
30
+ family: string;
31
+ cidr: string | null;
32
+ }>;
33
+ containerCliPresent: boolean;
34
+ }
35
+ export interface RawNetwork {
36
+ id: string;
37
+ name: string;
38
+ subnet: string | null;
39
+ gateway: string | null;
40
+ driver: string | null;
41
+ }
42
+ export interface RawContainer {
43
+ id: string;
44
+ name: string;
45
+ image: string | null;
46
+ status: string;
47
+ running: boolean;
48
+ ip: string | null;
49
+ networks: string[];
50
+ volumes: string[];
51
+ ports: string[];
52
+ command: string | null;
53
+ /** Live CPU % (0–100+) for RUNNING containers when a metrics source provides it; omitted otherwise. */
54
+ cpu?: number;
55
+ /** Live memory % (0–100) for RUNNING containers when a metrics source provides it; omitted otherwise. */
56
+ mem?: number;
57
+ }
58
+ export interface RawVolume {
59
+ id: string;
60
+ name: string;
61
+ driver: string | null;
62
+ source: string | null;
63
+ }
64
+ export interface RawListeningPort {
65
+ port: number;
66
+ proto: string;
67
+ address: string;
68
+ process: string | null;
69
+ pid: number | null;
70
+ }
71
+ export interface RawEnvironment {
72
+ host: RawHostFacts;
73
+ networks: RawNetwork[];
74
+ containers: RawContainer[];
75
+ volumes: RawVolume[];
76
+ listeningPorts: RawListeningPort[];
77
+ /** Human-readable degradation/diagnostic notes (missing binaries, parse fallbacks, …). */
78
+ notes: string[];
79
+ }
80
+ /**
81
+ * Inspect the local macOS host READ-ONLY and return the raw environment. Never
82
+ * throws: any failure is captured in `notes` and the corresponding section is
83
+ * returned empty/partial.
84
+ */
85
+ export declare function scanMacEnvironment(): Promise<RawEnvironment>;
86
+ //# sourceMappingURL=environmentScanner.d.ts.map