k8s-av 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 (84) hide show
  1. package/.env.example +18 -0
  2. package/dist/cli/docker.d.ts +29 -0
  3. package/dist/cli/docker.js +241 -0
  4. package/dist/cli/index.d.ts +16 -0
  5. package/dist/cli/index.js +155 -0
  6. package/dist/cli/scan.d.ts +16 -0
  7. package/dist/cli/scan.js +151 -0
  8. package/dist/cli/start.d.ts +20 -0
  9. package/dist/cli/start.js +261 -0
  10. package/dist/core/attack-path.d.ts +26 -0
  11. package/dist/core/attack-path.js +191 -0
  12. package/dist/core/cve-enricher.d.ts +10 -0
  13. package/dist/core/cve-enricher.js +175 -0
  14. package/dist/core/fetcher.d.ts +26 -0
  15. package/dist/core/fetcher.js +130 -0
  16. package/dist/core/schema.d.ts +290 -0
  17. package/dist/core/schema.js +125 -0
  18. package/dist/core/transformer.d.ts +19 -0
  19. package/dist/core/transformer.js +510 -0
  20. package/dist/db/loader.d.ts +16 -0
  21. package/dist/db/loader.js +261 -0
  22. package/dist/db/neo4j-client.d.ts +35 -0
  23. package/dist/db/neo4j-client.js +218 -0
  24. package/dist/db/queries.d.ts +71 -0
  25. package/dist/db/queries.js +290 -0
  26. package/dist/db/test.d.ts +19 -0
  27. package/dist/db/test.js +268 -0
  28. package/dist/db/types.d.ts +137 -0
  29. package/dist/db/types.js +37 -0
  30. package/dist/schemas/index.d.ts +70 -0
  31. package/dist/schemas/index.js +43 -0
  32. package/dist/server/routes/blast.d.ts +3 -0
  33. package/dist/server/routes/blast.js +44 -0
  34. package/dist/server/routes/critical.d.ts +3 -0
  35. package/dist/server/routes/critical.js +80 -0
  36. package/dist/server/routes/cycles.d.ts +3 -0
  37. package/dist/server/routes/cycles.js +41 -0
  38. package/dist/server/routes/graph.d.ts +3 -0
  39. package/dist/server/routes/graph.js +57 -0
  40. package/dist/server/routes/ingest.d.ts +3 -0
  41. package/dist/server/routes/ingest.js +82 -0
  42. package/dist/server/routes/paths.d.ts +3 -0
  43. package/dist/server/routes/paths.js +66 -0
  44. package/dist/server/routes/report.d.ts +3 -0
  45. package/dist/server/routes/report.js +47 -0
  46. package/dist/server/routes/simulate.d.ts +3 -0
  47. package/dist/server/routes/simulate.js +75 -0
  48. package/dist/server/routes/vulnerabilities.d.ts +3 -0
  49. package/dist/server/routes/vulnerabilities.js +129 -0
  50. package/dist/server/server.d.ts +15 -0
  51. package/dist/server/server.js +136 -0
  52. package/dist/services/ingestion.service.d.ts +25 -0
  53. package/dist/services/ingestion.service.js +100 -0
  54. package/dist/services/report/formatter.d.ts +12 -0
  55. package/dist/services/report/formatter.js +138 -0
  56. package/dist/services/report/generator.d.ts +27 -0
  57. package/dist/services/report/generator.js +68 -0
  58. package/dist/services/risk-explainer.d.ts +67 -0
  59. package/dist/services/risk-explainer.js +285 -0
  60. package/docker/docker-compose.yml +66 -0
  61. package/package.json +75 -0
  62. package/ui/index.html +12 -0
  63. package/ui/package-lock.json +3150 -0
  64. package/ui/package.json +30 -0
  65. package/ui/postcss.config.cjs +6 -0
  66. package/ui/src/App.tsx +37 -0
  67. package/ui/src/components/Box.tsx +33 -0
  68. package/ui/src/components/DetailPanel.tsx +239 -0
  69. package/ui/src/components/RiskBadge.tsx +38 -0
  70. package/ui/src/components/Sidebar.tsx +107 -0
  71. package/ui/src/components/graph/CustomNode.tsx +102 -0
  72. package/ui/src/components/graph/GraphCanvas.tsx +174 -0
  73. package/ui/src/index.css +48 -0
  74. package/ui/src/lib/api.ts +161 -0
  75. package/ui/src/main.tsx +10 -0
  76. package/ui/src/store/useAppStore.ts +168 -0
  77. package/ui/src/views/CriticalNodeView.tsx +150 -0
  78. package/ui/src/views/OverviewView.tsx +76 -0
  79. package/ui/src/views/PathsView.tsx +280 -0
  80. package/ui/src/views/ReportView.tsx +367 -0
  81. package/ui/src/views/VulnerabilitiesView.tsx +135 -0
  82. package/ui/tailwind.config.ts +14 -0
  83. package/ui/tsconfig.json +20 -0
  84. package/ui/vite.config.ts +19 -0
@@ -0,0 +1,26 @@
1
+ /** Raw output from kubectl get <resource> -A -o json */
2
+ export interface KubeList {
3
+ apiVersion: string;
4
+ kind: string;
5
+ items: unknown[];
6
+ }
7
+ /** Structured raw cluster data as fetched from kubectl */
8
+ export interface RawClusterData {
9
+ pods: KubeList;
10
+ serviceAccounts: KubeList;
11
+ roles: KubeList;
12
+ clusterRoles: KubeList;
13
+ roleBindings: KubeList;
14
+ clusterRoleBindings: KubeList;
15
+ secrets: KubeList;
16
+ configMaps: KubeList;
17
+ services: KubeList;
18
+ }
19
+ /**
20
+ * Fetches raw Kubernetes cluster data.
21
+ *
22
+ * @param mockMode When true, loads data from the bundled mock JSON file
23
+ * and skips all kubectl calls entirely.
24
+ */
25
+ export declare function fetchClusterData(mockMode?: boolean): Promise<RawClusterData>;
26
+ //# sourceMappingURL=fetcher.d.ts.map
@@ -0,0 +1,130 @@
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.fetchClusterData = fetchClusterData;
37
+ const child_process_1 = require("child_process");
38
+ const fs = __importStar(require("fs"));
39
+ const path = __importStar(require("path"));
40
+ // ─────────────────────────────────────────────────────────────────────────────
41
+ // CONSTANTS
42
+ // ─────────────────────────────────────────────────────────────────────────────
43
+ const EMPTY_LIST = { apiVersion: 'v1', kind: 'List', items: [] };
44
+ const KUBECTL_TIMEOUT_MS = 30000;
45
+ // ─────────────────────────────────────────────────────────────────────────────
46
+ // HELPERS
47
+ // ─────────────────────────────────────────────────────────────────────────────
48
+ /**
49
+ * Executes a single kubectl command and returns the parsed JSON.
50
+ * On failure, logs a warning and returns an empty list — never crashes.
51
+ */
52
+ function runKubectl(command, resourceName) {
53
+ try {
54
+ const raw = (0, child_process_1.execSync)(command, {
55
+ encoding: 'utf8',
56
+ timeout: KUBECTL_TIMEOUT_MS,
57
+ // Pipe all three streams so kubectl's stderr never reaches the terminal.
58
+ // stdout is returned as a string; stderr is captured silently.
59
+ stdio: ['pipe', 'pipe', 'pipe'],
60
+ });
61
+ return JSON.parse(raw);
62
+ }
63
+ catch (err) {
64
+ // Extract the first meaningful line from stderr if available, otherwise
65
+ // fall back to the generic Error message.
66
+ let msg = 'unknown error';
67
+ if (err instanceof Error) {
68
+ const detail = err.stderr;
69
+ const firstLine = (detail ?? err.message)
70
+ .split('\n')
71
+ .map((l) => l.trim())
72
+ .find((l) => l.length > 0 && !l.startsWith('E0') && !l.startsWith('W0'));
73
+ msg = firstLine ?? err.message.split('\n')[0];
74
+ }
75
+ console.warn(` ⚠️ Failed to fetch ${resourceName}: ${msg}`);
76
+ return { ...EMPTY_LIST, kind: `${resourceName}List` };
77
+ }
78
+ }
79
+ /** Resolves the path to the mock data file, supporting both ts-node and compiled dist. */
80
+ function resolveMockPath() {
81
+ // When running via ts-node: __dirname = src/cli or src/core
82
+ // When running compiled: __dirname = dist/cli or dist/core
83
+ const candidates = [
84
+ path.resolve(__dirname, '../data/mock-cluster-graph.json'), // ts-node from src/core
85
+ path.resolve(__dirname, '../../src/data/mock-cluster-graph.json'), // compiled from dist/core
86
+ path.resolve(process.cwd(), 'src/data/mock-cluster-graph.json'),
87
+ path.resolve(process.cwd(), 'data/mock-cluster-graph.json'),
88
+ ];
89
+ for (const p of candidates) {
90
+ if (fs.existsSync(p))
91
+ return p;
92
+ }
93
+ throw new Error(`Mock data file not found. Tried:\n${candidates.map((c) => ` • ${c}`).join('\n')}`);
94
+ }
95
+ // ─────────────────────────────────────────────────────────────────────────────
96
+ // PUBLIC API
97
+ // ─────────────────────────────────────────────────────────────────────────────
98
+ /**
99
+ * Fetches raw Kubernetes cluster data.
100
+ *
101
+ * @param mockMode When true, loads data from the bundled mock JSON file
102
+ * and skips all kubectl calls entirely.
103
+ */
104
+ async function fetchClusterData(mockMode = false) {
105
+ if (mockMode) {
106
+ const filePath = resolveMockPath();
107
+ console.log(` → Loading mock cluster data from: ${filePath}`);
108
+ const raw = fs.readFileSync(filePath, 'utf8');
109
+ return JSON.parse(raw);
110
+ }
111
+ // ── Live kubectl mode ──────────────────────────────────────────────────────
112
+ const resources = [
113
+ ['pods', 'kubectl get pods -A -o json', 'pods'],
114
+ ['serviceAccounts', 'kubectl get serviceaccounts -A -o json', 'serviceAccounts'],
115
+ ['roles', 'kubectl get roles -A -o json', 'roles'],
116
+ ['clusterRoles', 'kubectl get clusterroles -A -o json', 'clusterRoles'],
117
+ ['roleBindings', 'kubectl get rolebindings -A -o json', 'roleBindings'],
118
+ ['clusterRoleBindings', 'kubectl get clusterrolebindings -A -o json', 'clusterRoleBindings'],
119
+ ['secrets', 'kubectl get secrets -A -o json', 'secrets'],
120
+ ['configMaps', 'kubectl get configmaps -A -o json', 'configMaps'],
121
+ ['services', 'kubectl get services -A -o json', 'services'],
122
+ ];
123
+ const result = {};
124
+ for (const [key, command, label] of resources) {
125
+ console.log(` → Fetching ${label}...`);
126
+ result[key] = runKubectl(command, label);
127
+ }
128
+ return result;
129
+ }
130
+ //# sourceMappingURL=fetcher.js.map
@@ -0,0 +1,290 @@
1
+ import { z } from 'zod';
2
+ export declare const NodeTypeEnum: z.ZodEnum<["Pod", "ServiceAccount", "Role", "ClusterRole", "Secret", "ConfigMap", "Database", "Service"]>;
3
+ export declare const EdgeTypeEnum: z.ZodEnum<["USES_SERVICE_ACCOUNT", "BINDS_TO", "HAS_ACCESS", "EXPOSES", "MOUNTS_SECRET", "READS_CONFIGMAP", "CAN_EXEC_INTO"]>;
4
+ export declare const NodeSchema: z.ZodObject<{
5
+ /** Unique identifier: "namespace:name" */
6
+ id: z.ZodString;
7
+ type: z.ZodEnum<["Pod", "ServiceAccount", "Role", "ClusterRole", "Secret", "ConfigMap", "Database", "Service"]>;
8
+ name: z.ZodString;
9
+ /** "cluster" for cluster-scoped resources */
10
+ namespace: z.ZodString;
11
+ /** 0–10; higher = more dangerous */
12
+ riskScore: z.ZodNumber;
13
+ /** True if directly reachable from outside the cluster */
14
+ isEntryPoint: z.ZodBoolean;
15
+ /** True if compromising this node is critical (secrets, prod DBs) */
16
+ isCrownJewel: z.ZodBoolean;
17
+ /** CVE identifiers discovered via NVD enrichment */
18
+ cve: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
19
+ /** Container image (Pod nodes only) */
20
+ image: z.ZodOptional<z.ZodString>;
21
+ labels: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
22
+ annotations: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
23
+ }, "strip", z.ZodTypeAny, {
24
+ id: string;
25
+ type: "Pod" | "ServiceAccount" | "Role" | "ClusterRole" | "Secret" | "ConfigMap" | "Database" | "Service";
26
+ name: string;
27
+ namespace: string;
28
+ riskScore: number;
29
+ isEntryPoint: boolean;
30
+ isCrownJewel: boolean;
31
+ cve?: string[] | undefined;
32
+ image?: string | undefined;
33
+ labels?: Record<string, string> | undefined;
34
+ annotations?: Record<string, string> | undefined;
35
+ }, {
36
+ id: string;
37
+ type: "Pod" | "ServiceAccount" | "Role" | "ClusterRole" | "Secret" | "ConfigMap" | "Database" | "Service";
38
+ name: string;
39
+ namespace: string;
40
+ riskScore: number;
41
+ isEntryPoint: boolean;
42
+ isCrownJewel: boolean;
43
+ cve?: string[] | undefined;
44
+ image?: string | undefined;
45
+ labels?: Record<string, string> | undefined;
46
+ annotations?: Record<string, string> | undefined;
47
+ }>;
48
+ export declare const EdgeSchema: z.ZodObject<{
49
+ from: z.ZodString;
50
+ to: z.ZodString;
51
+ type: z.ZodEnum<["USES_SERVICE_ACCOUNT", "BINDS_TO", "HAS_ACCESS", "EXPOSES", "MOUNTS_SECRET", "READS_CONFIGMAP", "CAN_EXEC_INTO"]>;
52
+ /** Privilege severity: 0 = read-only, 10 = full admin */
53
+ weight: z.ZodNumber;
54
+ /** RBAC verbs that grant this access */
55
+ verbs: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
56
+ /** Kubernetes resource types involved */
57
+ resources: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
58
+ }, "strip", z.ZodTypeAny, {
59
+ type: "USES_SERVICE_ACCOUNT" | "BINDS_TO" | "HAS_ACCESS" | "EXPOSES" | "MOUNTS_SECRET" | "READS_CONFIGMAP" | "CAN_EXEC_INTO";
60
+ from: string;
61
+ to: string;
62
+ weight: number;
63
+ verbs?: string[] | undefined;
64
+ resources?: string[] | undefined;
65
+ }, {
66
+ type: "USES_SERVICE_ACCOUNT" | "BINDS_TO" | "HAS_ACCESS" | "EXPOSES" | "MOUNTS_SECRET" | "READS_CONFIGMAP" | "CAN_EXEC_INTO";
67
+ from: string;
68
+ to: string;
69
+ weight: number;
70
+ verbs?: string[] | undefined;
71
+ resources?: string[] | undefined;
72
+ }>;
73
+ export declare const AttackPathSchema: z.ZodObject<{
74
+ /** Ordered list of node IDs from entry point to crown jewel */
75
+ path: z.ZodArray<z.ZodString, "many">;
76
+ /** Average risk score across path nodes */
77
+ riskScore: z.ZodNumber;
78
+ entryPoint: z.ZodString;
79
+ crownJewel: z.ZodString;
80
+ /** Number of hops (edges) in the path */
81
+ hops: z.ZodNumber;
82
+ }, "strip", z.ZodTypeAny, {
83
+ riskScore: number;
84
+ path: string[];
85
+ entryPoint: string;
86
+ crownJewel: string;
87
+ hops: number;
88
+ }, {
89
+ riskScore: number;
90
+ path: string[];
91
+ entryPoint: string;
92
+ crownJewel: string;
93
+ hops: number;
94
+ }>;
95
+ export declare const GraphSchema: z.ZodObject<{
96
+ nodes: z.ZodArray<z.ZodObject<{
97
+ /** Unique identifier: "namespace:name" */
98
+ id: z.ZodString;
99
+ type: z.ZodEnum<["Pod", "ServiceAccount", "Role", "ClusterRole", "Secret", "ConfigMap", "Database", "Service"]>;
100
+ name: z.ZodString;
101
+ /** "cluster" for cluster-scoped resources */
102
+ namespace: z.ZodString;
103
+ /** 0–10; higher = more dangerous */
104
+ riskScore: z.ZodNumber;
105
+ /** True if directly reachable from outside the cluster */
106
+ isEntryPoint: z.ZodBoolean;
107
+ /** True if compromising this node is critical (secrets, prod DBs) */
108
+ isCrownJewel: z.ZodBoolean;
109
+ /** CVE identifiers discovered via NVD enrichment */
110
+ cve: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
111
+ /** Container image (Pod nodes only) */
112
+ image: z.ZodOptional<z.ZodString>;
113
+ labels: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
114
+ annotations: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
115
+ }, "strip", z.ZodTypeAny, {
116
+ id: string;
117
+ type: "Pod" | "ServiceAccount" | "Role" | "ClusterRole" | "Secret" | "ConfigMap" | "Database" | "Service";
118
+ name: string;
119
+ namespace: string;
120
+ riskScore: number;
121
+ isEntryPoint: boolean;
122
+ isCrownJewel: boolean;
123
+ cve?: string[] | undefined;
124
+ image?: string | undefined;
125
+ labels?: Record<string, string> | undefined;
126
+ annotations?: Record<string, string> | undefined;
127
+ }, {
128
+ id: string;
129
+ type: "Pod" | "ServiceAccount" | "Role" | "ClusterRole" | "Secret" | "ConfigMap" | "Database" | "Service";
130
+ name: string;
131
+ namespace: string;
132
+ riskScore: number;
133
+ isEntryPoint: boolean;
134
+ isCrownJewel: boolean;
135
+ cve?: string[] | undefined;
136
+ image?: string | undefined;
137
+ labels?: Record<string, string> | undefined;
138
+ annotations?: Record<string, string> | undefined;
139
+ }>, "many">;
140
+ edges: z.ZodArray<z.ZodObject<{
141
+ from: z.ZodString;
142
+ to: z.ZodString;
143
+ type: z.ZodEnum<["USES_SERVICE_ACCOUNT", "BINDS_TO", "HAS_ACCESS", "EXPOSES", "MOUNTS_SECRET", "READS_CONFIGMAP", "CAN_EXEC_INTO"]>;
144
+ /** Privilege severity: 0 = read-only, 10 = full admin */
145
+ weight: z.ZodNumber;
146
+ /** RBAC verbs that grant this access */
147
+ verbs: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
148
+ /** Kubernetes resource types involved */
149
+ resources: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
150
+ }, "strip", z.ZodTypeAny, {
151
+ type: "USES_SERVICE_ACCOUNT" | "BINDS_TO" | "HAS_ACCESS" | "EXPOSES" | "MOUNTS_SECRET" | "READS_CONFIGMAP" | "CAN_EXEC_INTO";
152
+ from: string;
153
+ to: string;
154
+ weight: number;
155
+ verbs?: string[] | undefined;
156
+ resources?: string[] | undefined;
157
+ }, {
158
+ type: "USES_SERVICE_ACCOUNT" | "BINDS_TO" | "HAS_ACCESS" | "EXPOSES" | "MOUNTS_SECRET" | "READS_CONFIGMAP" | "CAN_EXEC_INTO";
159
+ from: string;
160
+ to: string;
161
+ weight: number;
162
+ verbs?: string[] | undefined;
163
+ resources?: string[] | undefined;
164
+ }>, "many">;
165
+ attackPaths: z.ZodOptional<z.ZodArray<z.ZodObject<{
166
+ /** Ordered list of node IDs from entry point to crown jewel */
167
+ path: z.ZodArray<z.ZodString, "many">;
168
+ /** Average risk score across path nodes */
169
+ riskScore: z.ZodNumber;
170
+ entryPoint: z.ZodString;
171
+ crownJewel: z.ZodString;
172
+ /** Number of hops (edges) in the path */
173
+ hops: z.ZodNumber;
174
+ }, "strip", z.ZodTypeAny, {
175
+ riskScore: number;
176
+ path: string[];
177
+ entryPoint: string;
178
+ crownJewel: string;
179
+ hops: number;
180
+ }, {
181
+ riskScore: number;
182
+ path: string[];
183
+ entryPoint: string;
184
+ crownJewel: string;
185
+ hops: number;
186
+ }>, "many">>;
187
+ metadata: z.ZodOptional<z.ZodObject<{
188
+ generatedAt: z.ZodString;
189
+ clusterContext: z.ZodOptional<z.ZodString>;
190
+ totalNodes: z.ZodNumber;
191
+ totalEdges: z.ZodNumber;
192
+ totalAttackPaths: z.ZodOptional<z.ZodNumber>;
193
+ }, "strip", z.ZodTypeAny, {
194
+ generatedAt: string;
195
+ totalNodes: number;
196
+ totalEdges: number;
197
+ clusterContext?: string | undefined;
198
+ totalAttackPaths?: number | undefined;
199
+ }, {
200
+ generatedAt: string;
201
+ totalNodes: number;
202
+ totalEdges: number;
203
+ clusterContext?: string | undefined;
204
+ totalAttackPaths?: number | undefined;
205
+ }>>;
206
+ }, "strip", z.ZodTypeAny, {
207
+ nodes: {
208
+ id: string;
209
+ type: "Pod" | "ServiceAccount" | "Role" | "ClusterRole" | "Secret" | "ConfigMap" | "Database" | "Service";
210
+ name: string;
211
+ namespace: string;
212
+ riskScore: number;
213
+ isEntryPoint: boolean;
214
+ isCrownJewel: boolean;
215
+ cve?: string[] | undefined;
216
+ image?: string | undefined;
217
+ labels?: Record<string, string> | undefined;
218
+ annotations?: Record<string, string> | undefined;
219
+ }[];
220
+ edges: {
221
+ type: "USES_SERVICE_ACCOUNT" | "BINDS_TO" | "HAS_ACCESS" | "EXPOSES" | "MOUNTS_SECRET" | "READS_CONFIGMAP" | "CAN_EXEC_INTO";
222
+ from: string;
223
+ to: string;
224
+ weight: number;
225
+ verbs?: string[] | undefined;
226
+ resources?: string[] | undefined;
227
+ }[];
228
+ attackPaths?: {
229
+ riskScore: number;
230
+ path: string[];
231
+ entryPoint: string;
232
+ crownJewel: string;
233
+ hops: number;
234
+ }[] | undefined;
235
+ metadata?: {
236
+ generatedAt: string;
237
+ totalNodes: number;
238
+ totalEdges: number;
239
+ clusterContext?: string | undefined;
240
+ totalAttackPaths?: number | undefined;
241
+ } | undefined;
242
+ }, {
243
+ nodes: {
244
+ id: string;
245
+ type: "Pod" | "ServiceAccount" | "Role" | "ClusterRole" | "Secret" | "ConfigMap" | "Database" | "Service";
246
+ name: string;
247
+ namespace: string;
248
+ riskScore: number;
249
+ isEntryPoint: boolean;
250
+ isCrownJewel: boolean;
251
+ cve?: string[] | undefined;
252
+ image?: string | undefined;
253
+ labels?: Record<string, string> | undefined;
254
+ annotations?: Record<string, string> | undefined;
255
+ }[];
256
+ edges: {
257
+ type: "USES_SERVICE_ACCOUNT" | "BINDS_TO" | "HAS_ACCESS" | "EXPOSES" | "MOUNTS_SECRET" | "READS_CONFIGMAP" | "CAN_EXEC_INTO";
258
+ from: string;
259
+ to: string;
260
+ weight: number;
261
+ verbs?: string[] | undefined;
262
+ resources?: string[] | undefined;
263
+ }[];
264
+ attackPaths?: {
265
+ riskScore: number;
266
+ path: string[];
267
+ entryPoint: string;
268
+ crownJewel: string;
269
+ hops: number;
270
+ }[] | undefined;
271
+ metadata?: {
272
+ generatedAt: string;
273
+ totalNodes: number;
274
+ totalEdges: number;
275
+ clusterContext?: string | undefined;
276
+ totalAttackPaths?: number | undefined;
277
+ } | undefined;
278
+ }>;
279
+ export type NodeType = z.infer<typeof NodeTypeEnum>;
280
+ export type EdgeType = z.infer<typeof EdgeTypeEnum>;
281
+ export type Node = z.infer<typeof NodeSchema>;
282
+ export type Edge = z.infer<typeof EdgeSchema>;
283
+ export type AttackPath = z.infer<typeof AttackPathSchema>;
284
+ export type Graph = z.infer<typeof GraphSchema>;
285
+ /**
286
+ * Validates a graph object against the Zod schema.
287
+ * Prints a summary and throws a readable error on failure.
288
+ */
289
+ export declare function validateGraph(graph: unknown): Graph;
290
+ //# sourceMappingURL=schema.d.ts.map
@@ -0,0 +1,125 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.GraphSchema = exports.AttackPathSchema = exports.EdgeSchema = exports.NodeSchema = exports.EdgeTypeEnum = exports.NodeTypeEnum = void 0;
4
+ exports.validateGraph = validateGraph;
5
+ const zod_1 = require("zod");
6
+ // ─────────────────────────────────────────────────────────────────────────────
7
+ // ENUMS
8
+ // ─────────────────────────────────────────────────────────────────────────────
9
+ exports.NodeTypeEnum = zod_1.z.enum([
10
+ 'Pod',
11
+ 'ServiceAccount',
12
+ 'Role',
13
+ 'ClusterRole',
14
+ 'Secret',
15
+ 'ConfigMap',
16
+ 'Database',
17
+ 'Service',
18
+ ]);
19
+ exports.EdgeTypeEnum = zod_1.z.enum([
20
+ 'USES_SERVICE_ACCOUNT',
21
+ 'BINDS_TO',
22
+ 'HAS_ACCESS',
23
+ 'EXPOSES',
24
+ 'MOUNTS_SECRET',
25
+ 'READS_CONFIGMAP',
26
+ 'CAN_EXEC_INTO',
27
+ ]);
28
+ // ─────────────────────────────────────────────────────────────────────────────
29
+ // NODE SCHEMA
30
+ // ─────────────────────────────────────────────────────────────────────────────
31
+ exports.NodeSchema = zod_1.z.object({
32
+ /** Unique identifier: "namespace:name" */
33
+ id: zod_1.z.string().min(1, 'Node id must not be empty'),
34
+ type: exports.NodeTypeEnum,
35
+ name: zod_1.z.string().min(1, 'Node name must not be empty'),
36
+ /** "cluster" for cluster-scoped resources */
37
+ namespace: zod_1.z.string(),
38
+ /** 0–10; higher = more dangerous */
39
+ riskScore: zod_1.z.number().min(0).max(10),
40
+ /** True if directly reachable from outside the cluster */
41
+ isEntryPoint: zod_1.z.boolean(),
42
+ /** True if compromising this node is critical (secrets, prod DBs) */
43
+ isCrownJewel: zod_1.z.boolean(),
44
+ /** CVE identifiers discovered via NVD enrichment */
45
+ cve: zod_1.z.array(zod_1.z.string()).optional(),
46
+ /** Container image (Pod nodes only) */
47
+ image: zod_1.z.string().optional(),
48
+ labels: zod_1.z.record(zod_1.z.string()).optional(),
49
+ annotations: zod_1.z.record(zod_1.z.string()).optional(),
50
+ });
51
+ // ─────────────────────────────────────────────────────────────────────────────
52
+ // EDGE SCHEMA
53
+ // ─────────────────────────────────────────────────────────────────────────────
54
+ exports.EdgeSchema = zod_1.z.object({
55
+ from: zod_1.z.string().min(1),
56
+ to: zod_1.z.string().min(1),
57
+ type: exports.EdgeTypeEnum,
58
+ /** Privilege severity: 0 = read-only, 10 = full admin */
59
+ weight: zod_1.z.number().min(0).max(10),
60
+ /** RBAC verbs that grant this access */
61
+ verbs: zod_1.z.array(zod_1.z.string()).optional(),
62
+ /** Kubernetes resource types involved */
63
+ resources: zod_1.z.array(zod_1.z.string()).optional(),
64
+ });
65
+ // ─────────────────────────────────────────────────────────────────────────────
66
+ // ATTACK PATH SCHEMA
67
+ // ─────────────────────────────────────────────────────────────────────────────
68
+ exports.AttackPathSchema = zod_1.z.object({
69
+ /** Ordered list of node IDs from entry point to crown jewel */
70
+ path: zod_1.z.array(zod_1.z.string()),
71
+ /** Average risk score across path nodes */
72
+ riskScore: zod_1.z.number().min(0).max(10),
73
+ entryPoint: zod_1.z.string(),
74
+ crownJewel: zod_1.z.string(),
75
+ /** Number of hops (edges) in the path */
76
+ hops: zod_1.z.number().int().nonnegative(),
77
+ });
78
+ // ─────────────────────────────────────────────────────────────────────────────
79
+ // GRAPH SCHEMA
80
+ // ─────────────────────────────────────────────────────────────────────────────
81
+ exports.GraphSchema = zod_1.z.object({
82
+ nodes: zod_1.z.array(exports.NodeSchema).min(1, 'Graph must contain at least one node'),
83
+ edges: zod_1.z.array(exports.EdgeSchema),
84
+ attackPaths: zod_1.z.array(exports.AttackPathSchema).optional(),
85
+ metadata: zod_1.z
86
+ .object({
87
+ generatedAt: zod_1.z.string(),
88
+ clusterContext: zod_1.z.string().optional(),
89
+ totalNodes: zod_1.z.number().int().nonnegative(),
90
+ totalEdges: zod_1.z.number().int().nonnegative(),
91
+ totalAttackPaths: zod_1.z.number().int().nonnegative().optional(),
92
+ })
93
+ .optional(),
94
+ });
95
+ // ─────────────────────────────────────────────────────────────────────────────
96
+ // VALIDATION HELPER
97
+ // ─────────────────────────────────────────────────────────────────────────────
98
+ /**
99
+ * Validates a graph object against the Zod schema.
100
+ * Prints a summary and throws a readable error on failure.
101
+ */
102
+ function validateGraph(graph) {
103
+ const result = exports.GraphSchema.safeParse(graph);
104
+ if (!result.success) {
105
+ const errors = result.error.errors
106
+ .map((e) => ` • ${e.path.join('.')}: ${e.message}`)
107
+ .join('\n');
108
+ throw new Error(`Schema validation failed:\n${errors}`);
109
+ }
110
+ const g = result.data;
111
+ const crownJewelCount = g.nodes.filter((n) => n.isCrownJewel).length;
112
+ const entryPointCount = g.nodes.filter((n) => n.isEntryPoint).length;
113
+ const cveEnriched = g.nodes.filter((n) => (n.cve?.length ?? 0) > 0).length;
114
+ console.log(' ✔ Graph validated successfully');
115
+ console.log(` ✔ Node count : ${g.nodes.length}`);
116
+ console.log(` ✔ Edge count : ${g.edges.length}`);
117
+ console.log(` ✔ Entry points : ${entryPointCount}`);
118
+ console.log(` ✔ Crown jewels : ${crownJewelCount}`);
119
+ console.log(` ✔ CVE-enriched pods : ${cveEnriched}`);
120
+ if (g.attackPaths) {
121
+ console.log(` ✔ Attack paths : ${g.attackPaths.length}`);
122
+ }
123
+ return g;
124
+ }
125
+ //# sourceMappingURL=schema.js.map
@@ -0,0 +1,19 @@
1
+ import { Graph } from './schema';
2
+ import { RawClusterData } from './fetcher';
3
+ /**
4
+ * Converts raw Kubernetes resource lists into a directed attack graph.
5
+ *
6
+ * Node creation order:
7
+ * 1. Pods → 2. ServiceAccounts → 3. Roles → 4. ClusterRoles
8
+ * 5. Secrets → 6. ConfigMaps → 7. Services (entry points) → 8. Mock DBs
9
+ *
10
+ * Edge creation order:
11
+ * 1. Pod → ServiceAccount (USES_SERVICE_ACCOUNT)
12
+ * 2. Service → Pod (EXPOSES)
13
+ * 3. ServiceAccount → Role/ClusterRole (BINDS_TO via RoleBindings)
14
+ * 4. ServiceAccount → ClusterRole (BINDS_TO via ClusterRoleBindings)
15
+ * 5. Role → Resources (HAS_ACCESS via rules)
16
+ * 6. ClusterRole → Resources (HAS_ACCESS via rules, cross-namespace)
17
+ */
18
+ export declare function transformToGraph(raw: RawClusterData): Graph;
19
+ //# sourceMappingURL=transformer.d.ts.map