@grc-claw/security-graph 0.8.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.
@@ -0,0 +1,111 @@
1
+ export type NodeType = 'agent' | 'tool' | 'control' | 'evidence' | 'alert' | 'identity' | 'infrastructure' | 'framework' | 'policy' | 'tenant';
2
+ export type EdgeRelationship = 'invoked' | 'certified_by' | 'violated' | 'produced' | 'mitigates' | 'depends_on' | 'owns' | 'detected' | 'remediates' | 'authenticated_by' | 'scoped_to';
3
+ export type EdgeResult = 'pass' | 'fail' | 'blocked' | 'pending' | 'unknown';
4
+ export interface SecurityNode {
5
+ id: string;
6
+ type: NodeType;
7
+ name: string;
8
+ riskScore: number;
9
+ properties: Record<string, unknown>;
10
+ lastSeen: string;
11
+ firstSeen: string;
12
+ tags: string[];
13
+ }
14
+ export interface SecurityEdge {
15
+ id: string;
16
+ source: string;
17
+ target: string;
18
+ relationship: EdgeRelationship;
19
+ metadata: {
20
+ timestamp: string;
21
+ sessionId?: string;
22
+ result: EdgeResult;
23
+ confidence: number;
24
+ details?: string;
25
+ };
26
+ }
27
+ export interface AttackPathSegment {
28
+ node: SecurityNode;
29
+ edge: SecurityEdge;
30
+ riskContribution: number;
31
+ }
32
+ export interface AttackPath {
33
+ id: string;
34
+ segments: AttackPathSegment[];
35
+ totalRisk: number;
36
+ startNode: string;
37
+ endNode: string;
38
+ discoveredAt: string;
39
+ }
40
+ export interface RiskAssessment {
41
+ agentDid: string;
42
+ overallRisk: number;
43
+ riskFactors: {
44
+ factor: string;
45
+ score: number;
46
+ weight: number;
47
+ details: string;
48
+ }[];
49
+ recommendedActions: string[];
50
+ assessedAt: string;
51
+ }
52
+ export interface BlastRadius {
53
+ controlId: string;
54
+ affectedNodes: SecurityNode[];
55
+ affectedEdges: SecurityEdge[];
56
+ impactScore: number;
57
+ cascadeDepth: number;
58
+ assessedAt: string;
59
+ }
60
+ export interface CompliancePosture {
61
+ tenantId: string;
62
+ framework: string;
63
+ overallScore: number;
64
+ controlScores: {
65
+ controlId: string;
66
+ score: number;
67
+ status: 'pass' | 'fail' | 'partial';
68
+ }[];
69
+ trend: {
70
+ date: string;
71
+ score: number;
72
+ }[];
73
+ lastEvaluated: string;
74
+ }
75
+ export declare class SecurityGraph {
76
+ private nodes;
77
+ private edges;
78
+ private adjacency;
79
+ private reverseAdjacency;
80
+ private postureCache;
81
+ addNode(node: Omit<SecurityNode, 'firstSeen' | 'lastSeen'>): SecurityNode;
82
+ getNode(id: string): SecurityNode | undefined;
83
+ getNodesByType(type: NodeType): SecurityNode[];
84
+ addEdge(edge: Omit<SecurityEdge, 'id'>): SecurityEdge;
85
+ getEdgesFrom(nodeId: string): SecurityEdge[];
86
+ getEdgesTo(nodeId: string): SecurityEdge[];
87
+ /** Trace attack paths from a compromised node using BFS */
88
+ traceAttackPaths(startNodeId: string, maxDepth?: number): AttackPath[];
89
+ /** Calculate real-time risk score for an agent */
90
+ assessAgentRisk(agentDid: string): RiskAssessment;
91
+ /** Calculate the blast radius if a control fails */
92
+ calculateBlastRadius(controlId: string, maxDepth?: number): BlastRadius;
93
+ /** Calculate real-time compliance posture score */
94
+ calculateCompliancePosture(tenantId: string, framework: string): CompliancePosture;
95
+ /** Find all agents with access to specific tool types */
96
+ queryAgentsByToolAccess(toolName: string): SecurityNode[];
97
+ /** Find all uncertified agents accessing sensitive tools */
98
+ findUncertifiedAccess(framework: string): {
99
+ agent: SecurityNode;
100
+ tool: SecurityNode;
101
+ edge: SecurityEdge;
102
+ }[];
103
+ getStats(): {
104
+ totalNodes: number;
105
+ totalEdges: number;
106
+ nodesByType: Record<string, number>;
107
+ avgRiskScore: number;
108
+ highRiskNodes: number;
109
+ };
110
+ }
111
+ export { SecurityGraphSeeder } from './seeder.js';
package/dist/index.js ADDED
@@ -0,0 +1,289 @@
1
+ /**
2
+ * @grc-claw/security-graph
3
+ * Real-time Security Graph with attack path analysis and risk scoring
4
+ *
5
+ * Connects Agents, Tools, Policies, Evidence, Alerts, Controls, and
6
+ * Infrastructure into a unified graph. Enables attack path tracing,
7
+ * blast radius analysis, and continuous risk scoring.
8
+ */
9
+ import * as crypto from 'crypto';
10
+ // ─── Security Graph Engine ───────────────────────────────────────────
11
+ export class SecurityGraph {
12
+ nodes = new Map();
13
+ edges = new Map();
14
+ adjacency = new Map();
15
+ reverseAdjacency = new Map();
16
+ postureCache = new Map();
17
+ // ── Node Operations ──
18
+ addNode(node) {
19
+ const now = new Date().toISOString();
20
+ const existing = this.nodes.get(node.id);
21
+ const fullNode = {
22
+ ...node,
23
+ firstSeen: existing?.firstSeen ?? now,
24
+ lastSeen: now,
25
+ };
26
+ this.nodes.set(node.id, fullNode);
27
+ if (!this.adjacency.has(node.id))
28
+ this.adjacency.set(node.id, new Set());
29
+ if (!this.reverseAdjacency.has(node.id))
30
+ this.reverseAdjacency.set(node.id, new Set());
31
+ return fullNode;
32
+ }
33
+ getNode(id) {
34
+ return this.nodes.get(id);
35
+ }
36
+ getNodesByType(type) {
37
+ return Array.from(this.nodes.values()).filter((n) => n.type === type);
38
+ }
39
+ // ── Edge Operations ──
40
+ addEdge(edge) {
41
+ const edgeId = `edge_${crypto.randomUUID().substring(0, 8)}`;
42
+ const fullEdge = { ...edge, id: edgeId };
43
+ this.edges.set(edgeId, fullEdge);
44
+ // Update adjacency
45
+ const fwd = this.adjacency.get(edge.source);
46
+ if (fwd)
47
+ fwd.add(edgeId);
48
+ const rev = this.reverseAdjacency.get(edge.target);
49
+ if (rev)
50
+ rev.add(edgeId);
51
+ return fullEdge;
52
+ }
53
+ getEdgesFrom(nodeId) {
54
+ const edgeIds = this.adjacency.get(nodeId);
55
+ if (!edgeIds)
56
+ return [];
57
+ return Array.from(edgeIds).map((id) => this.edges.get(id)).filter(Boolean);
58
+ }
59
+ getEdgesTo(nodeId) {
60
+ const edgeIds = this.reverseAdjacency.get(nodeId);
61
+ if (!edgeIds)
62
+ return [];
63
+ return Array.from(edgeIds).map((id) => this.edges.get(id)).filter(Boolean);
64
+ }
65
+ // ── Attack Path Analysis ──
66
+ /** Trace attack paths from a compromised node using BFS */
67
+ traceAttackPaths(startNodeId, maxDepth = 5) {
68
+ const startNode = this.nodes.get(startNodeId);
69
+ if (!startNode)
70
+ return [];
71
+ const paths = [];
72
+ const queue = [];
73
+ const visited = new Set();
74
+ queue.push({ nodeId: startNodeId, path: [], depth: 0 });
75
+ while (queue.length > 0) {
76
+ const current = queue.shift();
77
+ if (current.depth >= maxDepth)
78
+ continue;
79
+ if (visited.has(current.nodeId))
80
+ continue;
81
+ visited.add(current.nodeId);
82
+ const outEdges = this.getEdgesFrom(current.nodeId);
83
+ for (const edge of outEdges) {
84
+ const targetNode = this.nodes.get(edge.target);
85
+ if (!targetNode)
86
+ continue;
87
+ const segment = {
88
+ node: targetNode,
89
+ edge,
90
+ riskContribution: targetNode.riskScore * (1 / (current.depth + 1)),
91
+ };
92
+ const newPath = [...current.path, segment];
93
+ // Record path if it reaches a high-value target
94
+ if (targetNode.type === 'control' || targetNode.type === 'evidence' || targetNode.type === 'infrastructure') {
95
+ const totalRisk = newPath.reduce((sum, s) => sum + s.riskContribution, 0);
96
+ paths.push({
97
+ id: `attack_path_${crypto.randomUUID().substring(0, 8)}`,
98
+ segments: newPath,
99
+ totalRisk: Math.min(100, totalRisk),
100
+ startNode: startNodeId,
101
+ endNode: edge.target,
102
+ discoveredAt: new Date().toISOString(),
103
+ });
104
+ }
105
+ queue.push({ nodeId: edge.target, path: newPath, depth: current.depth + 1 });
106
+ }
107
+ }
108
+ return paths.sort((a, b) => b.totalRisk - a.totalRisk);
109
+ }
110
+ // ── Risk Assessment ──
111
+ /** Calculate real-time risk score for an agent */
112
+ assessAgentRisk(agentDid) {
113
+ const agentNode = this.nodes.get(agentDid);
114
+ const riskFactors = [];
115
+ // Factor 1: Direct violations
116
+ const violations = this.getEdgesFrom(agentDid).filter((e) => e.relationship === 'violated');
117
+ const violationScore = Math.min(40, violations.length * 10);
118
+ riskFactors.push({
119
+ factor: 'policy_violations',
120
+ score: violationScore,
121
+ weight: 0.35,
122
+ details: `${violations.length} policy violations detected`,
123
+ });
124
+ // Factor 2: Tool invocation pattern
125
+ const invocations = this.getEdgesFrom(agentDid).filter((e) => e.relationship === 'invoked');
126
+ const destructiveInvocations = invocations.filter((e) => e.metadata.details?.includes('destructive'));
127
+ const invocationRisk = Math.min(30, destructiveInvocations.length * 15);
128
+ riskFactors.push({
129
+ factor: 'destructive_tool_usage',
130
+ score: invocationRisk,
131
+ weight: 0.25,
132
+ details: `${destructiveInvocations.length} destructive tool invocations`,
133
+ });
134
+ // Factor 3: Missing certifications
135
+ const certEdges = this.getEdgesFrom(agentDid).filter((e) => e.relationship === 'certified_by');
136
+ const certScore = certEdges.length >= 3 ? 0 : (3 - certEdges.length) * 10;
137
+ riskFactors.push({
138
+ factor: 'certification_gaps',
139
+ score: certScore,
140
+ weight: 0.2,
141
+ details: `${certEdges.length} active certifications`,
142
+ });
143
+ // Factor 4: Attack path exposure
144
+ const attackPaths = this.traceAttackPaths(agentDid, 3);
145
+ const exposureScore = Math.min(30, attackPaths.length * 5);
146
+ riskFactors.push({
147
+ factor: 'attack_path_exposure',
148
+ score: exposureScore,
149
+ weight: 0.2,
150
+ details: `${attackPaths.length} potential attack paths`,
151
+ });
152
+ const overallRisk = riskFactors.reduce((sum, f) => sum + f.score * f.weight, 0);
153
+ const recommendedActions = [];
154
+ if (violationScore > 0)
155
+ recommendedActions.push('Review and remediate policy violations');
156
+ if (invocationRisk > 0)
157
+ recommendedActions.push('Audit destructive tool access permissions');
158
+ if (certScore > 0)
159
+ recommendedActions.push('Obtain missing framework certifications');
160
+ if (exposureScore > 0)
161
+ recommendedActions.push('Reduce blast radius by applying least-privilege controls');
162
+ return {
163
+ agentDid,
164
+ overallRisk: Math.round(overallRisk * 100) / 100,
165
+ riskFactors,
166
+ recommendedActions,
167
+ assessedAt: new Date().toISOString(),
168
+ };
169
+ }
170
+ // ── Blast Radius ──
171
+ /** Calculate the blast radius if a control fails */
172
+ calculateBlastRadius(controlId, maxDepth = 4) {
173
+ const affectedNodes = [];
174
+ const affectedEdges = [];
175
+ const visited = new Set();
176
+ const queue = [{ nodeId: controlId, depth: 0 }];
177
+ while (queue.length > 0) {
178
+ const current = queue.shift();
179
+ if (current.depth >= maxDepth || visited.has(current.nodeId))
180
+ continue;
181
+ visited.add(current.nodeId);
182
+ const node = this.nodes.get(current.nodeId);
183
+ if (node && current.nodeId !== controlId)
184
+ affectedNodes.push(node);
185
+ // Follow both forward and reverse edges
186
+ const allEdges = [...this.getEdgesFrom(current.nodeId), ...this.getEdgesTo(current.nodeId)];
187
+ for (const edge of allEdges) {
188
+ affectedEdges.push(edge);
189
+ const nextNodeId = edge.source === current.nodeId ? edge.target : edge.source;
190
+ if (!visited.has(nextNodeId)) {
191
+ queue.push({ nodeId: nextNodeId, depth: current.depth + 1 });
192
+ }
193
+ }
194
+ }
195
+ const impactScore = Math.min(100, affectedNodes.length * 5 + affectedEdges.length * 2);
196
+ return {
197
+ controlId,
198
+ affectedNodes,
199
+ affectedEdges,
200
+ impactScore,
201
+ cascadeDepth: maxDepth,
202
+ assessedAt: new Date().toISOString(),
203
+ };
204
+ }
205
+ // ── Compliance Posture ──
206
+ /** Calculate real-time compliance posture score */
207
+ calculateCompliancePosture(tenantId, framework) {
208
+ const controlNodes = this.getNodesByType('control').filter((n) => n.tags.includes(framework) && (n.properties.tenantId === tenantId || !n.properties.tenantId));
209
+ const controlScores = controlNodes.map((control) => {
210
+ const edges = this.getEdgesTo(control.id);
211
+ const passEdges = edges.filter((e) => e.metadata.result === 'pass');
212
+ const failEdges = edges.filter((e) => e.metadata.result === 'fail');
213
+ const total = passEdges.length + failEdges.length;
214
+ const score = total > 0 ? (passEdges.length / total) * 100 : 50;
215
+ const status = score >= 90 ? 'pass' : score >= 50 ? 'partial' : 'fail';
216
+ return { controlId: control.id, score: Math.round(score), status };
217
+ });
218
+ const overallScore = controlScores.length > 0
219
+ ? controlScores.reduce((sum, c) => sum + c.score, 0) / controlScores.length
220
+ : 0;
221
+ const posture = {
222
+ tenantId,
223
+ framework,
224
+ overallScore: Math.round(overallScore * 100) / 100,
225
+ controlScores,
226
+ trend: [],
227
+ lastEvaluated: new Date().toISOString(),
228
+ };
229
+ // Cache for trend analysis
230
+ const cacheKey = `${tenantId}_${framework}`;
231
+ const existing = this.postureCache.get(cacheKey);
232
+ if (existing) {
233
+ posture.trend = [...existing.trend, { date: new Date().toISOString(), score: posture.overallScore }].slice(-90);
234
+ }
235
+ else {
236
+ posture.trend = [{ date: new Date().toISOString(), score: posture.overallScore }];
237
+ }
238
+ this.postureCache.set(cacheKey, posture);
239
+ return posture;
240
+ }
241
+ // ── Graph Queries ──
242
+ /** Find all agents with access to specific tool types */
243
+ queryAgentsByToolAccess(toolName) {
244
+ const toolNode = Array.from(this.nodes.values()).find((n) => n.type === 'tool' && n.name === toolName);
245
+ if (!toolNode)
246
+ return [];
247
+ const edges = this.getEdgesTo(toolNode.id).filter((e) => e.relationship === 'invoked');
248
+ return edges
249
+ .map((e) => this.nodes.get(e.source))
250
+ .filter((n) => n !== undefined && n.type === 'agent');
251
+ }
252
+ /** Find all uncertified agents accessing sensitive tools */
253
+ findUncertifiedAccess(framework) {
254
+ const results = [];
255
+ const agents = this.getNodesByType('agent');
256
+ for (const agent of agents) {
257
+ const certEdges = this.getEdgesFrom(agent.id).filter((e) => e.relationship === 'certified_by' && e.metadata.details?.includes(framework));
258
+ if (certEdges.length === 0) {
259
+ const toolEdges = this.getEdgesFrom(agent.id).filter((e) => e.relationship === 'invoked');
260
+ for (const edge of toolEdges) {
261
+ const tool = this.nodes.get(edge.target);
262
+ if (tool && tool.type === 'tool') {
263
+ results.push({ agent, tool, edge });
264
+ }
265
+ }
266
+ }
267
+ }
268
+ return results;
269
+ }
270
+ // ── Statistics ──
271
+ getStats() {
272
+ const allNodes = Array.from(this.nodes.values());
273
+ const nodesByType = {};
274
+ for (const n of allNodes) {
275
+ nodesByType[n.type] = (nodesByType[n.type] ?? 0) + 1;
276
+ }
277
+ const avgRisk = allNodes.length > 0
278
+ ? allNodes.reduce((sum, n) => sum + n.riskScore, 0) / allNodes.length
279
+ : 0;
280
+ return {
281
+ totalNodes: this.nodes.size,
282
+ totalEdges: this.edges.size,
283
+ nodesByType,
284
+ avgRiskScore: Math.round(avgRisk * 100) / 100,
285
+ highRiskNodes: allNodes.filter((n) => n.riskScore >= 70).length,
286
+ };
287
+ }
288
+ }
289
+ export { SecurityGraphSeeder } from './seeder.js';
@@ -0,0 +1,21 @@
1
+ import type { SecurityGraph } from './index.js';
2
+ export interface FrameworkPack {
3
+ code: string;
4
+ name: string;
5
+ version: string;
6
+ controls: {
7
+ id: string;
8
+ controlCode: string;
9
+ title: string;
10
+ frameworkCode: string;
11
+ domain?: string;
12
+ }[];
13
+ }
14
+ export declare class SecurityGraphSeeder {
15
+ private graph;
16
+ constructor(graph: SecurityGraph);
17
+ seedFromFrameworks(frameworks: FrameworkPack[]): void;
18
+ private seedEdges;
19
+ seedAttackPaths(): void;
20
+ seedRiskNetwork(): void;
21
+ }
package/dist/seeder.js ADDED
@@ -0,0 +1,264 @@
1
+ const INFRASTRUCTURE_TARGETS = [
2
+ 'prod-auth-db', 'core-api-gateway', 'aws-s3-compliance', 'k8s-cluster-prod',
3
+ 'ci-runner-pool', 'vault-secrets', 'redis-session-cache', 'cdn-edge-proxy',
4
+ 'postgres-primary', 'elasticsearch-logs', 'sqs-event-queue', 'ec2-bastion-host',
5
+ ];
6
+ const AGENT_DIDS = [
7
+ 'did:grc:agent-grc-auditor', 'did:grc:agent-compliance-scanner', 'did:grc:agent-evidence-collector',
8
+ 'did:grc:agent-incident-responder', 'did:grc:agent-policy-enforcer', 'did:grc:agent-risk-analyst',
9
+ 'did:grc:agent-soar-orchestrator', 'did:grc:agent-cloud-security', 'did:grc:agent-identity-mgr',
10
+ 'did:grc:agent-vuln-scanner', 'did:grc:agent-log-analyzer', 'did:grc:agent-crypto-guard',
11
+ ];
12
+ const TOOL_IDS = [
13
+ 'tool-wazuh-siem', 'tool-suricata-ids', 'tool-elastic-stack', 'tool-snort-ips',
14
+ 'tool-aws-guardduty', 'tool-azure-sentinel', 'tool-gcp-chronicle',
15
+ 'tool-vault-secrets', 'tool-terraform-iac', 'tool-trivy-scanner',
16
+ 'tool-caldera-redteam', 'tool-openvas-vuln', 'tool-splunk-hec',
17
+ ];
18
+ const POLICY_IDS = [
19
+ 'policy-acceptable-use', 'policy-access-control', 'policy-data-classification',
20
+ 'policy-incident-response', 'policy-bcp-dr', 'policy-change-management',
21
+ 'policy-vendor-risk', 'policy-encryption-standards', 'policy-logging-monitoring',
22
+ 'policy-remote-access', 'policy-mobile-device', 'policy-physical-security',
23
+ ];
24
+ function pick(arr, i) {
25
+ return arr[i % arr.length];
26
+ }
27
+ function riskFromIndex(i, max = 100) {
28
+ return ((i * 7 + 13) % max);
29
+ }
30
+ export class SecurityGraphSeeder {
31
+ graph;
32
+ constructor(graph) {
33
+ this.graph = graph;
34
+ }
35
+ seedFromFrameworks(frameworks) {
36
+ const frameworkNodeIds = [];
37
+ for (const pack of frameworks) {
38
+ const fwId = `fw-${pack.code}`;
39
+ this.graph.addNode({
40
+ id: fwId, type: 'framework', name: pack.name,
41
+ riskScore: 5,
42
+ properties: { code: pack.code, version: pack.version, controlCount: pack.controls.length },
43
+ tags: [pack.code],
44
+ });
45
+ frameworkNodeIds.push(fwId);
46
+ for (let ci = 0; ci < pack.controls.length; ci++) {
47
+ const ctrl = pack.controls[ci];
48
+ const ctrlId = `ctrl-${ctrl.id}`;
49
+ this.graph.addNode({
50
+ id: ctrlId, type: 'control', name: ctrl.title,
51
+ riskScore: riskFromIndex(ci + frameworks.indexOf(pack) * 17),
52
+ properties: { controlCode: ctrl.controlCode, domain: ctrl.domain ?? 'unknown', frameworkCode: ctrl.frameworkCode },
53
+ tags: [pack.code, ctrl.domain ?? 'general'],
54
+ });
55
+ this.graph.addEdge({
56
+ source: fwId, target: ctrlId, relationship: 'depends_on',
57
+ metadata: { timestamp: new Date().toISOString(), result: 'pass', confidence: 0.9, details: `Framework ${pack.code} owns control ${ctrl.controlCode}` },
58
+ });
59
+ if (ci % 3 === 0) {
60
+ const evidenceId = `ev-${ctrl.id}-evidence`;
61
+ this.graph.addNode({
62
+ id: evidenceId, type: 'evidence', name: `Evidence for ${ctrl.controlCode}`,
63
+ riskScore: Math.max(0, 40 - riskFromIndex(ci)),
64
+ properties: { controlId: ctrlId, sha256: `sha256_evidence_${ctrl.id}`, status: ci % 5 === 0 ? 'pending' : 'collected' },
65
+ tags: [pack.code, 'evidence'],
66
+ });
67
+ this.graph.addEdge({
68
+ source: evidenceId, target: ctrlId, relationship: 'produced',
69
+ metadata: { timestamp: new Date().toISOString(), result: ci % 5 === 0 ? 'pending' : 'pass', confidence: 0.85, details: `Evidence produced for control ${ctrl.controlCode}` },
70
+ });
71
+ }
72
+ if (ci % 4 === 0) {
73
+ const riskId = `risk-${ctrl.id}-risk`;
74
+ this.graph.addNode({
75
+ id: riskId, type: 'alert', name: `Risk: ${ctrl.title}`,
76
+ riskScore: riskFromIndex(ci + 50),
77
+ properties: { controlId: ctrlId, severity: ci % 3 === 0 ? 'high' : 'medium', description: `Compliance risk for ${ctrl.controlCode}` },
78
+ tags: [pack.code, 'risk'],
79
+ });
80
+ this.graph.addEdge({
81
+ source: riskId, target: ctrlId, relationship: 'violated',
82
+ metadata: { timestamp: new Date().toISOString(), result: 'fail', confidence: 0.7, details: `Risk detected against control ${ctrl.controlCode}` },
83
+ });
84
+ }
85
+ }
86
+ }
87
+ for (let i = 0; i < INFRASTRUCTURE_TARGETS.length; i++) {
88
+ this.graph.addNode({
89
+ id: `infra-${INFRASTRUCTURE_TARGETS[i]}`, type: 'infrastructure', name: INFRASTRUCTURE_TARGETS[i],
90
+ riskScore: riskFromIndex(i + 100),
91
+ properties: { host: INFRASTRUCTURE_TARGETS[i], environment: 'production', region: i % 2 === 0 ? 'us-east-1' : 'eu-west-1' },
92
+ tags: ['infrastructure', 'production'],
93
+ });
94
+ }
95
+ for (let i = 0; i < AGENT_DIDS.length; i++) {
96
+ const agentId = AGENT_DIDS[i];
97
+ this.graph.addNode({
98
+ id: agentId, type: 'agent', name: agentId.split(':').pop(),
99
+ riskScore: riskFromIndex(i + 200),
100
+ properties: { did: agentId, role: i % 3 === 0 ? 'admin' : 'operator', tenantScope: ['1'] },
101
+ tags: ['agent', 'did:grc'],
102
+ });
103
+ }
104
+ for (let i = 0; i < TOOL_IDS.length; i++) {
105
+ this.graph.addNode({
106
+ id: TOOL_IDS[i], type: 'tool', name: TOOL_IDS[i],
107
+ riskScore: riskFromIndex(i + 300),
108
+ properties: { provider: i % 3 === 0 ? 'aws' : i % 3 === 1 ? 'oss' : 'azure', tier: i % 4 === 0 ? 'destructive' : 'read' },
109
+ tags: ['tool'],
110
+ });
111
+ }
112
+ for (let i = 0; i < POLICY_IDS.length; i++) {
113
+ this.graph.addNode({
114
+ id: POLICY_IDS[i], type: 'policy', name: POLICY_IDS[i],
115
+ riskScore: riskFromIndex(i + 400) % 30,
116
+ properties: { policyType: 'organizational', version: '2026.1' },
117
+ tags: ['policy'],
118
+ });
119
+ }
120
+ this.seedEdges(frameworkNodeIds);
121
+ }
122
+ seedEdges(frameworkNodeIds) {
123
+ const allControlNodes = this.graph.getNodesByType('control');
124
+ const allAgentNodes = this.graph.getNodesByType('agent');
125
+ const allToolNodes = this.graph.getNodesByType('tool');
126
+ const allInfraNodes = this.graph.getNodesByType('infrastructure');
127
+ const allPolicyNodes = this.graph.getNodesByType('policy');
128
+ const allRiskNodes = this.graph.getNodesByType('alert');
129
+ for (let ai = 0; ai < allAgentNodes.length; ai++) {
130
+ const agent = allAgentNodes[ai];
131
+ const toolCount = 2 + (ai % 3);
132
+ for (let t = 0; t < toolCount; t++) {
133
+ const tool = pick(allToolNodes, ai * 3 + t);
134
+ this.graph.addEdge({
135
+ source: agent.id, target: tool.id, relationship: 'invoked',
136
+ metadata: { timestamp: new Date().toISOString(), result: 'pass', confidence: 0.95, details: `Agent ${agent.name} invokes ${tool.name}` },
137
+ });
138
+ }
139
+ const certCount = 1 + (ai % 4);
140
+ for (let c = 0; c < certCount; c++) {
141
+ const fw = pick(frameworkNodeIds, ai + c);
142
+ this.graph.addEdge({
143
+ source: agent.id, target: fw, relationship: 'certified_by',
144
+ metadata: { timestamp: new Date().toISOString(), result: 'pass', confidence: 0.88, details: `Agent ${agent.name} certified for framework` },
145
+ });
146
+ }
147
+ }
148
+ for (let ci = 0; ci < allControlNodes.length; ci++) {
149
+ const ctrl = allControlNodes[ci];
150
+ if (ci % 2 === 0 && allControlNodes.length > ci + 1) {
151
+ const dep = allControlNodes[ci + 1];
152
+ this.graph.addEdge({
153
+ source: ctrl.id, target: dep.id, relationship: 'depends_on',
154
+ metadata: { timestamp: new Date().toISOString(), result: 'pass', confidence: 0.82, details: 'Control dependency chain' },
155
+ });
156
+ }
157
+ if (ci % 5 === 0) {
158
+ const infra = pick(allInfraNodes, ci);
159
+ this.graph.addEdge({
160
+ source: ctrl.id, target: infra.id, relationship: 'scoped_to',
161
+ metadata: { timestamp: new Date().toISOString(), result: 'pass', confidence: 0.75, details: `Control scoped to infrastructure ${infra.name}` },
162
+ });
163
+ }
164
+ if (ci % 3 === 0) {
165
+ const policy = pick(allPolicyNodes, ci);
166
+ this.graph.addEdge({
167
+ source: policy.id, target: ctrl.id, relationship: 'mitigates',
168
+ metadata: { timestamp: new Date().toISOString(), result: 'pass', confidence: 0.8, details: `Policy mitigates risk for control` },
169
+ });
170
+ }
171
+ if (ci % 7 === 0 && allInfraNodes.length > 0) {
172
+ const infra = pick(allInfraNodes, ci);
173
+ this.graph.addEdge({
174
+ source: infra.id, target: ctrl.id, relationship: 'owns',
175
+ metadata: { timestamp: new Date().toISOString(), result: 'pass', confidence: 0.7, details: `Infrastructure ${infra.name} implements control` },
176
+ });
177
+ }
178
+ }
179
+ for (let ri = 0; ri < allRiskNodes.length; ri++) {
180
+ const risk = allRiskNodes[ri];
181
+ if (ri % 3 === 0) {
182
+ const agent = pick(allAgentNodes, ri);
183
+ this.graph.addEdge({
184
+ source: agent.id, target: risk.id, relationship: 'detected',
185
+ metadata: { timestamp: new Date().toISOString(), result: 'fail', confidence: 0.6, details: 'Agent detected risk event' },
186
+ });
187
+ }
188
+ if (ri % 4 === 0) {
189
+ const infra = pick(allInfraNodes, ri);
190
+ this.graph.addEdge({
191
+ source: risk.id, target: infra.id, relationship: 'violated',
192
+ metadata: { timestamp: new Date().toISOString(), result: 'fail', confidence: 0.65, details: 'Risk violates infrastructure boundary' },
193
+ });
194
+ }
195
+ }
196
+ }
197
+ seedAttackPaths() {
198
+ const agents = this.graph.getNodesByType('agent');
199
+ const tools = this.graph.getNodesByType('tool');
200
+ const infra = this.graph.getNodesByType('infrastructure');
201
+ const controls = this.graph.getNodesByType('control');
202
+ const entryPoints = agents.slice(0, 5);
203
+ for (const entry of entryPoints) {
204
+ const tool1 = tools[0];
205
+ const tool2 = tools[1];
206
+ const targetInfra = infra[0];
207
+ const targetControl = controls[0];
208
+ if (tool1) {
209
+ this.graph.addEdge({
210
+ source: entry.id, target: tool1.id, relationship: 'invoked',
211
+ metadata: { timestamp: new Date().toISOString(), result: 'pass', confidence: 1.0, details: 'Attack path: initial credential compromise via tool invocation' },
212
+ });
213
+ }
214
+ if (tool1 && tool2) {
215
+ this.graph.addEdge({
216
+ source: tool1.id, target: tool2.id, relationship: 'depends_on',
217
+ metadata: { timestamp: new Date().toISOString(), result: 'pass', confidence: 0.8, details: 'Attack path: lateral movement through tool chain' },
218
+ });
219
+ }
220
+ if (tool2 && targetInfra) {
221
+ this.graph.addEdge({
222
+ source: tool2.id, target: targetInfra.id, relationship: 'scoped_to',
223
+ metadata: { timestamp: new Date().toISOString(), result: 'fail', confidence: 0.7, details: 'Attack path: infrastructure compromise via lateral movement' },
224
+ });
225
+ }
226
+ if (targetInfra && targetControl) {
227
+ this.graph.addEdge({
228
+ source: targetInfra.id, target: targetControl.id, relationship: 'violated',
229
+ metadata: { timestamp: new Date().toISOString(), result: 'fail', confidence: 0.6, details: 'Attack path: control bypass after infrastructure compromise' },
230
+ });
231
+ }
232
+ }
233
+ }
234
+ seedRiskNetwork() {
235
+ const agents = this.graph.getNodesByType('agent');
236
+ const infra = this.graph.getNodesByType('infrastructure');
237
+ const policies = this.graph.getNodesByType('policy');
238
+ const controls = this.graph.getNodesByType('control');
239
+ for (let i = 0; i < infra.length; i++) {
240
+ const host = infra[i];
241
+ const nextHost = infra[(i + 1) % infra.length];
242
+ this.graph.addEdge({
243
+ source: host.id, target: nextHost.id, relationship: 'depends_on',
244
+ metadata: { timestamp: new Date().toISOString(), result: 'pass', confidence: 0.75, details: `Network spread: ${host.name} -> ${nextHost.name}` },
245
+ });
246
+ }
247
+ for (let i = 0; i < 6; i++) {
248
+ const agent = agents[i % agents.length];
249
+ const host = infra[i % infra.length];
250
+ this.graph.addEdge({
251
+ source: agent.id, target: host.id, relationship: 'authenticated_by',
252
+ metadata: { timestamp: new Date().toISOString(), result: 'pass', confidence: 0.85, details: 'Agent authenticated to host' },
253
+ });
254
+ }
255
+ for (let i = 0; i < Math.min(policies.length, controls.length); i++) {
256
+ const policy = policies[i];
257
+ const control = controls[i * 7 % controls.length];
258
+ this.graph.addEdge({
259
+ source: policy.id, target: control.id, relationship: 'mitigates',
260
+ metadata: { timestamp: new Date().toISOString(), result: 'pass', confidence: 0.78, details: 'Policy mitigates control risk' },
261
+ });
262
+ }
263
+ }
264
+ }
package/package.json ADDED
@@ -0,0 +1,33 @@
1
+ {
2
+ "name": "@grc-claw/security-graph",
3
+ "version": "0.8.0",
4
+ "description": "Real-time security graph with attack path analysis and risk scoring for GRC_Claw",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "main": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "import": "./dist/index.js"
13
+ },
14
+ "./seeder": {
15
+ "types": "./dist/seeder.d.ts",
16
+ "import": "./dist/seeder.js"
17
+ }
18
+ },
19
+ "scripts": {
20
+ "build": "tsc -p tsconfig.json",
21
+ "test": "node --test dist/**/*.test.js 2>/dev/null || true"
22
+ },
23
+ "files": [
24
+ "dist"
25
+ ],
26
+ "publishConfig": {
27
+ "access": "public"
28
+ },
29
+ "repository": {
30
+ "type": "git",
31
+ "url": "https://github.com/AAH20/GRC_Claw"
32
+ }
33
+ }