@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,114 @@
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.detectFramework = detectFramework;
37
+ /** Detect application framework and runtime from project files. */
38
+ const fs = __importStar(require("fs"));
39
+ const path = __importStar(require("path"));
40
+ const FRAMEWORK_MAP = {
41
+ express: { framework: "express", runtime: "node" },
42
+ next: { framework: "next.js", runtime: "node" },
43
+ koa: { framework: "koa", runtime: "node" },
44
+ fastify: { framework: "fastify", runtime: "node" },
45
+ "@nestjs/core": { framework: "nestjs", runtime: "node" },
46
+ hapi: { framework: "hapi", runtime: "node" },
47
+ flask: { framework: "flask", runtime: "python" },
48
+ django: { framework: "django", runtime: "python" },
49
+ fastapi: { framework: "fastapi", runtime: "python" },
50
+ uvicorn: { framework: "fastapi", runtime: "python" },
51
+ };
52
+ async function detectFramework(projectPath) {
53
+ const result = { framework: "unknown", runtime: "unknown", version: null, packageManager: null };
54
+ // Node.js
55
+ const pkgPath = path.join(projectPath, "package.json");
56
+ if (fs.existsSync(pkgPath)) {
57
+ try {
58
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf8"));
59
+ const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
60
+ result.packageManager = fs.existsSync(path.join(projectPath, "yarn.lock")) ? "yarn"
61
+ : fs.existsSync(path.join(projectPath, "pnpm-lock.yaml")) ? "pnpm" : "npm";
62
+ for (const [dep, meta] of Object.entries(FRAMEWORK_MAP)) {
63
+ if (allDeps[dep]) {
64
+ result.framework = meta.framework;
65
+ result.runtime = meta.runtime;
66
+ result.version = String(allDeps[dep]).replace(/[\^~>=<]/g, "");
67
+ break;
68
+ }
69
+ }
70
+ if (pkg.engines?.node)
71
+ result.runtimeVersion = pkg.engines.node;
72
+ }
73
+ catch { /* ignore parse errors */ }
74
+ }
75
+ // Python requirements.txt
76
+ const reqPath = path.join(projectPath, "requirements.txt");
77
+ if (fs.existsSync(reqPath) && result.framework === "unknown") {
78
+ const content = fs.readFileSync(reqPath, "utf8").toLowerCase();
79
+ for (const [dep, meta] of Object.entries(FRAMEWORK_MAP)) {
80
+ if (content.includes(dep)) {
81
+ result.framework = meta.framework;
82
+ result.runtime = meta.runtime;
83
+ const match = content.match(new RegExp(`${dep}[=~><]*([\\d.]+)`));
84
+ if (match)
85
+ result.version = match[1];
86
+ break;
87
+ }
88
+ }
89
+ result.packageManager = "pip";
90
+ }
91
+ // pyproject.toml
92
+ const pyprojectPath = path.join(projectPath, "pyproject.toml");
93
+ if (fs.existsSync(pyprojectPath) && result.framework === "unknown") {
94
+ const content = fs.readFileSync(pyprojectPath, "utf8").toLowerCase();
95
+ for (const [dep, meta] of Object.entries(FRAMEWORK_MAP)) {
96
+ if (content.includes(dep)) {
97
+ result.framework = meta.framework;
98
+ result.runtime = meta.runtime;
99
+ break;
100
+ }
101
+ }
102
+ result.packageManager = "poetry";
103
+ }
104
+ // Docker
105
+ const dockerfilePath = path.join(projectPath, "Dockerfile");
106
+ if (fs.existsSync(dockerfilePath)) {
107
+ const dockerfile = fs.readFileSync(dockerfilePath, "utf8");
108
+ const fromMatch = dockerfile.match(/FROM\s+(\S+)/i);
109
+ if (fromMatch)
110
+ result.dockerImage = fromMatch[1];
111
+ }
112
+ return result;
113
+ }
114
+ //# sourceMappingURL=frameworkDetector.js.map
@@ -0,0 +1,12 @@
1
+ import type { FrameworkInfo, EndpointInfo, ExternalService, DataStore, PiiField, SecurityManifest } from "../types";
2
+ interface GenerateInput {
3
+ framework: FrameworkInfo;
4
+ endpoints: EndpointInfo[];
5
+ externalServices: ExternalService[];
6
+ dataStores: DataStore[];
7
+ piiFields: PiiField[];
8
+ projectPath: string;
9
+ }
10
+ export declare function generateManifest(input: GenerateInput): SecurityManifest;
11
+ export {};
12
+ //# sourceMappingURL=manifestGenerator.d.ts.map
@@ -0,0 +1,124 @@
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.generateManifest = generateManifest;
37
+ /** Generate security manifest from discovery results. */
38
+ const path = __importStar(require("path"));
39
+ const piiDetector_1 = require("./piiDetector");
40
+ function generateManifest(input) {
41
+ const { framework, endpoints, externalServices, dataStores, piiFields, projectPath } = input;
42
+ const complianceHints = (0, piiDetector_1.getComplianceHints)(piiFields);
43
+ const sensitivityLevel = (0, piiDetector_1.getSensitivityLevel)(piiFields);
44
+ const defaultPort = inferPort(framework);
45
+ const dataFlows = inferDataFlows(endpoints, externalServices, dataStores, piiFields);
46
+ return {
47
+ application: {
48
+ name: path.basename(projectPath),
49
+ version: "1.0",
50
+ framework: framework.framework,
51
+ runtime: framework.runtime,
52
+ ...(framework.runtimeVersion && { runtimeVersion: framework.runtimeVersion }),
53
+ },
54
+ endpoints: endpoints.map(ep => ({
55
+ type: ep.type, protocol: ep.protocol || "https", port: defaultPort,
56
+ path: ep.path, auth_method: ep.auth_method || "unknown",
57
+ })),
58
+ external_services: externalServices.map(svc => ({
59
+ name: svc.name, domain: svc.domain, port: svc.port, protocol: svc.protocol || "https", purpose: svc.purpose,
60
+ })),
61
+ data_stores: dataStores.map(ds => ({
62
+ type: ds.type, host: ds.host, port: ds.port, encrypted: ds.encrypted, contains_pii: piiFields.length > 0,
63
+ })),
64
+ data_flows: dataFlows,
65
+ compliance: {
66
+ frameworks: complianceHints.length > 0 ? complianceHints : ["SOC2"],
67
+ data_residency: "US",
68
+ audit_required: sensitivityLevel !== "low",
69
+ },
70
+ security_preferences: {
71
+ session_ttl: sensitivityLevel === "critical" ? 15 : sensitivityLevel === "high" ? 30 : 60,
72
+ mfa_required: sensitivityLevel === "critical" || sensitivityLevel === "high",
73
+ ip_binding: true,
74
+ auto_rotate_certs: true,
75
+ },
76
+ _metadata: {
77
+ generated_by: "@blacksandscyber/mcp-server-bursar",
78
+ generated_at: new Date().toISOString(),
79
+ confidence: calculateConfidence(framework, endpoints, externalServices, dataStores),
80
+ pii_summary: {
81
+ total_fields: piiFields.length,
82
+ sensitivity_level: sensitivityLevel,
83
+ types: [...new Set(piiFields.map(f => f.type))],
84
+ },
85
+ },
86
+ };
87
+ }
88
+ function inferPort(framework) {
89
+ const portMap = {
90
+ "next.js": 3000, express: 3000, fastify: 3000, koa: 3000, nestjs: 3000, hapi: 3000,
91
+ flask: 5000, django: 8000, fastapi: 8000,
92
+ };
93
+ return portMap[framework.framework] || 3000;
94
+ }
95
+ function inferDataFlows(endpoints, services, stores, pii) {
96
+ const flows = [];
97
+ const piiTypes = [...new Set(pii.map(f => f.type))];
98
+ const web = endpoints.filter(e => e.type === "web");
99
+ const api = endpoints.filter(e => e.type === "api");
100
+ if (web.length > 0 && api.length > 0) {
101
+ flows.push({ source: "web", destination: "api", data_types: piiTypes.length > 0 ? piiTypes.slice(0, 3) : ["user_input"], sensitivity: piiTypes.length > 0 ? "high" : "medium" });
102
+ }
103
+ for (const svc of services) {
104
+ const dt = svc.purpose === "payments" ? ["payment_info"] : svc.purpose === "email" ? ["email"] : ["api_data"];
105
+ flows.push({ source: "api", destination: svc.name.toLowerCase(), data_types: dt, sensitivity: svc.purpose === "payments" ? "critical" : "medium" });
106
+ }
107
+ for (const ds of stores) {
108
+ flows.push({ source: "api", destination: ds.type, data_types: piiTypes.length > 0 ? piiTypes : ["application_data"], sensitivity: piiTypes.length > 0 ? "high" : "medium" });
109
+ }
110
+ return flows;
111
+ }
112
+ function calculateConfidence(fw, ep, svc, ds) {
113
+ let score = 10;
114
+ if (fw.framework !== "unknown")
115
+ score += 30;
116
+ if (ep.length > 0)
117
+ score += 25;
118
+ if (svc.length > 0)
119
+ score += 20;
120
+ if (ds.length > 0)
121
+ score += 15;
122
+ return Math.min(score, 100);
123
+ }
124
+ //# sourceMappingURL=manifestGenerator.js.map
@@ -0,0 +1,5 @@
1
+ import type { PiiField } from "../types";
2
+ export declare function detectPii(projectPath: string): Promise<PiiField[]>;
3
+ export declare function getSensitivityLevel(findings: PiiField[]): string;
4
+ export declare function getComplianceHints(findings: PiiField[]): string[];
5
+ //# sourceMappingURL=piiDetector.d.ts.map
@@ -0,0 +1,203 @@
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.detectPii = detectPii;
37
+ exports.getSensitivityLevel = getSensitivityLevel;
38
+ exports.getComplianceHints = getComplianceHints;
39
+ /** Detect PII fields in source code and infer compliance requirements.
40
+ *
41
+ * Workstream-1 credibility upgrade: every finding now carries file:line, the
42
+ * source snippet, a confidence level, and remediation guidance. Matches that
43
+ * occur only in comments or prose (no structural evidence) are classified
44
+ * "low" confidence and filtered out by default, sharply cutting the
45
+ * word-match false-positive rate that eroded trust on earlier scans.
46
+ */
47
+ const fs = __importStar(require("fs"));
48
+ const path = __importStar(require("path"));
49
+ const glob_1 = require("glob");
50
+ const PII_PATTERNS = [
51
+ { pattern: /\b(email|e_mail|email_address|emailAddress)\b/gi, type: "email", sensitivity: "medium" },
52
+ { pattern: /\b(password|passwd|pass_word|passwordHash)\b/gi, type: "password", sensitivity: "high" },
53
+ { pattern: /\b(ssn|social_security|socialSecurity|social_security_number)\b/gi, type: "ssn", sensitivity: "critical" },
54
+ { pattern: /\b(phone|phone_number|phoneNumber|mobile|cell)\b/gi, type: "phone", sensitivity: "medium" },
55
+ { pattern: /\b(address|street_address|streetAddress|mailing_address)\b/gi, type: "address", sensitivity: "medium" },
56
+ { pattern: /\b(credit_card|creditCard|card_number|cardNumber|ccNumber)\b/gi, type: "credit_card", sensitivity: "critical" },
57
+ { pattern: /\b(date_of_birth|dateOfBirth|dob|birthdate|birthday)\b/gi, type: "date_of_birth", sensitivity: "high" },
58
+ { pattern: /\b(first_name|firstName|last_name|lastName|full_name|fullName)\b/gi, type: "name", sensitivity: "low" },
59
+ { pattern: /\b(passport|passport_number|passportNumber)\b/gi, type: "passport", sensitivity: "critical" },
60
+ { pattern: /\b(driver_license|driversLicense|driverLicense)\b/gi, type: "driver_license", sensitivity: "critical" },
61
+ { pattern: /\b(bank_account|bankAccount|routing_number|routingNumber)\b/gi, type: "bank_account", sensitivity: "critical" },
62
+ { pattern: /\b(medical|diagnosis|health_record|healthRecord|patient)\b/gi, type: "health_data", sensitivity: "critical" },
63
+ { pattern: /\b(ip_address|ipAddress|user_agent|userAgent)\b/gi, type: "tracking", sensitivity: "low" },
64
+ ];
65
+ const REMEDIATION = {
66
+ email: "Treat as GDPR personal data: encrypt at rest, scope access by role, and avoid logging in plaintext.",
67
+ password: "Never store plaintext; hash with bcrypt/argon2 and exclude from logs, responses, and audit trails.",
68
+ ssn: "Highest-sensitivity PII: encrypt at rest + in transit, mask in UI/logs, restrict to least-privilege access.",
69
+ phone: "Encrypt at rest and mask in logs; subject to GDPR/CCPA personal-data handling.",
70
+ address: "Encrypt at rest; subject to GDPR/CCPA personal-data handling.",
71
+ credit_card: "PCI-DSS scope: do not store PAN unless tokenized; if stored, encrypt and isolate the cardholder data environment.",
72
+ date_of_birth: "Encrypt at rest and minimize retention; with name/address it becomes high-risk identity data.",
73
+ name: "Low individually but combines with other fields into identifiable PII; apply GDPR/CCPA handling in aggregate.",
74
+ passport: "Government identifier: encrypt at rest + in transit, mask in UI, restrict to least-privilege access.",
75
+ driver_license: "Government identifier: encrypt at rest, mask in UI/logs, restrict access.",
76
+ bank_account: "Financial PII: encrypt at rest, mask in UI/logs, restrict to least-privilege access.",
77
+ health_data: "PHI under HIPAA: encrypt at rest + in transit, enforce access logging, apply minimum-necessary access.",
78
+ tracking: "Tracking identifier: subject to GDPR/CCPA consent; minimize retention and avoid joining to identity data.",
79
+ };
80
+ /** Paths whose matches are almost always documentation/fixtures/prose, not live data handling. */
81
+ const SKIP_PATH_RE = /(^|\/)(docs?|test|tests|__tests__|__mocks__|mocks|spec|specs|fixtures?|examples?|stories|\.storybook|sample|samples)(\/|$)|\.(md|mdx|stories\.[jt]sx?)$/i;
82
+ const MAX_PER_TYPE_PER_FILE = 5;
83
+ /** Build a per-line "is this line a comment" mask for a file. */
84
+ function buildCommentMask(lines) {
85
+ const mask = new Array(lines.length).fill(false);
86
+ let inBlock = false;
87
+ for (let i = 0; i < lines.length; i++) {
88
+ const trimmed = lines[i].trim();
89
+ if (inBlock) {
90
+ mask[i] = true;
91
+ if (trimmed.includes("*/"))
92
+ inBlock = false;
93
+ continue;
94
+ }
95
+ if (/^\/\*/.test(trimmed)) {
96
+ mask[i] = true;
97
+ if (!trimmed.includes("*/"))
98
+ inBlock = true;
99
+ continue;
100
+ }
101
+ // Whole-line single-line comment (JS/TS //, Python/YAML #, JSDoc-continuation *)
102
+ if (/^(\/\/|#|\*)/.test(trimmed))
103
+ mask[i] = true;
104
+ }
105
+ return mask;
106
+ }
107
+ /** Classify the structural evidence for a keyword match on a given line. */
108
+ function classifyConfidence(keyword, line, isCommentLine) {
109
+ if (isCommentLine)
110
+ return "low";
111
+ const kw = keyword.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
112
+ // Strong structural evidence: object/schema key, quoted field, form input name,
113
+ // SQL column declaration, or typed/ORM-column declaration.
114
+ const structural = [
115
+ new RegExp(`\\b${kw}\\b\\s*:`, "i"), // object/type key: email:
116
+ new RegExp(`["'\`]${kw}["'\`]`, "i"), // quoted field: "email"
117
+ new RegExp(`name\\s*=\\s*["'\`]${kw}["'\`]`, "i"), // form input: name="email"
118
+ new RegExp(`\\b${kw}\\b\\s+(varchar|text|char|int|bigint|numeric|date|timestamp|boolean|serial|uuid)`, "i"), // SQL column
119
+ new RegExp(`(const|let|var|field|column|@column)\\b[^=]*\\b${kw}\\b`, "i"), // declaration / ORM column
120
+ ];
121
+ if (structural.some(re => re.test(line)))
122
+ return "high";
123
+ // Bare identifier in real code (destructuring, params, property access).
124
+ return "medium";
125
+ }
126
+ async function detectPii(projectPath) {
127
+ const findings = [];
128
+ const seen = new Set();
129
+ const perTypePerFile = new Map();
130
+ const files = await (0, glob_1.glob)("**/*.{js,ts,py,jsx,tsx,json,yaml,yml}", {
131
+ cwd: projectPath,
132
+ ignore: ["node_modules/**", "venv/**", ".venv/**", "dist/**", "build/**", "package-lock.json", "yarn.lock", "*.test.*", "*.spec.*"],
133
+ absolute: true,
134
+ });
135
+ for (const file of files.slice(0, 200)) {
136
+ const rel = path.relative(projectPath, file);
137
+ if (SKIP_PATH_RE.test(rel))
138
+ continue;
139
+ const content = fs.readFileSync(file, "utf8");
140
+ const lines = content.split("\n");
141
+ const commentMask = buildCommentMask(lines);
142
+ for (const { pattern, type, sensitivity } of PII_PATTERNS) {
143
+ const regex = new RegExp(pattern.source, pattern.flags);
144
+ for (let lineIdx = 0; lineIdx < lines.length; lineIdx++) {
145
+ const lineText = lines[lineIdx];
146
+ if (!regex.test(lineText))
147
+ continue; // fast reject; resets lastIndex below
148
+ regex.lastIndex = 0;
149
+ const matches = [...lineText.matchAll(regex)];
150
+ if (matches.length === 0)
151
+ continue;
152
+ const isComment = commentMask[lineIdx] === true;
153
+ const confidence = classifyConfidence(matches[0][0], lineText, isComment);
154
+ // Filter low-confidence (comment/prose) matches — the false-positive reducer.
155
+ if (confidence === "low")
156
+ continue;
157
+ const key = `${type}:${rel}:${lineIdx + 1}`;
158
+ if (seen.has(key))
159
+ continue;
160
+ const ptfKey = `${type}:${rel}`;
161
+ const count = perTypePerFile.get(ptfKey) ?? 0;
162
+ if (count >= MAX_PER_TYPE_PER_FILE)
163
+ continue;
164
+ perTypePerFile.set(ptfKey, count + 1);
165
+ seen.add(key);
166
+ findings.push({
167
+ type,
168
+ sensitivity,
169
+ field: matches[0][0],
170
+ file: rel,
171
+ line: lineIdx + 1,
172
+ snippet: lineText.trim().slice(0, 160),
173
+ confidence,
174
+ remediation: REMEDIATION[type],
175
+ });
176
+ }
177
+ }
178
+ }
179
+ return findings;
180
+ }
181
+ function getSensitivityLevel(findings) {
182
+ if (findings.some(f => f.sensitivity === "critical"))
183
+ return "critical";
184
+ if (findings.some(f => f.sensitivity === "high"))
185
+ return "high";
186
+ if (findings.some(f => f.sensitivity === "medium"))
187
+ return "medium";
188
+ return "low";
189
+ }
190
+ function getComplianceHints(findings) {
191
+ const hints = [];
192
+ const types = new Set(findings.map(f => f.type));
193
+ if (types.has("credit_card") || types.has("bank_account"))
194
+ hints.push("PCI-DSS");
195
+ if (types.has("health_data"))
196
+ hints.push("HIPAA");
197
+ if (types.has("email") || types.has("name") || types.has("address") || types.has("phone"))
198
+ hints.push("GDPR");
199
+ if (types.has("ssn") || types.has("passport") || types.has("driver_license"))
200
+ hints.push("SOC2");
201
+ return hints;
202
+ }
203
+ //# sourceMappingURL=piiDetector.js.map
@@ -0,0 +1,47 @@
1
+ /** Severity tagging for scan findings (Workstream-1 #13g, SHIELD_UPGRADES §2.4).
2
+ *
3
+ * Every finding the free-tier scanners emit gets a CRITICAL/HIGH/MEDIUM/LOW/INFO
4
+ * tag from a small set of explicit, defensible rules — so a report leads with a
5
+ * risk-ordered summary instead of an undifferentiated wall of matches. The rules
6
+ * are intentionally conservative: we only escalate to CRITICAL/HIGH when the
7
+ * evidence is strong (e.g. an unauthenticated mutating admin route), and we tag
8
+ * authenticated routes INFO rather than pretending they are problems.
9
+ */
10
+ import type { EndpointInfo, PiiField, Severity } from "../types";
11
+ export declare const SEVERITY_ORDER: Severity[];
12
+ /**
13
+ * Severity for an HTTP endpoint finding.
14
+ *
15
+ * Rule order (first match wins):
16
+ * - Authenticated route → INFO (documented, not a finding)
17
+ * - No detectable auth + admin namespace + mutating → CRITICAL
18
+ * - No detectable auth + (admin OR mutating) → HIGH
19
+ * - No detectable auth + sensitive path → MEDIUM
20
+ * - No detectable auth + unknown classification → MEDIUM (couldn't tell — flag it)
21
+ * - Explicitly public + read-only + non-sensitive → LOW
22
+ */
23
+ export declare function endpointSeverity(e: EndpointInfo): {
24
+ severity: Severity;
25
+ rationale: string;
26
+ };
27
+ /**
28
+ * Severity for a PII finding, from declared data sensitivity × detection confidence.
29
+ * (Confidence "low" is already filtered upstream, so only high/medium reach here.)
30
+ */
31
+ export declare function piiSeverity(f: PiiField): {
32
+ severity: Severity;
33
+ rationale: string;
34
+ };
35
+ /** Attach severity + rationale to each endpoint (returns new objects, does not mutate). */
36
+ export declare function tagEndpoints(endpoints: EndpointInfo[]): EndpointInfo[];
37
+ /** Attach severity + rationale to each PII finding (returns new objects, does not mutate). */
38
+ export declare function tagPiiFields(fields: PiiField[]): PiiField[];
39
+ /** Build an ordered CRITICAL→INFO count map from a set of tagged findings. */
40
+ export declare function summarizeBySeverity(items: Array<{
41
+ severity?: Severity;
42
+ }>): Record<Severity, number>;
43
+ /** Highest severity present in a set of findings (defaults to INFO when empty). */
44
+ export declare function topSeverity(items: Array<{
45
+ severity?: Severity;
46
+ }>): Severity;
47
+ //# sourceMappingURL=severity.d.ts.map
@@ -0,0 +1,138 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.SEVERITY_ORDER = void 0;
4
+ exports.endpointSeverity = endpointSeverity;
5
+ exports.piiSeverity = piiSeverity;
6
+ exports.tagEndpoints = tagEndpoints;
7
+ exports.tagPiiFields = tagPiiFields;
8
+ exports.summarizeBySeverity = summarizeBySeverity;
9
+ exports.topSeverity = topSeverity;
10
+ exports.SEVERITY_ORDER = ["CRITICAL", "HIGH", "MEDIUM", "LOW", "INFO"];
11
+ /** Routes under an administrative / privileged namespace. */
12
+ const ADMIN_RE = /(^|\/)(admin|administrator|internal|debug|management|mgmt|root|superuser|sudo|console)(\/|$|-)/i;
13
+ /** Paths that touch sensitive resources even outside an explicit admin namespace. */
14
+ const SENSITIVE_RE = /(payment|billing|invoice|charge|payout|refund|secret|credential|password|token|apikey|api[-_]?key|\buser\b|\busers\b|account|tenant|\borg\b|\brole\b|permission|delete|destroy|drop|wipe|purge|export|download|upload)/i;
15
+ /** Methods that change state. */
16
+ const MUTATING = new Set(["post", "put", "delete", "patch"]);
17
+ function isMutating(method) {
18
+ const m = method.toLowerCase();
19
+ // "all" mounts every verb; "unknown" can't be ruled out as a mutation.
20
+ return MUTATING.has(m) || m === "all" || m === "unknown";
21
+ }
22
+ function hasNoDetectableAuth(authMethod) {
23
+ return authMethod === "unknown" || authMethod === "public";
24
+ }
25
+ /**
26
+ * Severity for an HTTP endpoint finding.
27
+ *
28
+ * Rule order (first match wins):
29
+ * - Authenticated route → INFO (documented, not a finding)
30
+ * - No detectable auth + admin namespace + mutating → CRITICAL
31
+ * - No detectable auth + (admin OR mutating) → HIGH
32
+ * - No detectable auth + sensitive path → MEDIUM
33
+ * - No detectable auth + unknown classification → MEDIUM (couldn't tell — flag it)
34
+ * - Explicitly public + read-only + non-sensitive → LOW
35
+ */
36
+ function endpointSeverity(e) {
37
+ const method = (e.method || "unknown").toLowerCase();
38
+ const path = e.path || "";
39
+ const admin = ADMIN_RE.test(path);
40
+ const sensitive = SENSITIVE_RE.test(path);
41
+ const mutating = isMutating(method);
42
+ if (!hasNoDetectableAuth(e.auth_method)) {
43
+ return { severity: "INFO", rationale: `Authenticated route (auth_method=${e.auth_method}).` };
44
+ }
45
+ // From here down: no detectable authentication on the route.
46
+ if (admin && mutating) {
47
+ return {
48
+ severity: "CRITICAL",
49
+ rationale: `Unauthenticated, state-changing route (${method.toUpperCase()}) under an admin namespace — anyone can mutate privileged state.`,
50
+ };
51
+ }
52
+ if (admin) {
53
+ return {
54
+ severity: "HIGH",
55
+ rationale: `Unauthenticated route under an admin namespace — privileged surface exposed without auth.`,
56
+ };
57
+ }
58
+ if (mutating) {
59
+ return {
60
+ severity: "HIGH",
61
+ rationale: `Unauthenticated, state-changing route (${method.toUpperCase()}) — unprotected write surface.`,
62
+ };
63
+ }
64
+ if (sensitive) {
65
+ return {
66
+ severity: "MEDIUM",
67
+ rationale: `Unauthenticated route touching a sensitive resource — possible information disclosure.`,
68
+ };
69
+ }
70
+ if (e.auth_method === "unknown") {
71
+ return {
72
+ severity: "MEDIUM",
73
+ rationale: `Authentication could not be determined for this route — verify it is intentionally open.`,
74
+ };
75
+ }
76
+ return {
77
+ severity: "LOW",
78
+ rationale: `Read-only route marked public — confirm no sensitive data is returned.`,
79
+ };
80
+ }
81
+ /**
82
+ * Severity for a PII finding, from declared data sensitivity × detection confidence.
83
+ * (Confidence "low" is already filtered upstream, so only high/medium reach here.)
84
+ */
85
+ function piiSeverity(f) {
86
+ const conf = f.confidence ?? "medium";
87
+ const high = conf === "high";
88
+ let severity;
89
+ switch (f.sensitivity) {
90
+ case "critical":
91
+ severity = high ? "CRITICAL" : "HIGH";
92
+ break;
93
+ case "high":
94
+ severity = high ? "HIGH" : "MEDIUM";
95
+ break;
96
+ case "medium":
97
+ severity = high ? "MEDIUM" : "LOW";
98
+ break;
99
+ default: // "low"
100
+ severity = "LOW";
101
+ }
102
+ return {
103
+ severity,
104
+ rationale: `${f.sensitivity}-sensitivity ${f.type} detected with ${conf} confidence.`,
105
+ };
106
+ }
107
+ /** Attach severity + rationale to each endpoint (returns new objects, does not mutate). */
108
+ function tagEndpoints(endpoints) {
109
+ return endpoints.map(e => {
110
+ const { severity, rationale } = endpointSeverity(e);
111
+ return { ...e, severity, severity_rationale: rationale };
112
+ });
113
+ }
114
+ /** Attach severity + rationale to each PII finding (returns new objects, does not mutate). */
115
+ function tagPiiFields(fields) {
116
+ return fields.map(f => {
117
+ const { severity, rationale } = piiSeverity(f);
118
+ return { ...f, severity, severity_rationale: rationale };
119
+ });
120
+ }
121
+ /** Build an ordered CRITICAL→INFO count map from a set of tagged findings. */
122
+ function summarizeBySeverity(items) {
123
+ const counts = { CRITICAL: 0, HIGH: 0, MEDIUM: 0, LOW: 0, INFO: 0 };
124
+ for (const it of items) {
125
+ if (it.severity)
126
+ counts[it.severity]++;
127
+ }
128
+ return counts;
129
+ }
130
+ /** Highest severity present in a set of findings (defaults to INFO when empty). */
131
+ function topSeverity(items) {
132
+ for (const s of exports.SEVERITY_ORDER) {
133
+ if (items.some(it => it.severity === s))
134
+ return s;
135
+ }
136
+ return "INFO";
137
+ }
138
+ //# sourceMappingURL=severity.js.map