@guava-parity/guard-scanner 13.0.0 ā 16.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.
- package/README.md +170 -215
- package/README_ja.md +252 -0
- package/SECURITY.md +12 -4
- package/SKILL.md +148 -57
- package/dist/cli.cjs +5997 -0
- package/dist/cli.d.mts +1 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.mjs +6003 -0
- package/dist/index.cjs +4825 -0
- package/dist/index.d.mts +17 -0
- package/dist/index.d.ts +17 -0
- package/dist/index.mjs +4798 -0
- package/dist/mcp-server.cjs +4756 -0
- package/dist/mcp-server.d.mts +1 -0
- package/dist/mcp-server.d.ts +1 -0
- package/dist/mcp-server.mjs +4767 -0
- package/dist/openclaw-plugin.cjs +4863 -0
- package/dist/openclaw-plugin.d.mts +11 -0
- package/dist/openclaw-plugin.d.ts +11 -0
- package/dist/openclaw-plugin.mjs +4854 -0
- package/dist/types.cjs +18 -0
- package/dist/types.d.mts +215 -0
- package/dist/types.d.ts +215 -0
- package/dist/types.mjs +1 -0
- package/docs/EVIDENCE_DRIVEN.md +182 -0
- package/docs/banner.png +0 -0
- package/docs/data/benchmark-ledger.json +1428 -0
- package/docs/data/corpus-metrics.json +11 -0
- package/docs/data/fp-ledger.json +18 -0
- package/docs/data/latest.json +25837 -2481
- package/docs/data/quality-contract.json +36 -0
- package/docs/generated/npm-audit-20260312.json +96 -0
- package/docs/generated/openclaw-upstream-status.json +25 -0
- package/docs/glossary.md +46 -0
- package/docs/index.html +1085 -496
- package/docs/logo.png +0 -0
- package/docs/openclaw-compatibility-audit.md +45 -0
- package/docs/openclaw-continuous-compatibility-plan.md +37 -0
- package/docs/rules/a2a-contagion.md +68 -0
- package/docs/rules/advanced-exfil.md +52 -0
- package/docs/rules/agent-protocol.md +108 -0
- package/docs/rules/api-abuse.md +68 -0
- package/docs/rules/autonomous-risk.md +92 -0
- package/docs/rules/config-impact.md +132 -0
- package/docs/rules/credential-handling.md +100 -0
- package/docs/rules/cve-patterns.md +332 -0
- package/docs/rules/data-exposure.md +84 -0
- package/docs/rules/exfiltration.md +36 -0
- package/docs/rules/financial-access.md +84 -0
- package/docs/rules/identity-hijack.md +140 -0
- package/docs/rules/inference-manipulation.md +60 -0
- package/docs/rules/leaky-skills.md +52 -0
- package/docs/rules/malicious-code.md +108 -0
- package/docs/rules/mcp-security.md +148 -0
- package/docs/rules/memory-poisoning.md +84 -0
- package/docs/rules/model-poisoning.md +44 -0
- package/docs/rules/obfuscation.md +60 -0
- package/docs/rules/persistence.md +108 -0
- package/docs/rules/pii-exposure.md +116 -0
- package/docs/rules/prompt-injection.md +148 -0
- package/docs/rules/prompt-worm.md +44 -0
- package/docs/rules/safeguard-bypass.md +44 -0
- package/docs/rules/sandbox-escape.md +100 -0
- package/docs/rules/secret-detection.md +44 -0
- package/docs/rules/supply-chain-v2.md +92 -0
- package/docs/rules/suspicious-download.md +60 -0
- package/docs/rules/trust-boundary.md +76 -0
- package/docs/rules/trust-exploitation.md +92 -0
- package/docs/rules/unverifiable-deps.md +84 -0
- package/docs/rules/vdb-injection.md +84 -0
- package/docs/security-vulnerability-report-20260312.md +53 -0
- package/docs/spec/PRD_V2_ARCHITECTURE.md +55 -0
- package/docs/spec/capabilities.json +174 -0
- package/docs/spec/finding.schema.json +104 -0
- package/docs/spec/integration-manifest.md +39 -0
- package/docs/spec/plugin-trust.json +11 -0
- package/docs/spec/sbom.json +33 -0
- package/docs/threat-model.md +65 -0
- package/docs/v13-architecture-manifest.md +55 -0
- package/hooks/context.ts +306 -0
- package/hooks/guard-scanner/plugin.ts +24 -1
- package/openclaw-plugin.mts +107 -0
- package/openclaw.plugin.json +30 -53
- package/package.json +66 -13
- package/src/asset-auditor.js +0 -508
- package/src/ci-reporter.js +0 -135
- package/src/cli.js +0 -294
- package/src/html-template.js +0 -239
- package/src/ioc-db.js +0 -54
- package/src/mcp-server.js +0 -702
- package/src/patterns.js +0 -611
- package/src/quarantine.js +0 -41
- package/src/runtime-guard.js +0 -346
- package/src/scanner.js +0 -1157
- package/src/vt-client.js +0 -202
- package/src/watcher.js +0 -170
package/src/vt-client.js
DELETED
|
@@ -1,202 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* guard-scanner v7 ā VirusTotal API v3 Client
|
|
3
|
-
*
|
|
4
|
-
* @security-manifest
|
|
5
|
-
* env-read: [VT_API_KEY]
|
|
6
|
-
* env-write: []
|
|
7
|
-
* network: [virustotal.com API v3]
|
|
8
|
-
* fs-read: [files for SHA256 hashing]
|
|
9
|
-
* fs-write: []
|
|
10
|
-
* exec: none
|
|
11
|
-
* purpose: VirusTotal threat intelligence integration
|
|
12
|
-
*/
|
|
13
|
-
|
|
14
|
-
const https = require('https');
|
|
15
|
-
const crypto = require('crypto');
|
|
16
|
-
const fs = require('fs');
|
|
17
|
-
|
|
18
|
-
const VT_API_BASE = 'https://www.virustotal.com/api/v3';
|
|
19
|
-
const VT_RATE_LIMIT = 4; // requests per minute (free tier)
|
|
20
|
-
|
|
21
|
-
class VTClient {
|
|
22
|
-
constructor(apiKey, options = {}) {
|
|
23
|
-
if (!apiKey) throw new Error('VirusTotal API key is required. Set VT_API_KEY environment variable.');
|
|
24
|
-
this.apiKey = apiKey;
|
|
25
|
-
this.timeout = options.timeout || 15000;
|
|
26
|
-
this.verbose = options.verbose || false;
|
|
27
|
-
this._requestCount = 0;
|
|
28
|
-
this._windowStart = Date.now();
|
|
29
|
-
this._httpGet = options._httpGet || null; // DI for testing
|
|
30
|
-
this._httpPost = options._httpPost || null;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
// āā Rate limiter (4 req/min free tier) āāāāāāāāāāāāāāāāāāāāāā
|
|
34
|
-
async _throttle() {
|
|
35
|
-
const now = Date.now();
|
|
36
|
-
const elapsed = now - this._windowStart;
|
|
37
|
-
if (elapsed >= 60000) {
|
|
38
|
-
this._requestCount = 0;
|
|
39
|
-
this._windowStart = now;
|
|
40
|
-
}
|
|
41
|
-
if (this._requestCount >= VT_RATE_LIMIT) {
|
|
42
|
-
const waitMs = 60000 - elapsed + 100;
|
|
43
|
-
if (this.verbose) console.log(`ā³ VT rate limit: waiting ${Math.ceil(waitMs / 1000)}s`);
|
|
44
|
-
await new Promise(r => setTimeout(r, waitMs));
|
|
45
|
-
this._requestCount = 0;
|
|
46
|
-
this._windowStart = Date.now();
|
|
47
|
-
}
|
|
48
|
-
this._requestCount++;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
// āā HTTP helpers āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
52
|
-
async _get(path) {
|
|
53
|
-
await this._throttle();
|
|
54
|
-
if (this._httpGet) return this._httpGet(`${VT_API_BASE}${path}`);
|
|
55
|
-
|
|
56
|
-
return new Promise((resolve, reject) => {
|
|
57
|
-
const req = https.request({
|
|
58
|
-
hostname: 'www.virustotal.com',
|
|
59
|
-
path: `/api/v3${path}`,
|
|
60
|
-
method: 'GET',
|
|
61
|
-
headers: {
|
|
62
|
-
'x-apikey': this.apiKey,
|
|
63
|
-
'Accept': 'application/json',
|
|
64
|
-
},
|
|
65
|
-
}, (res) => {
|
|
66
|
-
let data = '';
|
|
67
|
-
res.on('data', chunk => { data += chunk; });
|
|
68
|
-
res.on('end', () => {
|
|
69
|
-
try {
|
|
70
|
-
const parsed = JSON.parse(data);
|
|
71
|
-
if (res.statusCode === 429) {
|
|
72
|
-
reject(new Error('VT rate limit exceeded'));
|
|
73
|
-
} else if (res.statusCode === 404) {
|
|
74
|
-
resolve({ found: false, data: null });
|
|
75
|
-
} else if (res.statusCode >= 200 && res.statusCode < 300) {
|
|
76
|
-
resolve({ found: true, data: parsed });
|
|
77
|
-
} else {
|
|
78
|
-
reject(new Error(`VT API error ${res.statusCode}: ${JSON.stringify(parsed).substring(0, 200)}`));
|
|
79
|
-
}
|
|
80
|
-
} catch (e) {
|
|
81
|
-
reject(new Error(`VT response parse error: ${e.message}`));
|
|
82
|
-
}
|
|
83
|
-
});
|
|
84
|
-
});
|
|
85
|
-
req.on('error', reject);
|
|
86
|
-
req.setTimeout(this.timeout, () => { req.destroy(); reject(new Error('VT API timeout')); });
|
|
87
|
-
req.end();
|
|
88
|
-
});
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
// āā File Hash Lookup āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
92
|
-
async lookupHash(hash) {
|
|
93
|
-
if (!hash || hash.length < 32) throw new Error('Invalid hash: provide MD5, SHA1, or SHA256');
|
|
94
|
-
const result = await this._get(`/files/${hash}`);
|
|
95
|
-
|
|
96
|
-
if (!result.found) {
|
|
97
|
-
return { found: false, hash, malicious: 0, suspicious: 0, harmless: 0, undetected: 0, engines: {} };
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
const attrs = result.data.data?.attributes || {};
|
|
101
|
-
const stats = attrs.last_analysis_stats || {};
|
|
102
|
-
const results = attrs.last_analysis_results || {};
|
|
103
|
-
|
|
104
|
-
// Extract detected engines
|
|
105
|
-
const detectedEngines = {};
|
|
106
|
-
for (const [engine, info] of Object.entries(results)) {
|
|
107
|
-
if (info.category === 'malicious' || info.category === 'suspicious') {
|
|
108
|
-
detectedEngines[engine] = { category: info.category, result: info.result };
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
return {
|
|
113
|
-
found: true,
|
|
114
|
-
hash,
|
|
115
|
-
malicious: stats.malicious || 0,
|
|
116
|
-
suspicious: stats.suspicious || 0,
|
|
117
|
-
harmless: stats.harmless || 0,
|
|
118
|
-
undetected: stats.undetected || 0,
|
|
119
|
-
engines: detectedEngines,
|
|
120
|
-
reputation: attrs.reputation || 0,
|
|
121
|
-
tags: attrs.tags || [],
|
|
122
|
-
};
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
// āā URL Scan āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
126
|
-
async scanURL(url) {
|
|
127
|
-
if (!url) throw new Error('URL is required');
|
|
128
|
-
// URL ID = base64url of the URL
|
|
129
|
-
const urlId = Buffer.from(url).toString('base64').replace(/=/g, '').replace(/\+/g, '-').replace(/\//g, '_');
|
|
130
|
-
const result = await this._get(`/urls/${urlId}`);
|
|
131
|
-
|
|
132
|
-
if (!result.found) {
|
|
133
|
-
return { found: false, url, malicious: 0, suspicious: 0, harmless: 0 };
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
const attrs = result.data.data?.attributes || {};
|
|
137
|
-
const stats = attrs.last_analysis_stats || {};
|
|
138
|
-
|
|
139
|
-
return {
|
|
140
|
-
found: true,
|
|
141
|
-
url,
|
|
142
|
-
malicious: stats.malicious || 0,
|
|
143
|
-
suspicious: stats.suspicious || 0,
|
|
144
|
-
harmless: stats.harmless || 0,
|
|
145
|
-
categories: attrs.categories || {},
|
|
146
|
-
};
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
// āā Domain Report āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
150
|
-
async checkDomain(domain) {
|
|
151
|
-
if (!domain) throw new Error('Domain is required');
|
|
152
|
-
const result = await this._get(`/domains/${domain}`);
|
|
153
|
-
|
|
154
|
-
if (!result.found) {
|
|
155
|
-
return { found: false, domain, reputation: 0, malicious: 0 };
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
const attrs = result.data.data?.attributes || {};
|
|
159
|
-
const stats = attrs.last_analysis_stats || {};
|
|
160
|
-
|
|
161
|
-
return {
|
|
162
|
-
found: true,
|
|
163
|
-
domain,
|
|
164
|
-
reputation: attrs.reputation || 0,
|
|
165
|
-
malicious: stats.malicious || 0,
|
|
166
|
-
suspicious: stats.suspicious || 0,
|
|
167
|
-
categories: attrs.categories || {},
|
|
168
|
-
registrar: attrs.registrar || 'unknown',
|
|
169
|
-
};
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
// āā IP Report āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
173
|
-
async checkIP(ip) {
|
|
174
|
-
if (!ip) throw new Error('IP address is required');
|
|
175
|
-
const result = await this._get(`/ip_addresses/${ip}`);
|
|
176
|
-
|
|
177
|
-
if (!result.found) {
|
|
178
|
-
return { found: false, ip, reputation: 0, malicious: 0 };
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
const attrs = result.data.data?.attributes || {};
|
|
182
|
-
const stats = attrs.last_analysis_stats || {};
|
|
183
|
-
|
|
184
|
-
return {
|
|
185
|
-
found: true,
|
|
186
|
-
ip,
|
|
187
|
-
reputation: attrs.reputation || 0,
|
|
188
|
-
malicious: stats.malicious || 0,
|
|
189
|
-
suspicious: stats.suspicious || 0,
|
|
190
|
-
country: attrs.country || 'unknown',
|
|
191
|
-
as_owner: attrs.as_owner || 'unknown',
|
|
192
|
-
};
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
// āā File SHA256 helper āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
196
|
-
static hashFile(filePath) {
|
|
197
|
-
const content = fs.readFileSync(filePath);
|
|
198
|
-
return crypto.createHash('sha256').update(content).digest('hex');
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
module.exports = { VTClient, VT_API_BASE, VT_RATE_LIMIT };
|
package/src/watcher.js
DELETED
|
@@ -1,170 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* guard-scanner v8 ā Real-time File Watcher
|
|
3
|
-
*
|
|
4
|
-
* @security-manifest
|
|
5
|
-
* env-read: []
|
|
6
|
-
* env-write: []
|
|
7
|
-
* network: none
|
|
8
|
-
* fs-read: [watched directory]
|
|
9
|
-
* fs-write: []
|
|
10
|
-
* exec: none
|
|
11
|
-
* purpose: Real-time file system monitoring for security threats
|
|
12
|
-
*/
|
|
13
|
-
|
|
14
|
-
const fs = require('fs');
|
|
15
|
-
const path = require('path');
|
|
16
|
-
const { GuardScanner } = require('./scanner.js');
|
|
17
|
-
const EventEmitter = require('events');
|
|
18
|
-
|
|
19
|
-
class GuardWatcher extends EventEmitter {
|
|
20
|
-
constructor(options = {}) {
|
|
21
|
-
super();
|
|
22
|
-
this.verbose = options.verbose || false;
|
|
23
|
-
this.strict = options.strict || false;
|
|
24
|
-
this.soulLock = options.soulLock || false;
|
|
25
|
-
this.debounceMs = options.debounceMs || 300;
|
|
26
|
-
this.quiet = options.quiet || false;
|
|
27
|
-
this._watchers = [];
|
|
28
|
-
this._debounceTimers = new Map();
|
|
29
|
-
this._running = false;
|
|
30
|
-
this._scanCount = 0;
|
|
31
|
-
this._alertCount = 0;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
watch(directory) {
|
|
35
|
-
if (!directory) throw new Error('Watch directory is required');
|
|
36
|
-
if (!fs.existsSync(directory)) throw new Error(`Directory not found: ${directory}`);
|
|
37
|
-
if (!fs.statSync(directory).isDirectory()) throw new Error(`Not a directory: ${directory}`);
|
|
38
|
-
|
|
39
|
-
this._running = true;
|
|
40
|
-
|
|
41
|
-
if (!this.quiet) {
|
|
42
|
-
console.log(`\nš”ļø guard-scanner watch mode`);
|
|
43
|
-
console.log(`šļø Watching: ${path.resolve(directory)}`);
|
|
44
|
-
console.log(`ā” Strict: ${this.strict ? 'ON' : 'OFF'}`);
|
|
45
|
-
console.log(`š Soul Lock: ${this.soulLock ? 'ON' : 'OFF'}`);
|
|
46
|
-
console.log('āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā');
|
|
47
|
-
console.log('Press Ctrl+C to stop\n');
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
try {
|
|
51
|
-
const watcher = fs.watch(directory, { recursive: true }, (eventType, filename) => {
|
|
52
|
-
if (!filename || !this._running) return;
|
|
53
|
-
const fullPath = path.join(directory, filename);
|
|
54
|
-
|
|
55
|
-
// Debounce
|
|
56
|
-
if (this._debounceTimers.has(fullPath)) {
|
|
57
|
-
clearTimeout(this._debounceTimers.get(fullPath));
|
|
58
|
-
}
|
|
59
|
-
this._debounceTimers.set(fullPath, setTimeout(() => {
|
|
60
|
-
this._debounceTimers.delete(fullPath);
|
|
61
|
-
this._onFileChange(directory, filename, eventType);
|
|
62
|
-
}, this.debounceMs));
|
|
63
|
-
});
|
|
64
|
-
|
|
65
|
-
this._watchers.push(watcher);
|
|
66
|
-
this.emit('watching', { directory: path.resolve(directory) });
|
|
67
|
-
} catch (e) {
|
|
68
|
-
this.emit('error', e);
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
return this;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
_onFileChange(baseDir, filename, eventType) {
|
|
75
|
-
if (!this._running) return;
|
|
76
|
-
|
|
77
|
-
// Skip hidden files, node_modules, .git
|
|
78
|
-
if (filename.startsWith('.') || filename.includes('node_modules') || filename.includes('.git')) {
|
|
79
|
-
return;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
// Skip non-code files
|
|
83
|
-
const ext = path.extname(filename).toLowerCase();
|
|
84
|
-
const codeExts = ['.js', '.ts', '.py', '.sh', '.md', '.json', '.yaml', '.yml', '.toml'];
|
|
85
|
-
if (!codeExts.includes(ext)) return;
|
|
86
|
-
|
|
87
|
-
const fullPath = path.join(baseDir, filename);
|
|
88
|
-
if (!fs.existsSync(fullPath)) {
|
|
89
|
-
this.emit('deleted', { file: filename });
|
|
90
|
-
return;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
this._scanCount++;
|
|
94
|
-
const scanner = new GuardScanner({
|
|
95
|
-
summaryOnly: true,
|
|
96
|
-
strict: this.strict,
|
|
97
|
-
soulLock: this.soulLock,
|
|
98
|
-
quiet: true,
|
|
99
|
-
});
|
|
100
|
-
|
|
101
|
-
// Find the skill directory (parent directory of the changed file)
|
|
102
|
-
const skillDir = path.dirname(fullPath);
|
|
103
|
-
const skillName = path.basename(skillDir);
|
|
104
|
-
|
|
105
|
-
try {
|
|
106
|
-
scanner.scanSkill(skillDir, skillName);
|
|
107
|
-
const findings = scanner.findings[0];
|
|
108
|
-
|
|
109
|
-
if (findings && findings.findings.length > 0) {
|
|
110
|
-
this._alertCount += findings.findings.length;
|
|
111
|
-
const result = {
|
|
112
|
-
file: filename,
|
|
113
|
-
skill: skillName,
|
|
114
|
-
eventType,
|
|
115
|
-
verdict: findings.verdict,
|
|
116
|
-
risk: findings.risk,
|
|
117
|
-
findingCount: findings.findings.length,
|
|
118
|
-
findings: findings.findings,
|
|
119
|
-
timestamp: new Date().toISOString(),
|
|
120
|
-
};
|
|
121
|
-
|
|
122
|
-
this.emit('alert', result);
|
|
123
|
-
|
|
124
|
-
if (!this.quiet) {
|
|
125
|
-
const icon = findings.verdict === 'MALICIOUS' ? 'šØ' :
|
|
126
|
-
findings.verdict === 'SUSPICIOUS' ? 'ā ļø' : 'š¶';
|
|
127
|
-
console.log(`${icon} [${new Date().toLocaleTimeString()}] ${findings.verdict} ā ${filename}`);
|
|
128
|
-
console.log(` Risk: ${findings.risk} | Findings: ${findings.findings.length}`);
|
|
129
|
-
if (this.verbose) {
|
|
130
|
-
for (const f of findings.findings.slice(0, 5)) {
|
|
131
|
-
console.log(` ā [${f.severity}] ${f.id}: ${f.desc}`);
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
} else {
|
|
136
|
-
this.emit('clean', { file: filename, skill: skillName });
|
|
137
|
-
if (this.verbose && !this.quiet) {
|
|
138
|
-
console.log(`ā
[${new Date().toLocaleTimeString()}] CLEAN ā ${filename}`);
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
} catch (e) {
|
|
142
|
-
this.emit('error', { file: filename, error: e.message });
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
stop() {
|
|
147
|
-
this._running = false;
|
|
148
|
-
for (const w of this._watchers) {
|
|
149
|
-
try { w.close(); } catch (e) { /* ignore */ }
|
|
150
|
-
}
|
|
151
|
-
this._watchers = [];
|
|
152
|
-
for (const t of this._debounceTimers.values()) {
|
|
153
|
-
clearTimeout(t);
|
|
154
|
-
}
|
|
155
|
-
this._debounceTimers.clear();
|
|
156
|
-
this.emit('stopped', { scanCount: this._scanCount, alertCount: this._alertCount });
|
|
157
|
-
return { scanCount: this._scanCount, alertCount: this._alertCount };
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
getStats() {
|
|
161
|
-
return {
|
|
162
|
-
running: this._running,
|
|
163
|
-
scanCount: this._scanCount,
|
|
164
|
-
alertCount: this._alertCount,
|
|
165
|
-
watcherCount: this._watchers.length,
|
|
166
|
-
};
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
module.exports = { GuardWatcher };
|