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
|
@@ -0,0 +1,371 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
#
|
|
3
|
+
# setup-daemon.sh — Install osqueryd as a system daemon for OpenClaw Sentinel
|
|
4
|
+
#
|
|
5
|
+
# Usage: sudo ./scripts/setup-daemon.sh [--uninstall]
|
|
6
|
+
#
|
|
7
|
+
# Supports macOS (launchd) and Linux (systemd).
|
|
8
|
+
# Starts osqueryd on boot with Sentinel's config.
|
|
9
|
+
#
|
|
10
|
+
|
|
11
|
+
set -euo pipefail
|
|
12
|
+
|
|
13
|
+
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
14
|
+
REPO_DIR="$(dirname "$SCRIPT_DIR")"
|
|
15
|
+
|
|
16
|
+
# Colors
|
|
17
|
+
RED='\033[0;31m'
|
|
18
|
+
GREEN='\033[0;32m'
|
|
19
|
+
YELLOW='\033[1;33m'
|
|
20
|
+
NC='\033[0m'
|
|
21
|
+
|
|
22
|
+
info() { echo -e "${GREEN}✓${NC} $1"; }
|
|
23
|
+
warn() { echo -e "${YELLOW}⚠${NC} $1"; }
|
|
24
|
+
error() { echo -e "${RED}✗${NC} $1"; }
|
|
25
|
+
|
|
26
|
+
# ── Detect OS ──
|
|
27
|
+
OS="$(uname -s)"
|
|
28
|
+
case "$OS" in
|
|
29
|
+
Darwin) INIT_SYSTEM="launchd" ;;
|
|
30
|
+
Linux)
|
|
31
|
+
if command -v systemctl &>/dev/null; then
|
|
32
|
+
INIT_SYSTEM="systemd"
|
|
33
|
+
else
|
|
34
|
+
error "Linux detected but systemd not found. Only systemd is supported."
|
|
35
|
+
exit 1
|
|
36
|
+
fi
|
|
37
|
+
;;
|
|
38
|
+
*)
|
|
39
|
+
error "Unsupported OS: $OS"
|
|
40
|
+
exit 1
|
|
41
|
+
;;
|
|
42
|
+
esac
|
|
43
|
+
|
|
44
|
+
# ── Common: find osqueryd ──
|
|
45
|
+
find_osqueryd() {
|
|
46
|
+
local candidates=(
|
|
47
|
+
/opt/osquery/lib/osquery.app/Contents/MacOS/osqueryd # macOS .pkg
|
|
48
|
+
/usr/local/bin/osqueryd
|
|
49
|
+
/opt/homebrew/bin/osqueryd
|
|
50
|
+
/usr/bin/osqueryd
|
|
51
|
+
/usr/sbin/osqueryd
|
|
52
|
+
)
|
|
53
|
+
for candidate in "${candidates[@]}"; do
|
|
54
|
+
if [[ -x "$candidate" ]]; then
|
|
55
|
+
echo "$candidate"
|
|
56
|
+
return
|
|
57
|
+
fi
|
|
58
|
+
done
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
# ── Common: find user and sentinel dir ──
|
|
62
|
+
find_sentinel_dir() {
|
|
63
|
+
local real_user="${SUDO_USER:-$(logname 2>/dev/null || echo '')}"
|
|
64
|
+
if [[ -z "$real_user" ]]; then
|
|
65
|
+
error "Cannot determine the real user. Run with: sudo $0"
|
|
66
|
+
exit 1
|
|
67
|
+
fi
|
|
68
|
+
|
|
69
|
+
local real_home
|
|
70
|
+
if [[ "$OS" == "Darwin" ]]; then
|
|
71
|
+
real_home=$(dscl . -read "/Users/$real_user" NFSHomeDirectory | awk '{print $2}')
|
|
72
|
+
else
|
|
73
|
+
real_home=$(getent passwd "$real_user" | cut -d: -f6)
|
|
74
|
+
fi
|
|
75
|
+
|
|
76
|
+
echo "$real_user" "$real_home" "${real_home}/.openclaw/sentinel"
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
# ── Common: create dirs and config ──
|
|
80
|
+
setup_sentinel_dir() {
|
|
81
|
+
local sentinel_dir="$1"
|
|
82
|
+
|
|
83
|
+
mkdir -p "$sentinel_dir/config"
|
|
84
|
+
mkdir -p "$sentinel_dir/db"
|
|
85
|
+
mkdir -p "$sentinel_dir/logs/osquery"
|
|
86
|
+
|
|
87
|
+
local config_file="$sentinel_dir/config/osquery.conf"
|
|
88
|
+
if [[ ! -f "$config_file" ]]; then
|
|
89
|
+
warn "No osquery config found — generating default"
|
|
90
|
+
|
|
91
|
+
# Try to use the Node.js config generator (single source of truth)
|
|
92
|
+
if command -v node &>/dev/null && [[ -f "$REPO_DIR/dist/osquery.js" ]]; then
|
|
93
|
+
node -e "
|
|
94
|
+
import('file://$REPO_DIR/dist/osquery.js').then(m => {
|
|
95
|
+
const config = m.generateOsqueryConfig({});
|
|
96
|
+
process.stdout.write(JSON.stringify(config, null, 2));
|
|
97
|
+
});
|
|
98
|
+
" > "$config_file" 2>/dev/null && {
|
|
99
|
+
info "Generated $config_file (from plugin source)"
|
|
100
|
+
return
|
|
101
|
+
}
|
|
102
|
+
fi
|
|
103
|
+
|
|
104
|
+
# Fallback: inline config
|
|
105
|
+
cat > "$config_file" << 'OSQUERY_CONF'
|
|
106
|
+
{
|
|
107
|
+
"options": {
|
|
108
|
+
"logger_plugin": "filesystem",
|
|
109
|
+
"disable_events": "false",
|
|
110
|
+
"events_expiry": "3600",
|
|
111
|
+
"events_max": "100000"
|
|
112
|
+
},
|
|
113
|
+
"schedule": {
|
|
114
|
+
"process_events": {
|
|
115
|
+
"query": "SELECT pid, path, cmdline, uid, euid, username, signing_id, team_id, platform_binary, event_type, time FROM es_process_events WHERE event_type = 'exec';",
|
|
116
|
+
"interval": 30,
|
|
117
|
+
"description": "Process execution events from Endpoint Security"
|
|
118
|
+
},
|
|
119
|
+
"logged_in_users": {
|
|
120
|
+
"query": "SELECT type, user, host, time, pid FROM logged_in_users;",
|
|
121
|
+
"interval": 60,
|
|
122
|
+
"description": "Currently logged-in users"
|
|
123
|
+
},
|
|
124
|
+
"listening_ports": {
|
|
125
|
+
"query": "SELECT lp.port, lp.address, lp.protocol, p.name, p.path, p.cmdline FROM listening_ports lp JOIN processes p ON lp.pid = p.pid WHERE lp.port > 0;",
|
|
126
|
+
"interval": 120,
|
|
127
|
+
"description": "Listening network ports with process info"
|
|
128
|
+
},
|
|
129
|
+
"failed_auth": {
|
|
130
|
+
"query": "SELECT time, message FROM asl WHERE facility = 'auth' AND level <= 3 AND (message LIKE '%authentication error%' OR message LIKE '%Failed password%' OR message LIKE '%Invalid user%') ORDER BY time DESC LIMIT 50;",
|
|
131
|
+
"interval": 60,
|
|
132
|
+
"description": "Failed authentication attempts"
|
|
133
|
+
},
|
|
134
|
+
"launch_daemons": {
|
|
135
|
+
"query": "SELECT name, path, program, program_arguments, run_at_load FROM launchd WHERE path LIKE '/Library/LaunchDaemons/%' OR path LIKE '/Library/LaunchAgents/%';",
|
|
136
|
+
"interval": 300,
|
|
137
|
+
"description": "LaunchDaemons and LaunchAgents"
|
|
138
|
+
},
|
|
139
|
+
"shell_history": {
|
|
140
|
+
"query": "SELECT uid, command, time FROM shell_history WHERE command LIKE '%sudo%' OR command LIKE '%chmod%' OR command LIKE '%chown%' ORDER BY time DESC LIMIT 20;",
|
|
141
|
+
"interval": 60,
|
|
142
|
+
"description": "Shell commands involving privilege changes"
|
|
143
|
+
},
|
|
144
|
+
"ssh_keys": {
|
|
145
|
+
"query": "SELECT uid, path, encrypted FROM user_ssh_keys;",
|
|
146
|
+
"interval": 300,
|
|
147
|
+
"description": "SSH keys on the system"
|
|
148
|
+
},
|
|
149
|
+
"open_sockets": {
|
|
150
|
+
"query": "SELECT p.name, p.path, pos.remote_address, pos.remote_port, pos.local_port, pos.protocol FROM process_open_sockets pos JOIN processes p ON pos.pid = p.pid WHERE pos.remote_address != '' AND pos.remote_address != '127.0.0.1' AND pos.remote_address != '::1' AND pos.remote_address != '0.0.0.0' LIMIT 50;",
|
|
151
|
+
"interval": 120,
|
|
152
|
+
"description": "Outbound network connections"
|
|
153
|
+
}
|
|
154
|
+
},
|
|
155
|
+
"decorators": {
|
|
156
|
+
"load": ["SELECT hostname FROM system_info;"]
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
OSQUERY_CONF
|
|
160
|
+
info "Generated $config_file (inline fallback)"
|
|
161
|
+
fi
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
# ── Common: kill existing manual osqueryd ──
|
|
165
|
+
kill_existing() {
|
|
166
|
+
local sentinel_dir="$1"
|
|
167
|
+
if [[ -f "$sentinel_dir/osqueryd.pid" ]]; then
|
|
168
|
+
local old_pid
|
|
169
|
+
old_pid=$(cat "$sentinel_dir/osqueryd.pid" 2>/dev/null || echo "")
|
|
170
|
+
if [[ -n "$old_pid" ]] && kill -0 "$old_pid" 2>/dev/null; then
|
|
171
|
+
warn "Killing existing osqueryd (pid $old_pid)..."
|
|
172
|
+
kill "$old_pid" 2>/dev/null || true
|
|
173
|
+
sleep 1
|
|
174
|
+
fi
|
|
175
|
+
fi
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
# ════════════════════════════════════════════
|
|
179
|
+
# macOS (launchd)
|
|
180
|
+
# ════════════════════════════════════════════
|
|
181
|
+
|
|
182
|
+
PLIST_NAME="com.openclaw.osqueryd"
|
|
183
|
+
PLIST_DEST="/Library/LaunchDaemons/${PLIST_NAME}.plist"
|
|
184
|
+
PLIST_TEMPLATE="${REPO_DIR}/launchd/${PLIST_NAME}.plist"
|
|
185
|
+
|
|
186
|
+
launchd_uninstall() {
|
|
187
|
+
echo "Uninstalling osqueryd daemon (launchd)..."
|
|
188
|
+
if launchctl list "$PLIST_NAME" &>/dev/null; then
|
|
189
|
+
launchctl unload "$PLIST_DEST" 2>/dev/null || true
|
|
190
|
+
info "Daemon stopped"
|
|
191
|
+
fi
|
|
192
|
+
if [[ -f "$PLIST_DEST" ]]; then
|
|
193
|
+
rm "$PLIST_DEST"
|
|
194
|
+
info "Removed $PLIST_DEST"
|
|
195
|
+
fi
|
|
196
|
+
echo "Done."
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
launchd_install() {
|
|
200
|
+
local osqueryd="$1" sentinel_dir="$2"
|
|
201
|
+
|
|
202
|
+
if [[ ! -f "$PLIST_TEMPLATE" ]]; then
|
|
203
|
+
error "Plist template not found: $PLIST_TEMPLATE"
|
|
204
|
+
exit 1
|
|
205
|
+
fi
|
|
206
|
+
|
|
207
|
+
# Stop existing
|
|
208
|
+
if launchctl list "$PLIST_NAME" &>/dev/null; then
|
|
209
|
+
warn "Stopping existing daemon..."
|
|
210
|
+
launchctl unload "$PLIST_DEST" 2>/dev/null || true
|
|
211
|
+
fi
|
|
212
|
+
|
|
213
|
+
# Install plist
|
|
214
|
+
sed \
|
|
215
|
+
-e "s|__OSQUERYD_PATH__|${osqueryd}|g" \
|
|
216
|
+
-e "s|__SENTINEL_DIR__|${sentinel_dir}|g" \
|
|
217
|
+
"$PLIST_TEMPLATE" > "$PLIST_DEST"
|
|
218
|
+
|
|
219
|
+
chmod 644 "$PLIST_DEST"
|
|
220
|
+
chown root:wheel "$PLIST_DEST"
|
|
221
|
+
info "Installed $PLIST_DEST"
|
|
222
|
+
|
|
223
|
+
# Load
|
|
224
|
+
launchctl load "$PLIST_DEST"
|
|
225
|
+
sleep 2
|
|
226
|
+
|
|
227
|
+
if launchctl list "$PLIST_NAME" &>/dev/null; then
|
|
228
|
+
info "osqueryd daemon is running (launchd)"
|
|
229
|
+
else
|
|
230
|
+
error "Daemon failed to start. Check: $sentinel_dir/logs/osqueryd-stderr.log"
|
|
231
|
+
exit 1
|
|
232
|
+
fi
|
|
233
|
+
|
|
234
|
+
echo ""
|
|
235
|
+
echo -e "${GREEN}Done!${NC} osqueryd is running as a launchd daemon."
|
|
236
|
+
echo ""
|
|
237
|
+
echo "Important: Grant Full Disk Access to osqueryd for Endpoint Security:"
|
|
238
|
+
echo " System Settings → Privacy & Security → Full Disk Access"
|
|
239
|
+
echo " Add: $osqueryd"
|
|
240
|
+
echo ""
|
|
241
|
+
echo "Commands:"
|
|
242
|
+
echo " Status: sudo launchctl list $PLIST_NAME"
|
|
243
|
+
echo " Stop: sudo launchctl unload $PLIST_DEST"
|
|
244
|
+
echo " Start: sudo launchctl load $PLIST_DEST"
|
|
245
|
+
echo " Uninstall: sudo $0 --uninstall"
|
|
246
|
+
echo " Logs: tail -f $sentinel_dir/logs/osquery/osqueryd.results.log"
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
# ════════════════════════════════════════════
|
|
250
|
+
# Linux (systemd)
|
|
251
|
+
# ════════════════════════════════════════════
|
|
252
|
+
|
|
253
|
+
SYSTEMD_UNIT="openclaw-osqueryd"
|
|
254
|
+
SYSTEMD_DEST="/etc/systemd/system/${SYSTEMD_UNIT}.service"
|
|
255
|
+
SYSTEMD_TEMPLATE="${REPO_DIR}/systemd/${SYSTEMD_UNIT}.service"
|
|
256
|
+
|
|
257
|
+
systemd_uninstall() {
|
|
258
|
+
echo "Uninstalling osqueryd daemon (systemd)..."
|
|
259
|
+
if systemctl is-active "$SYSTEMD_UNIT" &>/dev/null; then
|
|
260
|
+
systemctl stop "$SYSTEMD_UNIT"
|
|
261
|
+
info "Daemon stopped"
|
|
262
|
+
fi
|
|
263
|
+
if systemctl is-enabled "$SYSTEMD_UNIT" &>/dev/null; then
|
|
264
|
+
systemctl disable "$SYSTEMD_UNIT"
|
|
265
|
+
info "Daemon disabled"
|
|
266
|
+
fi
|
|
267
|
+
if [[ -f "$SYSTEMD_DEST" ]]; then
|
|
268
|
+
rm "$SYSTEMD_DEST"
|
|
269
|
+
systemctl daemon-reload
|
|
270
|
+
info "Removed $SYSTEMD_DEST"
|
|
271
|
+
fi
|
|
272
|
+
echo "Done."
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
systemd_install() {
|
|
276
|
+
local osqueryd="$1" sentinel_dir="$2"
|
|
277
|
+
|
|
278
|
+
if [[ ! -f "$SYSTEMD_TEMPLATE" ]]; then
|
|
279
|
+
error "Systemd template not found: $SYSTEMD_TEMPLATE"
|
|
280
|
+
exit 1
|
|
281
|
+
fi
|
|
282
|
+
|
|
283
|
+
# Stop existing
|
|
284
|
+
if systemctl is-active "$SYSTEMD_UNIT" &>/dev/null; then
|
|
285
|
+
warn "Stopping existing daemon..."
|
|
286
|
+
systemctl stop "$SYSTEMD_UNIT"
|
|
287
|
+
fi
|
|
288
|
+
|
|
289
|
+
# Install unit
|
|
290
|
+
sed \
|
|
291
|
+
-e "s|__OSQUERYD_PATH__|${osqueryd}|g" \
|
|
292
|
+
-e "s|__SENTINEL_DIR__|${sentinel_dir}|g" \
|
|
293
|
+
"$SYSTEMD_TEMPLATE" > "$SYSTEMD_DEST"
|
|
294
|
+
|
|
295
|
+
chmod 644 "$SYSTEMD_DEST"
|
|
296
|
+
info "Installed $SYSTEMD_DEST"
|
|
297
|
+
|
|
298
|
+
systemctl daemon-reload
|
|
299
|
+
systemctl enable "$SYSTEMD_UNIT"
|
|
300
|
+
systemctl start "$SYSTEMD_UNIT"
|
|
301
|
+
|
|
302
|
+
sleep 2
|
|
303
|
+
|
|
304
|
+
if systemctl is-active "$SYSTEMD_UNIT" &>/dev/null; then
|
|
305
|
+
info "osqueryd daemon is running (systemd)"
|
|
306
|
+
else
|
|
307
|
+
error "Daemon failed to start. Check: journalctl -u $SYSTEMD_UNIT"
|
|
308
|
+
exit 1
|
|
309
|
+
fi
|
|
310
|
+
|
|
311
|
+
echo ""
|
|
312
|
+
echo -e "${GREEN}Done!${NC} osqueryd is running as a systemd service."
|
|
313
|
+
echo ""
|
|
314
|
+
echo "Commands:"
|
|
315
|
+
echo " Status: sudo systemctl status $SYSTEMD_UNIT"
|
|
316
|
+
echo " Stop: sudo systemctl stop $SYSTEMD_UNIT"
|
|
317
|
+
echo " Start: sudo systemctl start $SYSTEMD_UNIT"
|
|
318
|
+
echo " Logs: journalctl -u $SYSTEMD_UNIT -f"
|
|
319
|
+
echo " Uninstall: sudo $0 --uninstall"
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
# ════════════════════════════════════════════
|
|
323
|
+
# Main
|
|
324
|
+
# ════════════════════════════════════════════
|
|
325
|
+
|
|
326
|
+
# Handle --uninstall
|
|
327
|
+
if [[ "${1:-}" == "--uninstall" ]]; then
|
|
328
|
+
if [[ $EUID -ne 0 ]]; then
|
|
329
|
+
error "This script must be run as root (sudo)"
|
|
330
|
+
exit 1
|
|
331
|
+
fi
|
|
332
|
+
case "$INIT_SYSTEM" in
|
|
333
|
+
launchd) launchd_uninstall ;;
|
|
334
|
+
systemd) systemd_uninstall ;;
|
|
335
|
+
esac
|
|
336
|
+
exit 0
|
|
337
|
+
fi
|
|
338
|
+
|
|
339
|
+
# Preflight
|
|
340
|
+
if [[ $EUID -ne 0 ]]; then
|
|
341
|
+
error "This script must be run as root (sudo)"
|
|
342
|
+
echo " Usage: sudo $0"
|
|
343
|
+
exit 1
|
|
344
|
+
fi
|
|
345
|
+
|
|
346
|
+
# Find osqueryd
|
|
347
|
+
OSQUERYD=$(find_osqueryd)
|
|
348
|
+
if [[ -z "$OSQUERYD" ]]; then
|
|
349
|
+
error "osqueryd not found."
|
|
350
|
+
echo " Install from: https://osquery.io/downloads"
|
|
351
|
+
echo " macOS: download the .pkg installer"
|
|
352
|
+
echo " Linux: see https://osquery.io/downloads/official"
|
|
353
|
+
exit 1
|
|
354
|
+
fi
|
|
355
|
+
info "Found osqueryd: $OSQUERYD"
|
|
356
|
+
|
|
357
|
+
# Find sentinel dir
|
|
358
|
+
read -r REAL_USER REAL_HOME SENTINEL_DIR <<< "$(find_sentinel_dir)"
|
|
359
|
+
info "User: $REAL_USER"
|
|
360
|
+
info "Sentinel dir: $SENTINEL_DIR"
|
|
361
|
+
info "Init system: $INIT_SYSTEM"
|
|
362
|
+
|
|
363
|
+
# Setup
|
|
364
|
+
setup_sentinel_dir "$SENTINEL_DIR"
|
|
365
|
+
kill_existing "$SENTINEL_DIR"
|
|
366
|
+
|
|
367
|
+
# Install
|
|
368
|
+
case "$INIT_SYSTEM" in
|
|
369
|
+
launchd) launchd_install "$OSQUERYD" "$SENTINEL_DIR" ;;
|
|
370
|
+
systemd) systemd_install "$OSQUERYD" "$SENTINEL_DIR" ;;
|
|
371
|
+
esac
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
# Sentinel — Endpoint Security Monitoring
|
|
2
|
+
|
|
3
|
+
You have access to real-time endpoint security monitoring via three tools powered by [osquery](https://osquery.io).
|
|
4
|
+
|
|
5
|
+
## Tools
|
|
6
|
+
|
|
7
|
+
### `sentinel_status`
|
|
8
|
+
Check if monitoring is active, how many events have been detected, and the current baseline (known hosts/ports). Call this first when investigating security concerns.
|
|
9
|
+
|
|
10
|
+
### `sentinel_events`
|
|
11
|
+
Get recent security events. Filter by severity (`critical`, `high`, `medium`, `low`, `info`) or category (`process`, `network`, `file`, `auth`, `privilege`). Use this to review what Sentinel has flagged.
|
|
12
|
+
|
|
13
|
+
### `sentinel_query`
|
|
14
|
+
Run ad-hoc osquery SQL for deeper investigation. osquery exposes 200+ virtual tables backed by live OS APIs — every query returns real-time system state, not cached data.
|
|
15
|
+
|
|
16
|
+
**Blocked tables** (security risk): `carves`, `curl`, `curl_certificate`
|
|
17
|
+
|
|
18
|
+
## When to use these tools
|
|
19
|
+
|
|
20
|
+
- User asks about security, open ports, running processes, SSH connections, or system health
|
|
21
|
+
- During heartbeat security checks
|
|
22
|
+
- Investigating suspicious activity or alerts
|
|
23
|
+
- Auditing system configuration (firewall, SSH keys, launch daemons)
|
|
24
|
+
|
|
25
|
+
## Common investigation queries
|
|
26
|
+
|
|
27
|
+
### System overview
|
|
28
|
+
```sql
|
|
29
|
+
-- Who's logged in right now?
|
|
30
|
+
SELECT type, user, host, time, pid FROM logged_in_users;
|
|
31
|
+
|
|
32
|
+
-- What's listening on the network?
|
|
33
|
+
SELECT lp.port, lp.protocol, lp.address, p.name, p.path
|
|
34
|
+
FROM listening_ports lp JOIN processes p ON lp.pid = p.pid
|
|
35
|
+
WHERE lp.port > 0 ORDER BY lp.port;
|
|
36
|
+
|
|
37
|
+
-- What processes are running as root?
|
|
38
|
+
SELECT pid, name, path, cmdline FROM processes WHERE uid = 0 ORDER BY start_time DESC LIMIT 30;
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
### SSH & authentication
|
|
42
|
+
```sql
|
|
43
|
+
-- SSH keys on this machine
|
|
44
|
+
SELECT uid, path, encrypted FROM user_ssh_keys;
|
|
45
|
+
|
|
46
|
+
-- Authorized keys (who can SSH in?)
|
|
47
|
+
SELECT * FROM authorized_keys;
|
|
48
|
+
|
|
49
|
+
-- SSH config
|
|
50
|
+
SELECT * FROM ssh_configs;
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### Persistence mechanisms
|
|
54
|
+
```sql
|
|
55
|
+
-- Launch daemons and agents (macOS)
|
|
56
|
+
SELECT name, path, program, program_arguments, run_at_load
|
|
57
|
+
FROM launchd
|
|
58
|
+
WHERE path LIKE '/Library/LaunchDaemons/%' OR path LIKE '/Library/LaunchAgents/%'
|
|
59
|
+
OR path LIKE '%/Library/LaunchAgents/%';
|
|
60
|
+
|
|
61
|
+
-- Cron jobs
|
|
62
|
+
SELECT * FROM crontab;
|
|
63
|
+
|
|
64
|
+
-- Startup items (Linux)
|
|
65
|
+
SELECT name, path, source FROM startup_items;
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### Process investigation
|
|
69
|
+
```sql
|
|
70
|
+
-- Processes with open network connections
|
|
71
|
+
SELECT p.name, p.path, pos.remote_address, pos.remote_port, pos.local_port
|
|
72
|
+
FROM process_open_sockets pos JOIN processes p ON pos.pid = p.pid
|
|
73
|
+
WHERE pos.remote_address != '' AND pos.remote_address != '127.0.0.1'
|
|
74
|
+
AND pos.remote_address != '::1' AND pos.remote_address != '0.0.0.0'
|
|
75
|
+
ORDER BY p.name;
|
|
76
|
+
|
|
77
|
+
-- Find a specific process
|
|
78
|
+
SELECT pid, name, path, cmdline, uid, parent FROM processes WHERE name LIKE '%suspicious%';
|
|
79
|
+
|
|
80
|
+
-- Process tree (who spawned what)
|
|
81
|
+
SELECT p.pid, p.name, p.path, p.cmdline, pp.name AS parent_name
|
|
82
|
+
FROM processes p LEFT JOIN processes pp ON p.parent = pp.pid
|
|
83
|
+
WHERE p.uid = 0 ORDER BY p.start_time DESC LIMIT 20;
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
### File integrity & system config
|
|
87
|
+
```sql
|
|
88
|
+
-- Check a specific file's hash
|
|
89
|
+
SELECT path, sha256 FROM hash WHERE path = '/etc/hosts';
|
|
90
|
+
|
|
91
|
+
-- Firewall status (macOS)
|
|
92
|
+
SELECT * FROM alf;
|
|
93
|
+
SELECT * FROM alf_exceptions;
|
|
94
|
+
|
|
95
|
+
-- Disk encryption
|
|
96
|
+
SELECT * FROM disk_encryption;
|
|
97
|
+
|
|
98
|
+
-- System info
|
|
99
|
+
SELECT hostname, cpu_type, hardware_model, physical_memory FROM system_info;
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### Network investigation
|
|
103
|
+
```sql
|
|
104
|
+
-- DNS resolvers
|
|
105
|
+
SELECT * FROM dns_resolvers;
|
|
106
|
+
|
|
107
|
+
-- ARP table (who's on the local network)
|
|
108
|
+
SELECT address, mac, interface FROM arp_cache;
|
|
109
|
+
|
|
110
|
+
-- Interfaces
|
|
111
|
+
SELECT interface, address, mask, type FROM interface_addresses WHERE address != '';
|
|
112
|
+
|
|
113
|
+
-- Routes
|
|
114
|
+
SELECT destination, gateway, interface FROM routes WHERE destination != '::1';
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
## Interpreting results
|
|
118
|
+
|
|
119
|
+
- **Unsigned binaries** from `/tmp`, `/var/tmp`, or user home dirs are suspicious
|
|
120
|
+
- **Root processes** you don't recognize warrant investigation
|
|
121
|
+
- **Listening ports** on `0.0.0.0` (all interfaces) are externally accessible — only expected for intentional services
|
|
122
|
+
- **SSH logins** from IPs outside your Tailscale range (`100.64.0.0/10`) or known hosts are flagged
|
|
123
|
+
- **New LaunchDaemons/LaunchAgents** could be persistence mechanisms
|
|
124
|
+
- **Failed auth attempts** — 3+ in a minute suggests targeted access attempts, 10+ suggests brute force
|
|
125
|
+
|
|
126
|
+
## Alert severity levels
|
|
127
|
+
|
|
128
|
+
| Severity | Meaning | Examples |
|
|
129
|
+
|----------|---------|---------|
|
|
130
|
+
| 🚨 critical | Immediate threat | Brute force attack, critical file modified (/etc/sudoers) |
|
|
131
|
+
| 🔴 high | Likely malicious | Unsigned binary, privilege escalation, unknown SSH login, persistence change |
|
|
132
|
+
| 🟡 medium | Unusual activity | New listening port, single failed login, suspicious command |
|
|
133
|
+
| 🔵 low | Informational | Minor config changes |
|
|
134
|
+
| ℹ️ info | Baseline data | Normal system activity |
|
|
135
|
+
|
|
136
|
+
## Tips
|
|
137
|
+
|
|
138
|
+
- Always check `sentinel_status` first to confirm monitoring is active
|
|
139
|
+
- Use `sentinel_events` before running ad-hoc queries — Sentinel may have already flagged the issue
|
|
140
|
+
- For recurring checks, suggest the user add items to their HEARTBEAT.md
|
|
141
|
+
- When reporting findings, distinguish between **confirmed threats** and **unusual but potentially benign** activity
|
|
142
|
+
- If osqueryd is not running, suggest: `sudo ./scripts/setup-daemon.sh` from the plugin directory
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
[Unit]
|
|
2
|
+
Description=osqueryd daemon for OpenClaw Sentinel
|
|
3
|
+
Documentation=https://github.com/sunil-sadasivan/openclaw-sentinel
|
|
4
|
+
After=network.target
|
|
5
|
+
|
|
6
|
+
[Service]
|
|
7
|
+
Type=simple
|
|
8
|
+
ExecStart=__OSQUERYD_PATH__ \
|
|
9
|
+
--config_path=__SENTINEL_DIR__/config/osquery.conf \
|
|
10
|
+
--database_path=__SENTINEL_DIR__/db \
|
|
11
|
+
--logger_path=__SENTINEL_DIR__/logs/osquery \
|
|
12
|
+
--pidfile=__SENTINEL_DIR__/osqueryd.pid \
|
|
13
|
+
--logger_plugin=filesystem \
|
|
14
|
+
--disable_events=false \
|
|
15
|
+
--events_expiry=3600 \
|
|
16
|
+
--events_max=100000 \
|
|
17
|
+
--logger_rotate=true \
|
|
18
|
+
--logger_max_log_size=52428800 \
|
|
19
|
+
--force
|
|
20
|
+
Restart=on-failure
|
|
21
|
+
RestartSec=10
|
|
22
|
+
StandardOutput=journal
|
|
23
|
+
StandardError=journal
|
|
24
|
+
SyslogIdentifier=openclaw-osqueryd
|
|
25
|
+
|
|
26
|
+
[Install]
|
|
27
|
+
WantedBy=multi-user.target
|