openclaw-sentinel 0.1.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/LICENSE +21 -0
- package/README.md +318 -0
- package/dist/__tests__/alerts.test.d.ts +1 -0
- package/dist/__tests__/alerts.test.js +86 -0
- package/dist/__tests__/analyzer.test.d.ts +1 -0
- package/dist/__tests__/analyzer.test.js +287 -0
- package/dist/__tests__/watcher.test.d.ts +1 -0
- package/dist/__tests__/watcher.test.js +127 -0
- package/dist/alerts.d.ts +20 -0
- package/dist/alerts.js +35 -0
- package/dist/analyzer.d.ts +28 -0
- package/dist/analyzer.js +184 -0
- package/dist/config.d.ts +33 -0
- package/dist/config.js +33 -0
- package/dist/index.d.ts +19 -0
- package/dist/index.js +334 -0
- package/dist/osquery.d.ts +13 -0
- package/dist/osquery.js +162 -0
- package/dist/persistence.d.ts +27 -0
- package/dist/persistence.js +89 -0
- package/dist/watcher.d.ts +42 -0
- package/dist/watcher.js +104 -0
- package/launchd/com.openclaw.osqueryd.plist +34 -0
- package/openclaw.plugin.json +99 -0
- package/package.json +54 -0
- package/scripts/setup-daemon.sh +371 -0
- package/skills/sentinel/SKILL.md +142 -0
- package/systemd/openclaw-osqueryd.service +27 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 OpenClaw Contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
# 🛡️ OpenClaw Sentinel
|
|
2
|
+
|
|
3
|
+
OpenClaw agents run with elevated privileges on your machine — shell access, file operations, network connections. Sentinel continuously monitors for unauthorized access, suspicious processes, privilege escalation, and system anomalies, alerting you in real-time through any OpenClaw channel.
|
|
4
|
+
|
|
5
|
+
A security monitoring plugin for [OpenClaw](https://github.com/openclaw/openclaw), powered by [osquery](https://osquery.io).
|
|
6
|
+
|
|
7
|
+
## What it does
|
|
8
|
+
|
|
9
|
+
Sentinel watches your machine for suspicious activity and alerts you in real-time:
|
|
10
|
+
|
|
11
|
+
- **🔍 Process monitoring** — unsigned binaries, privilege escalation, suspicious commands
|
|
12
|
+
- **🔐 SSH monitoring** — logins from unknown hosts, brute force attempts
|
|
13
|
+
- **🌐 Network monitoring** — new listening ports, unexpected services
|
|
14
|
+
- **📁 File integrity** — changes to critical system files, new persistence mechanisms (LaunchDaemons, cron)
|
|
15
|
+
- **🚨 Smart alerting** — learns your baseline (known hosts, ports) and only alerts on anomalies
|
|
16
|
+
|
|
17
|
+
## Architecture
|
|
18
|
+
|
|
19
|
+
```
|
|
20
|
+
osqueryd (root daemon)
|
|
21
|
+
↓ writes JSON results
|
|
22
|
+
~/.openclaw/sentinel/logs/osquery/osqueryd.results.log
|
|
23
|
+
↓ tailed by
|
|
24
|
+
Sentinel watcher (fs.watch + poll fallback)
|
|
25
|
+
↓ parsed results
|
|
26
|
+
Analyzer (detection rules)
|
|
27
|
+
↓ high/critical events
|
|
28
|
+
OpenClaw → Signal/Slack/Telegram alert
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Sentinel **does not** run osqueryd itself (it requires root). You start osqueryd separately via `sudo` or `launchd`, and Sentinel tails its result logs.
|
|
32
|
+
|
|
33
|
+
## Prerequisites
|
|
34
|
+
|
|
35
|
+
- **macOS** (Apple Silicon or Intel) or **Linux** (systemd-based)
|
|
36
|
+
- [osquery](https://osquery.io) installed
|
|
37
|
+
- [OpenClaw](https://github.com/openclaw/openclaw) running
|
|
38
|
+
|
|
39
|
+
### Install osquery
|
|
40
|
+
|
|
41
|
+
**macOS (Homebrew):**
|
|
42
|
+
```bash
|
|
43
|
+
brew install --cask osquery
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
**macOS (manual):**
|
|
47
|
+
```bash
|
|
48
|
+
# Download the official .pkg from https://osquery.io/downloads
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
> **Note:** osquery needs **Full Disk Access** on macOS for the Endpoint Security framework. Grant it to `/opt/osquery/lib/osquery.app/Contents/MacOS/osqueryd` in System Settings → Privacy & Security → Full Disk Access.
|
|
52
|
+
|
|
53
|
+
**Linux (Debian/Ubuntu):**
|
|
54
|
+
```bash
|
|
55
|
+
wget -qO - https://pkg.osquery.io/deb/pubkey.gpg | sudo gpg --dearmor -o /usr/share/keyrings/osquery-archive-keyring.gpg
|
|
56
|
+
echo "deb [signed-by=/usr/share/keyrings/osquery-archive-keyring.gpg] https://pkg.osquery.io/deb deb main" | sudo tee /etc/apt/sources.list.d/osquery.list
|
|
57
|
+
sudo apt-get update && sudo apt-get install osquery
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
**Linux (RHEL/CentOS):**
|
|
61
|
+
```bash
|
|
62
|
+
curl -L https://pkg.osquery.io/rpm/GPG | sudo tee /etc/pki/rpm-gpg/RPM-GPG-KEY-osquery
|
|
63
|
+
sudo yum-config-manager --add-repo https://pkg.osquery.io/rpm/osquery-s3-rpm.repo
|
|
64
|
+
sudo yum install osquery
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## Installation
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
openclaw plugins install /path/to/openclaw-sentinel
|
|
71
|
+
openclaw gateway restart
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## Configuration
|
|
75
|
+
|
|
76
|
+
Add to your `~/.openclaw/openclaw.json` under `plugins.entries`:
|
|
77
|
+
|
|
78
|
+
```json
|
|
79
|
+
{
|
|
80
|
+
"plugins": {
|
|
81
|
+
"entries": {
|
|
82
|
+
"sentinel": {
|
|
83
|
+
"enabled": true,
|
|
84
|
+
"config": {
|
|
85
|
+
"osqueryPath": "/opt/osquery/lib/osquery.app/Contents/MacOS/osqueryi",
|
|
86
|
+
"logPath": "~/.openclaw/sentinel",
|
|
87
|
+
"alertChannel": "signal",
|
|
88
|
+
"alertTo": "+1234567890",
|
|
89
|
+
"alertSeverity": "high"
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
### Config options
|
|
98
|
+
|
|
99
|
+
| Option | Type | Default | Description |
|
|
100
|
+
|--------|------|---------|-------------|
|
|
101
|
+
| `osqueryPath` | string | auto-detect | Path to `osqueryi` binary |
|
|
102
|
+
| `logPath` | string | `~/.openclaw/sentinel` | Directory for sentinel data and osquery logs |
|
|
103
|
+
| `alertChannel` | string | — | Channel for alerts (`signal`, `slack`, `telegram`, etc.) |
|
|
104
|
+
| `alertTo` | string | — | Alert target (phone number, channel ID, etc.) |
|
|
105
|
+
| `alertSeverity` | string | `high` | Minimum severity to alert: `critical`, `high`, `medium`, `low`, `info` |
|
|
106
|
+
| `trustedSigningIds` | string[] | `[]` | Code signing IDs to skip (e.g. `com.apple`) |
|
|
107
|
+
| `trustedPaths` | string[] | `[]` | Binary paths to skip (e.g. `/usr/bin`, `/opt/homebrew/bin`) |
|
|
108
|
+
| `watchPaths` | string[] | `[]` | File paths to monitor for integrity changes |
|
|
109
|
+
| `enableProcessMonitor` | boolean | `true` | Monitor process execution events |
|
|
110
|
+
| `enableFileIntegrity` | boolean | `true` | Monitor file integrity events |
|
|
111
|
+
| `enableNetworkMonitor` | boolean | `true` | Monitor network connections |
|
|
112
|
+
| `pollIntervalMs` | number | `30000` | Fallback poll interval (ms) if fs.watch misses events |
|
|
113
|
+
|
|
114
|
+
## Starting osqueryd
|
|
115
|
+
|
|
116
|
+
Sentinel watches osqueryd's output — you need to start osqueryd separately. The included setup script handles everything.
|
|
117
|
+
|
|
118
|
+
### Automated setup (recommended)
|
|
119
|
+
|
|
120
|
+
```bash
|
|
121
|
+
sudo ./scripts/setup-daemon.sh
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
The script auto-detects your OS and will:
|
|
125
|
+
1. Find your osqueryd binary
|
|
126
|
+
2. Create the sentinel directory structure (`~/.openclaw/sentinel/`)
|
|
127
|
+
3. Generate a default osquery config if none exists
|
|
128
|
+
4. Install a system daemon:
|
|
129
|
+
- **macOS**: LaunchDaemon (`/Library/LaunchDaemons/com.openclaw.osqueryd.plist`)
|
|
130
|
+
- **Linux**: systemd unit (`/etc/systemd/system/openclaw-osqueryd.service`)
|
|
131
|
+
5. Start osqueryd — auto-starts on boot and restarts on crash
|
|
132
|
+
|
|
133
|
+
```bash
|
|
134
|
+
# macOS
|
|
135
|
+
sudo launchctl list com.openclaw.osqueryd
|
|
136
|
+
|
|
137
|
+
# Linux
|
|
138
|
+
sudo systemctl status openclaw-osqueryd
|
|
139
|
+
|
|
140
|
+
# Uninstall (both)
|
|
141
|
+
sudo ./scripts/setup-daemon.sh --uninstall
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
### Manual start (for testing)
|
|
145
|
+
|
|
146
|
+
```bash
|
|
147
|
+
SENTINEL_DIR=~/.openclaw/sentinel
|
|
148
|
+
|
|
149
|
+
sudo osqueryd \
|
|
150
|
+
--config_path=$SENTINEL_DIR/config/osquery.conf \
|
|
151
|
+
--database_path=$SENTINEL_DIR/db \
|
|
152
|
+
--logger_path=$SENTINEL_DIR/logs/osquery \
|
|
153
|
+
--pidfile=$SENTINEL_DIR/osqueryd.pid \
|
|
154
|
+
--logger_plugin=filesystem \
|
|
155
|
+
--disable_events=false \
|
|
156
|
+
--events_expiry=3600 \
|
|
157
|
+
--daemonize \
|
|
158
|
+
--force
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
### Full Disk Access
|
|
162
|
+
|
|
163
|
+
For Endpoint Security framework support (process events, file events), grant Full Disk Access:
|
|
164
|
+
|
|
165
|
+
**System Settings → Privacy & Security → Full Disk Access → Add osqueryd**
|
|
166
|
+
|
|
167
|
+
The path is typically `/opt/osquery/lib/osquery.app/Contents/MacOS/osqueryd`.
|
|
168
|
+
|
|
169
|
+
## Agent tools
|
|
170
|
+
|
|
171
|
+
Sentinel registers three tools your OpenClaw agent can use:
|
|
172
|
+
|
|
173
|
+
### `sentinel_status`
|
|
174
|
+
|
|
175
|
+
Get monitoring status — daemon state, event counts, known baseline.
|
|
176
|
+
|
|
177
|
+
### `sentinel_query`
|
|
178
|
+
|
|
179
|
+
Run ad-hoc osquery SQL for security investigation:
|
|
180
|
+
|
|
181
|
+
```
|
|
182
|
+
"Show me all listening ports"
|
|
183
|
+
→ sentinel_query: SELECT * FROM listening_ports WHERE port > 0;
|
|
184
|
+
|
|
185
|
+
"What processes are running as root?"
|
|
186
|
+
→ sentinel_query: SELECT name, path, cmdline FROM processes WHERE uid = 0;
|
|
187
|
+
|
|
188
|
+
"Any SSH keys on this machine?"
|
|
189
|
+
→ sentinel_query: SELECT * FROM user_ssh_keys;
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
### `sentinel_events`
|
|
193
|
+
|
|
194
|
+
Get recent security events, filterable by severity or category:
|
|
195
|
+
|
|
196
|
+
```
|
|
197
|
+
"Show me critical events"
|
|
198
|
+
→ sentinel_events: { severity: "critical" }
|
|
199
|
+
|
|
200
|
+
"Any SSH-related events?"
|
|
201
|
+
→ sentinel_events: { category: "ssh_login" }
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
## Usage examples
|
|
205
|
+
|
|
206
|
+
Just ask your agent in natural language through any OpenClaw channel (Signal, Slack, Discord, etc.):
|
|
207
|
+
|
|
208
|
+
**System overview:**
|
|
209
|
+
> "How's my machine looking security-wise?"
|
|
210
|
+
> "Any security alerts today?"
|
|
211
|
+
> "What's the sentinel status?"
|
|
212
|
+
|
|
213
|
+
**Network investigation:**
|
|
214
|
+
> "What ports are open on this machine?"
|
|
215
|
+
> "Show me all outbound connections"
|
|
216
|
+
> "Is anything phoning home to an IP I don't recognize?"
|
|
217
|
+
> "What's listening on port 5432?"
|
|
218
|
+
|
|
219
|
+
**Process investigation:**
|
|
220
|
+
> "What's running as root right now?"
|
|
221
|
+
> "Any unsigned binaries running?"
|
|
222
|
+
> "Show me recently started processes"
|
|
223
|
+
> "What launched in the last hour?"
|
|
224
|
+
|
|
225
|
+
**SSH & access:**
|
|
226
|
+
> "Who's logged into this machine?"
|
|
227
|
+
> "Any failed SSH attempts?"
|
|
228
|
+
> "Has anyone tried to brute force SSH?"
|
|
229
|
+
> "Show me all SSH keys on the system"
|
|
230
|
+
|
|
231
|
+
**Persistence & malware hunting:**
|
|
232
|
+
> "Are there any new LaunchDaemons I should know about?"
|
|
233
|
+
> "Show me all cron jobs"
|
|
234
|
+
> "Any changes to /etc/hosts or sudoers?"
|
|
235
|
+
> "What browser extensions are installed?"
|
|
236
|
+
|
|
237
|
+
**Forensics:**
|
|
238
|
+
> "What happened on this machine between 2am and 5am?"
|
|
239
|
+
> "Show me all shell history with sudo commands"
|
|
240
|
+
> "Which processes have the most open file descriptors?"
|
|
241
|
+
> "What DNS queries were made in the last hour?"
|
|
242
|
+
|
|
243
|
+
The agent translates these into osquery SQL, runs them through `sentinel_query`, and explains the results in plain English.
|
|
244
|
+
|
|
245
|
+
## Detection rules
|
|
246
|
+
|
|
247
|
+
| Category | Severity | Trigger |
|
|
248
|
+
|----------|----------|---------|
|
|
249
|
+
| Unsigned binary | high | Process executed without valid code signature |
|
|
250
|
+
| Privilege escalation | critical | `sudo`, `su`, `doas` with unexpected targets |
|
|
251
|
+
| Suspicious command | high | `curl \| sh`, `base64 -d`, `nc -l`, reverse shells |
|
|
252
|
+
| Unknown SSH login | high | SSH from IP not in baseline |
|
|
253
|
+
| SSH brute force | critical | 5+ failed auth attempts in short window |
|
|
254
|
+
| New listening port | medium | Port not seen during baseline scan |
|
|
255
|
+
| File integrity | high | Changes to watched paths |
|
|
256
|
+
| Persistence | high | New LaunchDaemon, LaunchAgent, or cron entry |
|
|
257
|
+
|
|
258
|
+
## How baseline works
|
|
259
|
+
|
|
260
|
+
On startup, Sentinel snapshots:
|
|
261
|
+
- All currently logged-in remote hosts → **known hosts**
|
|
262
|
+
- All currently listening ports → **known ports**
|
|
263
|
+
|
|
264
|
+
Future events are compared against this baseline. Only anomalies trigger alerts. The baseline refreshes each time the gateway restarts.
|
|
265
|
+
|
|
266
|
+
## Example alerts
|
|
267
|
+
|
|
268
|
+
```
|
|
269
|
+
🚨 SECURITY ALERT
|
|
270
|
+
Severity: HIGH
|
|
271
|
+
Category: ssh_login
|
|
272
|
+
Time: 2026-02-21 10:15:00
|
|
273
|
+
|
|
274
|
+
Unknown SSH login from 203.0.113.42
|
|
275
|
+
User: root | TTY: ttys003
|
|
276
|
+
|
|
277
|
+
This host is not in the known baseline.
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
```
|
|
281
|
+
🔴 SECURITY ALERT
|
|
282
|
+
Severity: CRITICAL
|
|
283
|
+
Category: privilege_escalation
|
|
284
|
+
Time: 2026-02-21 14:30:00
|
|
285
|
+
|
|
286
|
+
Privilege escalation detected
|
|
287
|
+
User: www → root | PID: 54321
|
|
288
|
+
Command: sudo /bin/bash
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
## Development
|
|
292
|
+
|
|
293
|
+
```bash
|
|
294
|
+
git clone https://github.com/sunil-sadasivan/openclaw-sentinel.git
|
|
295
|
+
cd openclaw-sentinel
|
|
296
|
+
npm install
|
|
297
|
+
npm run build # Compile TypeScript
|
|
298
|
+
npm run dev # Watch mode
|
|
299
|
+
|
|
300
|
+
# Install locally for testing
|
|
301
|
+
openclaw plugins install .
|
|
302
|
+
openclaw gateway restart
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
## Project structure
|
|
306
|
+
|
|
307
|
+
```
|
|
308
|
+
src/
|
|
309
|
+
├── index.ts # Plugin entry point — tool registration, watcher startup
|
|
310
|
+
├── config.ts # SentinelConfig interface, defaults, SecurityEvent types
|
|
311
|
+
├── osquery.ts # osquery binary discovery, SQL execution, config generation
|
|
312
|
+
├── analyzer.ts # Detection rules — processes, SSH, ports, files, persistence
|
|
313
|
+
└── watcher.ts # Event-driven log tailer (fs.watch + poll fallback)
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
## License
|
|
317
|
+
|
|
318
|
+
MIT
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { describe, it } from "node:test";
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
import { shouldAlert, meetsThreshold, createAlertState } from "../alerts.js";
|
|
4
|
+
function makeEvent(title, severity = "high") {
|
|
5
|
+
return {
|
|
6
|
+
id: "test",
|
|
7
|
+
timestamp: Date.now(),
|
|
8
|
+
severity,
|
|
9
|
+
category: "process",
|
|
10
|
+
title,
|
|
11
|
+
description: "test",
|
|
12
|
+
details: {},
|
|
13
|
+
hostname: "test",
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
describe("shouldAlert", () => {
|
|
17
|
+
it("allows first alert", () => {
|
|
18
|
+
const state = createAlertState();
|
|
19
|
+
assert.equal(shouldAlert(makeEvent("Test"), state), true);
|
|
20
|
+
});
|
|
21
|
+
it("deduplicates same title within 5 minutes", () => {
|
|
22
|
+
const state = createAlertState();
|
|
23
|
+
const now = Date.now();
|
|
24
|
+
assert.equal(shouldAlert(makeEvent("Same Event"), state, now), true);
|
|
25
|
+
assert.equal(shouldAlert(makeEvent("Same Event"), state, now + 1000), false);
|
|
26
|
+
assert.equal(shouldAlert(makeEvent("Same Event"), state, now + 60_000), false);
|
|
27
|
+
assert.equal(shouldAlert(makeEvent("Same Event"), state, now + 299_000), false);
|
|
28
|
+
});
|
|
29
|
+
it("allows same title after 5 minute window (entries expire from rate limit window)", () => {
|
|
30
|
+
const state = createAlertState();
|
|
31
|
+
const now = Date.now();
|
|
32
|
+
shouldAlert(makeEvent("Recurring"), state, now);
|
|
33
|
+
// After 5+ minutes AND after the 1-min rate limit window cleans up
|
|
34
|
+
assert.equal(shouldAlert(makeEvent("Recurring"), state, now + 301_000), true);
|
|
35
|
+
});
|
|
36
|
+
it("allows different titles", () => {
|
|
37
|
+
const state = createAlertState();
|
|
38
|
+
const now = Date.now();
|
|
39
|
+
assert.equal(shouldAlert(makeEvent("Event A"), state, now), true);
|
|
40
|
+
assert.equal(shouldAlert(makeEvent("Event B"), state, now), true);
|
|
41
|
+
assert.equal(shouldAlert(makeEvent("Event C"), state, now), true);
|
|
42
|
+
});
|
|
43
|
+
it("rate limits at 10 per minute", () => {
|
|
44
|
+
const state = createAlertState();
|
|
45
|
+
const now = Date.now();
|
|
46
|
+
for (let i = 0; i < 10; i++) {
|
|
47
|
+
assert.equal(shouldAlert(makeEvent(`Event ${i}`), state, now), true);
|
|
48
|
+
}
|
|
49
|
+
// 11th should be blocked
|
|
50
|
+
assert.equal(shouldAlert(makeEvent("Event 10"), state, now), false);
|
|
51
|
+
});
|
|
52
|
+
it("allows alerts again after rate limit window expires", () => {
|
|
53
|
+
const state = createAlertState();
|
|
54
|
+
const now = Date.now();
|
|
55
|
+
for (let i = 0; i < 10; i++) {
|
|
56
|
+
shouldAlert(makeEvent(`Event ${i}`), state, now);
|
|
57
|
+
}
|
|
58
|
+
// After 1 minute, rate limit resets
|
|
59
|
+
assert.equal(shouldAlert(makeEvent("New Event"), state, now + 61_000), true);
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
describe("meetsThreshold", () => {
|
|
63
|
+
it("critical meets all thresholds", () => {
|
|
64
|
+
assert.equal(meetsThreshold("critical", "info"), true);
|
|
65
|
+
assert.equal(meetsThreshold("critical", "low"), true);
|
|
66
|
+
assert.equal(meetsThreshold("critical", "medium"), true);
|
|
67
|
+
assert.equal(meetsThreshold("critical", "high"), true);
|
|
68
|
+
assert.equal(meetsThreshold("critical", "critical"), true);
|
|
69
|
+
});
|
|
70
|
+
it("info only meets info threshold", () => {
|
|
71
|
+
assert.equal(meetsThreshold("info", "info"), true);
|
|
72
|
+
assert.equal(meetsThreshold("info", "low"), false);
|
|
73
|
+
assert.equal(meetsThreshold("info", "medium"), false);
|
|
74
|
+
assert.equal(meetsThreshold("info", "high"), false);
|
|
75
|
+
});
|
|
76
|
+
it("high meets high and below", () => {
|
|
77
|
+
assert.equal(meetsThreshold("high", "high"), true);
|
|
78
|
+
assert.equal(meetsThreshold("high", "medium"), true);
|
|
79
|
+
assert.equal(meetsThreshold("high", "critical"), false);
|
|
80
|
+
});
|
|
81
|
+
it("defaults to high when invalid severity given", () => {
|
|
82
|
+
assert.equal(meetsThreshold("critical", "invalid"), true);
|
|
83
|
+
assert.equal(meetsThreshold("high", "invalid"), true);
|
|
84
|
+
assert.equal(meetsThreshold("medium", "invalid"), false);
|
|
85
|
+
});
|
|
86
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|