devfortress-sdk 4.2.0 → 4.2.1
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.
- package/dist/abuseipdb.d.ts +10 -0
- package/dist/abuseipdb.js +121 -0
- package/dist/agent-security.d.ts +96 -0
- package/dist/agent-security.js +390 -0
- package/dist/agent.d.ts +61 -0
- package/dist/agent.js +177 -0
- package/dist/browser.d.ts +0 -27
- package/dist/browser.js +0 -33
- package/dist/circuit-breaker.d.ts +0 -41
- package/dist/circuit-breaker.js +1 -42
- package/dist/client.d.ts +0 -13
- package/dist/client.js +1 -19
- package/dist/devfortress.d.ts +64 -0
- package/dist/devfortress.js +758 -0
- package/dist/index.d.ts +0 -32
- package/dist/index.js +0 -40
- package/dist/internal-closed-loop-engine.d.ts +123 -0
- package/dist/internal-closed-loop-engine.js +683 -0
- package/dist/middleware/express.d.ts +0 -6
- package/dist/middleware/express.js +11 -41
- package/dist/quick.d.ts +0 -16
- package/dist/quick.js +0 -25
- package/dist/tier-gate.d.ts +38 -0
- package/dist/tier-gate.js +132 -0
- package/dist/token-alias.d.ts +47 -0
- package/dist/token-alias.js +312 -0
- package/dist/types.d.ts +0 -37
- package/dist/types.js +0 -10
- package/dist/unified-audit.d.ts +70 -0
- package/dist/unified-audit.js +171 -0
- package/package.json +2 -15
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { AbuseIPDBCheckResult, CompositeScoreResult } from './types';
|
|
2
|
+
export declare function checkIP(ip: string, apiKey: string): Promise<AbuseIPDBCheckResult>;
|
|
3
|
+
export declare function reportIP(ip: string, categories: number[], comment: string, apiKey: string): Promise<{
|
|
4
|
+
data: {
|
|
5
|
+
abuseConfidenceScore: number;
|
|
6
|
+
};
|
|
7
|
+
}>;
|
|
8
|
+
export declare function syncBlacklist(apiKey: string, confidenceMinimum?: number): Promise<number>;
|
|
9
|
+
export declare function scoreThreat(ip: string, devFortressConfidence: number, apiKey?: string): Promise<CompositeScoreResult>;
|
|
10
|
+
export declare function getCachedScore(ip: string): number | null;
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.checkIP = checkIP;
|
|
4
|
+
exports.reportIP = reportIP;
|
|
5
|
+
exports.syncBlacklist = syncBlacklist;
|
|
6
|
+
exports.scoreThreat = scoreThreat;
|
|
7
|
+
exports.getCachedScore = getCachedScore;
|
|
8
|
+
const ABUSEIPDB_API = 'https://api.abuseipdb.com/api/v2';
|
|
9
|
+
const ipCache = new Map();
|
|
10
|
+
async function checkIP(ip, apiKey) {
|
|
11
|
+
const res = await fetch(`${ABUSEIPDB_API}/check?ipAddress=${encodeURIComponent(ip)}&maxAgeInDays=90&verbose`, {
|
|
12
|
+
headers: {
|
|
13
|
+
Key: apiKey,
|
|
14
|
+
Accept: 'application/json',
|
|
15
|
+
},
|
|
16
|
+
});
|
|
17
|
+
if (!res.ok) {
|
|
18
|
+
throw new Error(`AbuseIPDB check failed: ${res.status}`);
|
|
19
|
+
}
|
|
20
|
+
const json = (await res.json());
|
|
21
|
+
return json.data;
|
|
22
|
+
}
|
|
23
|
+
async function reportIP(ip, categories, comment, apiKey) {
|
|
24
|
+
const res = await fetch(`${ABUSEIPDB_API}/report`, {
|
|
25
|
+
method: 'POST',
|
|
26
|
+
headers: {
|
|
27
|
+
Key: apiKey,
|
|
28
|
+
'Content-Type': 'application/x-www-form-urlencoded',
|
|
29
|
+
Accept: 'application/json',
|
|
30
|
+
},
|
|
31
|
+
body: new URLSearchParams({
|
|
32
|
+
ip,
|
|
33
|
+
categories: categories.join(','),
|
|
34
|
+
comment,
|
|
35
|
+
}),
|
|
36
|
+
});
|
|
37
|
+
if (!res.ok) {
|
|
38
|
+
throw new Error(`AbuseIPDB report failed: ${res.status}`);
|
|
39
|
+
}
|
|
40
|
+
return res.json();
|
|
41
|
+
}
|
|
42
|
+
async function syncBlacklist(apiKey, confidenceMinimum = 90) {
|
|
43
|
+
const res = await fetch(`${ABUSEIPDB_API}/blacklist?confidenceMinimum=${confidenceMinimum}&limit=10000`, {
|
|
44
|
+
headers: {
|
|
45
|
+
Key: apiKey,
|
|
46
|
+
Accept: 'application/json',
|
|
47
|
+
},
|
|
48
|
+
});
|
|
49
|
+
if (!res.ok) {
|
|
50
|
+
throw new Error(`AbuseIPDB blacklist sync failed: ${res.status}`);
|
|
51
|
+
}
|
|
52
|
+
const json = (await res.json());
|
|
53
|
+
const ttl = 6 * 60 * 60 * 1000;
|
|
54
|
+
for (const entry of json.data) {
|
|
55
|
+
ipCache.set(`abuseipdb:${entry.ipAddress}`, {
|
|
56
|
+
score: entry.abuseConfidenceScore,
|
|
57
|
+
expiresAt: Date.now() + ttl,
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
return json.data.length;
|
|
61
|
+
}
|
|
62
|
+
async function scoreThreat(ip, devFortressConfidence, apiKey) {
|
|
63
|
+
const cached = ipCache.get(`abuseipdb:${ip}`);
|
|
64
|
+
let abuseScore = 0;
|
|
65
|
+
let isTor = false;
|
|
66
|
+
let isDatacenter = false;
|
|
67
|
+
let distinctUsers = 0;
|
|
68
|
+
let fromCache = false;
|
|
69
|
+
if (cached && Date.now() < cached.expiresAt) {
|
|
70
|
+
abuseScore = cached.score;
|
|
71
|
+
fromCache = true;
|
|
72
|
+
}
|
|
73
|
+
else if (apiKey) {
|
|
74
|
+
try {
|
|
75
|
+
const data = await checkIP(ip, apiKey);
|
|
76
|
+
abuseScore = data.abuseConfidenceScore;
|
|
77
|
+
isTor = data.isTor;
|
|
78
|
+
isDatacenter = (data.usageType || '').includes('Data Center');
|
|
79
|
+
distinctUsers = data.numDistinctUsers;
|
|
80
|
+
ipCache.set(`abuseipdb:${ip}`, {
|
|
81
|
+
score: abuseScore,
|
|
82
|
+
expiresAt: Date.now() + 3600000,
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
catch {
|
|
86
|
+
abuseScore = 0;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
let score = 0;
|
|
90
|
+
score += abuseScore * 0.4;
|
|
91
|
+
score += devFortressConfidence * 100 * 0.6;
|
|
92
|
+
if (isTor)
|
|
93
|
+
score += 20;
|
|
94
|
+
if (isDatacenter)
|
|
95
|
+
score += 10;
|
|
96
|
+
if (distinctUsers >= 10)
|
|
97
|
+
score += 10;
|
|
98
|
+
score = Math.min(Math.round(score), 100);
|
|
99
|
+
const severity = score >= 85
|
|
100
|
+
? 'critical'
|
|
101
|
+
: score >= 60
|
|
102
|
+
? 'high'
|
|
103
|
+
: score >= 35
|
|
104
|
+
? 'medium'
|
|
105
|
+
: 'low';
|
|
106
|
+
return {
|
|
107
|
+
score,
|
|
108
|
+
severity,
|
|
109
|
+
abuseScore,
|
|
110
|
+
isTor,
|
|
111
|
+
isDatacenter,
|
|
112
|
+
distinctUsers,
|
|
113
|
+
fromCache,
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
function getCachedScore(ip) {
|
|
117
|
+
const cached = ipCache.get(`abuseipdb:${ip}`);
|
|
118
|
+
if (!cached || Date.now() > cached.expiresAt)
|
|
119
|
+
return null;
|
|
120
|
+
return cached.score;
|
|
121
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import type { CacheAdapter } from './token-alias';
|
|
2
|
+
import { TokenAliasManager } from './token-alias';
|
|
3
|
+
import type { RevocationReason } from './token-alias';
|
|
4
|
+
export type AgentCredentialType = 'agent_api_key' | 'agent_oauth_token' | 'service_account';
|
|
5
|
+
export interface AgentCredentialAliasData {
|
|
6
|
+
alias: string;
|
|
7
|
+
agentId: string;
|
|
8
|
+
credentialType: AgentCredentialType;
|
|
9
|
+
createdAt: number;
|
|
10
|
+
quarantined: boolean;
|
|
11
|
+
quarantineReason?: string;
|
|
12
|
+
quarantinedAt?: number;
|
|
13
|
+
}
|
|
14
|
+
export interface AgentBaselineMetrics {
|
|
15
|
+
avgToolsPerSession: number;
|
|
16
|
+
toolDiversityEntropy: number;
|
|
17
|
+
avgDataVolumeBytes: number;
|
|
18
|
+
avgSessionDurationMs: number;
|
|
19
|
+
topTools: string[];
|
|
20
|
+
totalSessions: number;
|
|
21
|
+
createdAt: number;
|
|
22
|
+
lastUpdatedAt: number;
|
|
23
|
+
taskDefinition?: string;
|
|
24
|
+
}
|
|
25
|
+
export interface AgentSessionRecord {
|
|
26
|
+
sessionId: string;
|
|
27
|
+
agentId: string;
|
|
28
|
+
toolCalls: {
|
|
29
|
+
tool: string;
|
|
30
|
+
durationMs?: number;
|
|
31
|
+
dataBytes?: number;
|
|
32
|
+
}[];
|
|
33
|
+
startedAt: number;
|
|
34
|
+
endedAt?: number;
|
|
35
|
+
totalDataBytes: number;
|
|
36
|
+
}
|
|
37
|
+
export type AgentAnomalyType = 'sequence_entropy_anomaly' | 'scope_deviation' | 'data_volume_anomaly' | 'cross_agent_credential_sharing' | 'unsanctioned_tool';
|
|
38
|
+
export interface AgentAnomalySignal {
|
|
39
|
+
type: AgentAnomalyType;
|
|
40
|
+
agentId: string;
|
|
41
|
+
severity: 'low' | 'medium' | 'high' | 'critical';
|
|
42
|
+
description: string;
|
|
43
|
+
detectedAt: number;
|
|
44
|
+
details: Record<string, unknown>;
|
|
45
|
+
}
|
|
46
|
+
export interface AgentScopeDefinition {
|
|
47
|
+
agentId: string;
|
|
48
|
+
allowedTools: string[];
|
|
49
|
+
strictMode: boolean;
|
|
50
|
+
createdAt: number;
|
|
51
|
+
updatedAt: number;
|
|
52
|
+
}
|
|
53
|
+
export declare function _resetAgentSecurityStores(): void;
|
|
54
|
+
export declare class AgentCredentialManager {
|
|
55
|
+
private tokenManager;
|
|
56
|
+
private cache;
|
|
57
|
+
constructor(tokenManager: TokenAliasManager, cache?: CacheAdapter);
|
|
58
|
+
createAgentAlias(realCredential: string, agentId: string, credentialType: AgentCredentialType, ttlSeconds?: number): Promise<string>;
|
|
59
|
+
revokeAgentAccess(agentId: string, reason?: RevocationReason): Promise<number>;
|
|
60
|
+
quarantineAgent(agentId: string, reason: string): Promise<void>;
|
|
61
|
+
unquarantineAgent(agentId: string): Promise<void>;
|
|
62
|
+
isQuarantined(agentId: string): boolean;
|
|
63
|
+
getQuarantineDetails(agentId: string): {
|
|
64
|
+
reason: string;
|
|
65
|
+
at: number;
|
|
66
|
+
} | null;
|
|
67
|
+
getAgentCredentials(agentId: string): AgentCredentialAliasData[];
|
|
68
|
+
}
|
|
69
|
+
export declare class AgentBaselineEngine {
|
|
70
|
+
private readonly BASELINE_WINDOW_DAYS;
|
|
71
|
+
private readonly MIN_SESSIONS_FOR_BASELINE;
|
|
72
|
+
recordSession(record: AgentSessionRecord): void;
|
|
73
|
+
agentBaseline(agentId: string, taskDefinition: string): AgentBaselineMetrics | null;
|
|
74
|
+
getBaseline(agentId: string): AgentBaselineMetrics | null;
|
|
75
|
+
private recalculateBaseline;
|
|
76
|
+
private stdDev;
|
|
77
|
+
is3SigmaDeviation(values: number[], newValue: number): boolean;
|
|
78
|
+
}
|
|
79
|
+
export declare class AgentAnomalyDetector {
|
|
80
|
+
private baselineEngine;
|
|
81
|
+
private anomalyHandlers;
|
|
82
|
+
private credentialUsage;
|
|
83
|
+
constructor(baselineEngine: AgentBaselineEngine);
|
|
84
|
+
onAnomaly(handler: (signal: AgentAnomalySignal) => void): void;
|
|
85
|
+
analyzeToolCall(agentId: string, toolName: string, dataBytes?: number, credentialAlias?: string): AgentAnomalySignal[];
|
|
86
|
+
analyzeSession(record: AgentSessionRecord): AgentAnomalySignal[];
|
|
87
|
+
private emitSignal;
|
|
88
|
+
}
|
|
89
|
+
export declare class AgentScopeEnforcer {
|
|
90
|
+
defineAgentScope(agentId: string, allowedTools: string[], strictMode?: boolean): AgentScopeDefinition;
|
|
91
|
+
isToolAllowed(agentId: string, toolName: string): boolean;
|
|
92
|
+
getScope(agentId: string): AgentScopeDefinition | null;
|
|
93
|
+
removeScope(agentId: string): void;
|
|
94
|
+
addAllowedTools(agentId: string, tools: string[]): void;
|
|
95
|
+
removeAllowedTools(agentId: string, tools: string[]): void;
|
|
96
|
+
}
|
|
@@ -0,0 +1,390 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.AgentScopeEnforcer = exports.AgentAnomalyDetector = exports.AgentBaselineEngine = exports.AgentCredentialManager = void 0;
|
|
4
|
+
exports._resetAgentSecurityStores = _resetAgentSecurityStores;
|
|
5
|
+
const agent_1 = require("./agent");
|
|
6
|
+
const agentCredentials = new Map();
|
|
7
|
+
const agentBaselines = new Map();
|
|
8
|
+
const agentSessions = new Map();
|
|
9
|
+
const agentScopes = new Map();
|
|
10
|
+
const agentQuarantines = new Map();
|
|
11
|
+
function _resetAgentSecurityStores() {
|
|
12
|
+
agentCredentials.clear();
|
|
13
|
+
agentBaselines.clear();
|
|
14
|
+
agentSessions.clear();
|
|
15
|
+
agentScopes.clear();
|
|
16
|
+
agentQuarantines.clear();
|
|
17
|
+
}
|
|
18
|
+
function shannonEntropy(items) {
|
|
19
|
+
if (items.length === 0)
|
|
20
|
+
return 0;
|
|
21
|
+
const freq = new Map();
|
|
22
|
+
for (const item of items) {
|
|
23
|
+
freq.set(item, (freq.get(item) || 0) + 1);
|
|
24
|
+
}
|
|
25
|
+
let entropy = 0;
|
|
26
|
+
const total = items.length;
|
|
27
|
+
for (const count of freq.values()) {
|
|
28
|
+
const p = count / total;
|
|
29
|
+
if (p > 0)
|
|
30
|
+
entropy -= p * Math.log2(p);
|
|
31
|
+
}
|
|
32
|
+
return entropy;
|
|
33
|
+
}
|
|
34
|
+
class AgentCredentialManager {
|
|
35
|
+
constructor(tokenManager, cache) {
|
|
36
|
+
this.tokenManager = tokenManager;
|
|
37
|
+
this.cache = cache || null;
|
|
38
|
+
}
|
|
39
|
+
async createAgentAlias(realCredential, agentId, credentialType, ttlSeconds = 7200) {
|
|
40
|
+
(0, agent_1.validateAgentId)(agentId);
|
|
41
|
+
const alias = await this.tokenManager.createAlias(realCredential, agentId, ttlSeconds);
|
|
42
|
+
const data = {
|
|
43
|
+
alias,
|
|
44
|
+
agentId,
|
|
45
|
+
credentialType,
|
|
46
|
+
createdAt: Date.now(),
|
|
47
|
+
quarantined: false,
|
|
48
|
+
};
|
|
49
|
+
if (!agentCredentials.has(agentId)) {
|
|
50
|
+
agentCredentials.set(agentId, []);
|
|
51
|
+
}
|
|
52
|
+
agentCredentials.get(agentId).push(data);
|
|
53
|
+
if (this.cache) {
|
|
54
|
+
try {
|
|
55
|
+
const existing = await this.cache.get(`agent:creds:${agentId}`);
|
|
56
|
+
const list = existing
|
|
57
|
+
? JSON.parse(existing)
|
|
58
|
+
: [];
|
|
59
|
+
list.push(data);
|
|
60
|
+
await this.cache.set(`agent:creds:${agentId}`, JSON.stringify(list), ttlSeconds);
|
|
61
|
+
}
|
|
62
|
+
catch {
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return alias;
|
|
66
|
+
}
|
|
67
|
+
async revokeAgentAccess(agentId, reason = 'active_threat') {
|
|
68
|
+
const creds = agentCredentials.get(agentId) || [];
|
|
69
|
+
let revokedCount = 0;
|
|
70
|
+
for (const cred of creds) {
|
|
71
|
+
try {
|
|
72
|
+
await this.tokenManager.revokeByAlias(cred.alias, reason, agentId);
|
|
73
|
+
revokedCount++;
|
|
74
|
+
}
|
|
75
|
+
catch {
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
agentCredentials.delete(agentId);
|
|
79
|
+
agentQuarantines.delete(agentId);
|
|
80
|
+
if (this.cache) {
|
|
81
|
+
try {
|
|
82
|
+
await this.cache.del(`agent:creds:${agentId}`);
|
|
83
|
+
await this.cache.del(`agent:quarantine:${agentId}`);
|
|
84
|
+
}
|
|
85
|
+
catch {
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
return revokedCount;
|
|
89
|
+
}
|
|
90
|
+
async quarantineAgent(agentId, reason) {
|
|
91
|
+
const quarantine = { reason, at: Date.now() };
|
|
92
|
+
agentQuarantines.set(agentId, quarantine);
|
|
93
|
+
const creds = agentCredentials.get(agentId) || [];
|
|
94
|
+
for (const cred of creds) {
|
|
95
|
+
cred.quarantined = true;
|
|
96
|
+
cred.quarantineReason = reason;
|
|
97
|
+
cred.quarantinedAt = quarantine.at;
|
|
98
|
+
}
|
|
99
|
+
if (this.cache) {
|
|
100
|
+
try {
|
|
101
|
+
await this.cache.set(`agent:quarantine:${agentId}`, JSON.stringify(quarantine), 86400);
|
|
102
|
+
}
|
|
103
|
+
catch {
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
async unquarantineAgent(agentId) {
|
|
108
|
+
agentQuarantines.delete(agentId);
|
|
109
|
+
const creds = agentCredentials.get(agentId) || [];
|
|
110
|
+
for (const cred of creds) {
|
|
111
|
+
cred.quarantined = false;
|
|
112
|
+
cred.quarantineReason = undefined;
|
|
113
|
+
cred.quarantinedAt = undefined;
|
|
114
|
+
}
|
|
115
|
+
if (this.cache) {
|
|
116
|
+
try {
|
|
117
|
+
await this.cache.del(`agent:quarantine:${agentId}`);
|
|
118
|
+
}
|
|
119
|
+
catch {
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
isQuarantined(agentId) {
|
|
124
|
+
return agentQuarantines.has(agentId);
|
|
125
|
+
}
|
|
126
|
+
getQuarantineDetails(agentId) {
|
|
127
|
+
return agentQuarantines.get(agentId) || null;
|
|
128
|
+
}
|
|
129
|
+
getAgentCredentials(agentId) {
|
|
130
|
+
return agentCredentials.get(agentId) || [];
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
exports.AgentCredentialManager = AgentCredentialManager;
|
|
134
|
+
class AgentBaselineEngine {
|
|
135
|
+
constructor() {
|
|
136
|
+
this.BASELINE_WINDOW_DAYS = 30;
|
|
137
|
+
this.MIN_SESSIONS_FOR_BASELINE = 5;
|
|
138
|
+
}
|
|
139
|
+
recordSession(record) {
|
|
140
|
+
if (!agentSessions.has(record.agentId)) {
|
|
141
|
+
agentSessions.set(record.agentId, []);
|
|
142
|
+
}
|
|
143
|
+
const sessions = agentSessions.get(record.agentId);
|
|
144
|
+
const windowStart = Date.now() - this.BASELINE_WINDOW_DAYS * 24 * 60 * 60 * 1000;
|
|
145
|
+
const filtered = sessions.filter(s => s.startedAt > windowStart);
|
|
146
|
+
filtered.push(record);
|
|
147
|
+
agentSessions.set(record.agentId, filtered);
|
|
148
|
+
if (filtered.length >= this.MIN_SESSIONS_FOR_BASELINE) {
|
|
149
|
+
this.recalculateBaseline(record.agentId);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
agentBaseline(agentId, taskDefinition) {
|
|
153
|
+
const existing = agentBaselines.get(agentId);
|
|
154
|
+
if (existing) {
|
|
155
|
+
existing.taskDefinition = taskDefinition;
|
|
156
|
+
existing.lastUpdatedAt = Date.now();
|
|
157
|
+
return existing;
|
|
158
|
+
}
|
|
159
|
+
const sessions = agentSessions.get(agentId) || [];
|
|
160
|
+
if (sessions.length < this.MIN_SESSIONS_FOR_BASELINE) {
|
|
161
|
+
return null;
|
|
162
|
+
}
|
|
163
|
+
return this.recalculateBaseline(agentId, taskDefinition);
|
|
164
|
+
}
|
|
165
|
+
getBaseline(agentId) {
|
|
166
|
+
return agentBaselines.get(agentId) || null;
|
|
167
|
+
}
|
|
168
|
+
recalculateBaseline(agentId, taskDefinition) {
|
|
169
|
+
const sessions = agentSessions.get(agentId) || [];
|
|
170
|
+
const toolsPerSession = sessions.map(s => s.toolCalls.length);
|
|
171
|
+
const avgToolsPerSession = toolsPerSession.reduce((a, b) => a + b, 0) / sessions.length;
|
|
172
|
+
const allTools = sessions.flatMap(s => s.toolCalls.map(tc => tc.tool));
|
|
173
|
+
const toolDiversityEntropy = shannonEntropy(allTools);
|
|
174
|
+
const dataVolumes = sessions.map(s => s.totalDataBytes);
|
|
175
|
+
const avgDataVolumeBytes = dataVolumes.reduce((a, b) => a + b, 0) / sessions.length;
|
|
176
|
+
const durations = sessions
|
|
177
|
+
.filter(s => s.endedAt)
|
|
178
|
+
.map(s => s.endedAt - s.startedAt);
|
|
179
|
+
const avgSessionDurationMs = durations.length > 0
|
|
180
|
+
? durations.reduce((a, b) => a + b, 0) / durations.length
|
|
181
|
+
: 0;
|
|
182
|
+
const toolFreq = new Map();
|
|
183
|
+
for (const tool of allTools) {
|
|
184
|
+
toolFreq.set(tool, (toolFreq.get(tool) || 0) + 1);
|
|
185
|
+
}
|
|
186
|
+
const topTools = [...toolFreq.entries()]
|
|
187
|
+
.sort((a, b) => b[1] - a[1])
|
|
188
|
+
.slice(0, 10)
|
|
189
|
+
.map(([tool]) => tool);
|
|
190
|
+
const baseline = {
|
|
191
|
+
avgToolsPerSession,
|
|
192
|
+
toolDiversityEntropy,
|
|
193
|
+
avgDataVolumeBytes,
|
|
194
|
+
avgSessionDurationMs,
|
|
195
|
+
topTools,
|
|
196
|
+
totalSessions: sessions.length,
|
|
197
|
+
createdAt: agentBaselines.get(agentId)?.createdAt || Date.now(),
|
|
198
|
+
lastUpdatedAt: Date.now(),
|
|
199
|
+
taskDefinition: taskDefinition || agentBaselines.get(agentId)?.taskDefinition,
|
|
200
|
+
};
|
|
201
|
+
agentBaselines.set(agentId, baseline);
|
|
202
|
+
return baseline;
|
|
203
|
+
}
|
|
204
|
+
stdDev(values) {
|
|
205
|
+
if (values.length < 2)
|
|
206
|
+
return 0;
|
|
207
|
+
const mean = values.reduce((a, b) => a + b, 0) / values.length;
|
|
208
|
+
const sqDiffs = values.map(v => Math.pow(v - mean, 2));
|
|
209
|
+
return Math.sqrt(sqDiffs.reduce((a, b) => a + b, 0) / values.length);
|
|
210
|
+
}
|
|
211
|
+
is3SigmaDeviation(values, newValue) {
|
|
212
|
+
if (values.length < 2)
|
|
213
|
+
return false;
|
|
214
|
+
const mean = values.reduce((a, b) => a + b, 0) / values.length;
|
|
215
|
+
const sd = this.stdDev(values);
|
|
216
|
+
if (sd === 0)
|
|
217
|
+
return newValue !== mean;
|
|
218
|
+
return Math.abs(newValue - mean) > 3 * sd;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
exports.AgentBaselineEngine = AgentBaselineEngine;
|
|
222
|
+
class AgentAnomalyDetector {
|
|
223
|
+
constructor(baselineEngine) {
|
|
224
|
+
this.anomalyHandlers = [];
|
|
225
|
+
this.credentialUsage = new Map();
|
|
226
|
+
this.baselineEngine = baselineEngine;
|
|
227
|
+
}
|
|
228
|
+
onAnomaly(handler) {
|
|
229
|
+
this.anomalyHandlers.push(handler);
|
|
230
|
+
}
|
|
231
|
+
analyzeToolCall(agentId, toolName, dataBytes = 0, credentialAlias) {
|
|
232
|
+
const signals = [];
|
|
233
|
+
const baseline = this.baselineEngine.getBaseline(agentId);
|
|
234
|
+
const scope = agentScopes.get(agentId);
|
|
235
|
+
if (scope?.strictMode && !scope.allowedTools.includes(toolName)) {
|
|
236
|
+
const signal = {
|
|
237
|
+
type: 'unsanctioned_tool',
|
|
238
|
+
agentId,
|
|
239
|
+
severity: 'critical',
|
|
240
|
+
description: `Agent ${agentId} invoked unsanctioned tool "${toolName}". Allowed: [${scope.allowedTools.join(', ')}]`,
|
|
241
|
+
detectedAt: Date.now(),
|
|
242
|
+
details: { tool: toolName, allowedTools: scope.allowedTools },
|
|
243
|
+
};
|
|
244
|
+
signals.push(signal);
|
|
245
|
+
this.emitSignal(signal);
|
|
246
|
+
}
|
|
247
|
+
if (!baseline)
|
|
248
|
+
return signals;
|
|
249
|
+
if (baseline.topTools.length > 0 && !baseline.topTools.includes(toolName)) {
|
|
250
|
+
const signal = {
|
|
251
|
+
type: 'scope_deviation',
|
|
252
|
+
agentId,
|
|
253
|
+
severity: 'medium',
|
|
254
|
+
description: `Agent ${agentId} invoked tool "${toolName}" outside its baseline tool set`,
|
|
255
|
+
detectedAt: Date.now(),
|
|
256
|
+
details: {
|
|
257
|
+
tool: toolName,
|
|
258
|
+
baselineTools: baseline.topTools,
|
|
259
|
+
},
|
|
260
|
+
};
|
|
261
|
+
signals.push(signal);
|
|
262
|
+
this.emitSignal(signal);
|
|
263
|
+
}
|
|
264
|
+
if (dataBytes > 0) {
|
|
265
|
+
const sessions = agentSessions.get(agentId) || [];
|
|
266
|
+
const volumes = sessions.map(s => s.totalDataBytes);
|
|
267
|
+
if (this.baselineEngine.is3SigmaDeviation(volumes, dataBytes)) {
|
|
268
|
+
const signal = {
|
|
269
|
+
type: 'data_volume_anomaly',
|
|
270
|
+
agentId,
|
|
271
|
+
severity: 'high',
|
|
272
|
+
description: `Agent ${agentId} data volume ${dataBytes}B is 3σ deviation from baseline avg ${Math.round(baseline.avgDataVolumeBytes)}B`,
|
|
273
|
+
detectedAt: Date.now(),
|
|
274
|
+
details: {
|
|
275
|
+
dataBytes,
|
|
276
|
+
baselineAvg: baseline.avgDataVolumeBytes,
|
|
277
|
+
},
|
|
278
|
+
};
|
|
279
|
+
signals.push(signal);
|
|
280
|
+
this.emitSignal(signal);
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
if (credentialAlias) {
|
|
284
|
+
if (!this.credentialUsage.has(credentialAlias)) {
|
|
285
|
+
this.credentialUsage.set(credentialAlias, new Set());
|
|
286
|
+
}
|
|
287
|
+
const agents = this.credentialUsage.get(credentialAlias);
|
|
288
|
+
agents.add(agentId);
|
|
289
|
+
if (agents.size > 1) {
|
|
290
|
+
const signal = {
|
|
291
|
+
type: 'cross_agent_credential_sharing',
|
|
292
|
+
agentId,
|
|
293
|
+
severity: 'critical',
|
|
294
|
+
description: `Credential ${credentialAlias.substring(0, 16)}... shared across ${agents.size} agents: [${[...agents].join(', ')}]`,
|
|
295
|
+
detectedAt: Date.now(),
|
|
296
|
+
details: {
|
|
297
|
+
credentialAlias: credentialAlias.substring(0, 16) + '...',
|
|
298
|
+
sharingAgents: [...agents],
|
|
299
|
+
},
|
|
300
|
+
};
|
|
301
|
+
signals.push(signal);
|
|
302
|
+
this.emitSignal(signal);
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
return signals;
|
|
306
|
+
}
|
|
307
|
+
analyzeSession(record) {
|
|
308
|
+
const signals = [];
|
|
309
|
+
const baseline = this.baselineEngine.getBaseline(record.agentId);
|
|
310
|
+
if (!baseline || baseline.totalSessions < 5)
|
|
311
|
+
return signals;
|
|
312
|
+
const sessionTools = record.toolCalls.map(tc => tc.tool);
|
|
313
|
+
const sessionEntropy = shannonEntropy(sessionTools);
|
|
314
|
+
const entropyDeviation = Math.abs(sessionEntropy - baseline.toolDiversityEntropy);
|
|
315
|
+
if (baseline.toolDiversityEntropy > 0 &&
|
|
316
|
+
entropyDeviation / baseline.toolDiversityEntropy > 0.5) {
|
|
317
|
+
const signal = {
|
|
318
|
+
type: 'sequence_entropy_anomaly',
|
|
319
|
+
agentId: record.agentId,
|
|
320
|
+
severity: 'high',
|
|
321
|
+
description: `Agent ${record.agentId} session entropy ${sessionEntropy.toFixed(2)} deviates ${((entropyDeviation / baseline.toolDiversityEntropy) * 100).toFixed(0)}% from baseline ${baseline.toolDiversityEntropy.toFixed(2)}`,
|
|
322
|
+
detectedAt: Date.now(),
|
|
323
|
+
details: {
|
|
324
|
+
sessionEntropy,
|
|
325
|
+
baselineEntropy: baseline.toolDiversityEntropy,
|
|
326
|
+
deviationPercent: (entropyDeviation / baseline.toolDiversityEntropy) * 100,
|
|
327
|
+
},
|
|
328
|
+
};
|
|
329
|
+
signals.push(signal);
|
|
330
|
+
this.emitSignal(signal);
|
|
331
|
+
}
|
|
332
|
+
return signals;
|
|
333
|
+
}
|
|
334
|
+
emitSignal(signal) {
|
|
335
|
+
for (const handler of this.anomalyHandlers) {
|
|
336
|
+
try {
|
|
337
|
+
handler(signal);
|
|
338
|
+
}
|
|
339
|
+
catch {
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
exports.AgentAnomalyDetector = AgentAnomalyDetector;
|
|
345
|
+
class AgentScopeEnforcer {
|
|
346
|
+
defineAgentScope(agentId, allowedTools, strictMode = true) {
|
|
347
|
+
const scope = {
|
|
348
|
+
agentId,
|
|
349
|
+
allowedTools,
|
|
350
|
+
strictMode,
|
|
351
|
+
createdAt: agentScopes.get(agentId)?.createdAt || Date.now(),
|
|
352
|
+
updatedAt: Date.now(),
|
|
353
|
+
};
|
|
354
|
+
agentScopes.set(agentId, scope);
|
|
355
|
+
return scope;
|
|
356
|
+
}
|
|
357
|
+
isToolAllowed(agentId, toolName) {
|
|
358
|
+
const scope = agentScopes.get(agentId);
|
|
359
|
+
if (!scope)
|
|
360
|
+
return true;
|
|
361
|
+
if (!scope.strictMode)
|
|
362
|
+
return true;
|
|
363
|
+
return scope.allowedTools.includes(toolName);
|
|
364
|
+
}
|
|
365
|
+
getScope(agentId) {
|
|
366
|
+
return agentScopes.get(agentId) || null;
|
|
367
|
+
}
|
|
368
|
+
removeScope(agentId) {
|
|
369
|
+
agentScopes.delete(agentId);
|
|
370
|
+
}
|
|
371
|
+
addAllowedTools(agentId, tools) {
|
|
372
|
+
const scope = agentScopes.get(agentId);
|
|
373
|
+
if (!scope) {
|
|
374
|
+
this.defineAgentScope(agentId, tools, true);
|
|
375
|
+
return;
|
|
376
|
+
}
|
|
377
|
+
const toolSet = new Set([...scope.allowedTools, ...tools]);
|
|
378
|
+
scope.allowedTools = [...toolSet];
|
|
379
|
+
scope.updatedAt = Date.now();
|
|
380
|
+
}
|
|
381
|
+
removeAllowedTools(agentId, tools) {
|
|
382
|
+
const scope = agentScopes.get(agentId);
|
|
383
|
+
if (!scope)
|
|
384
|
+
return;
|
|
385
|
+
const removeSet = new Set(tools);
|
|
386
|
+
scope.allowedTools = scope.allowedTools.filter(t => !removeSet.has(t));
|
|
387
|
+
scope.updatedAt = Date.now();
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
exports.AgentScopeEnforcer = AgentScopeEnforcer;
|
package/dist/agent.d.ts
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import type { DevFortress } from './devfortress';
|
|
2
|
+
import type { ObserveResult } from './types';
|
|
3
|
+
export interface AgentToolCall {
|
|
4
|
+
tool: string;
|
|
5
|
+
input?: Record<string, unknown>;
|
|
6
|
+
output?: unknown;
|
|
7
|
+
durationMs?: number;
|
|
8
|
+
model?: string;
|
|
9
|
+
framework?: 'langchain' | 'langgraph' | 'openai' | 'anthropic' | 'raw_http' | 'custom';
|
|
10
|
+
meta?: Record<string, unknown>;
|
|
11
|
+
}
|
|
12
|
+
export interface AgentAdapterConfig {
|
|
13
|
+
agentId: string;
|
|
14
|
+
agentName?: string;
|
|
15
|
+
agentIp?: string;
|
|
16
|
+
maxInputSummaryLength?: number;
|
|
17
|
+
sanitizeInputs?: boolean;
|
|
18
|
+
onFlagged?: (toolCall: AgentToolCall, result: ObserveResult) => void | Promise<void>;
|
|
19
|
+
}
|
|
20
|
+
declare function validateAgentId(agentId: string): void;
|
|
21
|
+
export { validateAgentId };
|
|
22
|
+
export declare class AgentAdapter {
|
|
23
|
+
private df;
|
|
24
|
+
private agentId;
|
|
25
|
+
private agentName;
|
|
26
|
+
private agentIp;
|
|
27
|
+
private maxInputSummaryLength;
|
|
28
|
+
private sanitizeInputs;
|
|
29
|
+
private onFlagged?;
|
|
30
|
+
private sessionToolCounts;
|
|
31
|
+
constructor(df: DevFortress, config: AgentAdapterConfig);
|
|
32
|
+
observeToolCall(toolCall: AgentToolCall, sessionId?: string): Promise<ObserveResult | null>;
|
|
33
|
+
observeLangChainTool(toolName: string, toolInput: Record<string, unknown>, options?: {
|
|
34
|
+
sessionId?: string;
|
|
35
|
+
model?: string;
|
|
36
|
+
durationMs?: number;
|
|
37
|
+
}): Promise<ObserveResult | null>;
|
|
38
|
+
observeOpenAIToolCall(toolCall: {
|
|
39
|
+
function: {
|
|
40
|
+
name: string;
|
|
41
|
+
arguments: string;
|
|
42
|
+
};
|
|
43
|
+
}, options?: {
|
|
44
|
+
sessionId?: string;
|
|
45
|
+
model?: string;
|
|
46
|
+
durationMs?: number;
|
|
47
|
+
}): Promise<ObserveResult | null>;
|
|
48
|
+
observeHttpToolCall(url: string, method: string, options?: {
|
|
49
|
+
statusCode?: number;
|
|
50
|
+
durationMs?: number;
|
|
51
|
+
sessionId?: string;
|
|
52
|
+
meta?: Record<string, unknown>;
|
|
53
|
+
}): Promise<ObserveResult | null>;
|
|
54
|
+
trackSessionStart(sessionId: string, taskDescription?: string): Promise<ObserveResult | null>;
|
|
55
|
+
trackSessionEnd(sessionId: string, summary?: {
|
|
56
|
+
totalTools?: number;
|
|
57
|
+
durationMs?: number;
|
|
58
|
+
success?: boolean;
|
|
59
|
+
}): Promise<ObserveResult | null>;
|
|
60
|
+
getSessionToolCount(sessionId: string): number;
|
|
61
|
+
}
|