agentsentinel-cli 0.5.2__tar.gz → 0.5.4__tar.gz
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.
- {agentsentinel_cli-0.5.2 → agentsentinel_cli-0.5.4}/DOCUMENTATION.md +369 -9
- {agentsentinel_cli-0.5.2 → agentsentinel_cli-0.5.4}/PKG-INFO +112 -4
- {agentsentinel_cli-0.5.2 → agentsentinel_cli-0.5.4}/README.md +111 -3
- {agentsentinel_cli-0.5.2 → agentsentinel_cli-0.5.4}/agentsentinel_cli/cli.py +21 -1
- {agentsentinel_cli-0.5.2 → agentsentinel_cli-0.5.4}/agentsentinel_cli/secrets.py +103 -24
- {agentsentinel_cli-0.5.2 → agentsentinel_cli-0.5.4}/agentsentinel_cli/secrets_report.py +2 -1
- {agentsentinel_cli-0.5.2 → agentsentinel_cli-0.5.4}/agentsentinel_cli/secrets_rules.py +5 -4
- {agentsentinel_cli-0.5.2 → agentsentinel_cli-0.5.4}/pyproject.toml +1 -1
- {agentsentinel_cli-0.5.2 → agentsentinel_cli-0.5.4}/.gitignore +0 -0
- {agentsentinel_cli-0.5.2 → agentsentinel_cli-0.5.4}/agentsentinel_cli/__init__.py +0 -0
- {agentsentinel_cli-0.5.2 → agentsentinel_cli-0.5.4}/agentsentinel_cli/ai_probe.py +0 -0
- {agentsentinel_cli-0.5.2 → agentsentinel_cli-0.5.4}/agentsentinel_cli/attacks/__init__.py +0 -0
- {agentsentinel_cli-0.5.2 → agentsentinel_cli-0.5.4}/agentsentinel_cli/attacks/library.py +0 -0
- {agentsentinel_cli-0.5.2 → agentsentinel_cli-0.5.4}/agentsentinel_cli/discover.py +0 -0
- {agentsentinel_cli-0.5.2 → agentsentinel_cli-0.5.4}/agentsentinel_cli/discover_report.py +0 -0
- {agentsentinel_cli-0.5.2 → agentsentinel_cli-0.5.4}/agentsentinel_cli/fingerprint.py +0 -0
- {agentsentinel_cli-0.5.2 → agentsentinel_cli-0.5.4}/agentsentinel_cli/frameworks.py +0 -0
- {agentsentinel_cli-0.5.2 → agentsentinel_cli-0.5.4}/agentsentinel_cli/inspect.py +0 -0
- {agentsentinel_cli-0.5.2 → agentsentinel_cli-0.5.4}/agentsentinel_cli/inspect_report.py +0 -0
- {agentsentinel_cli-0.5.2 → agentsentinel_cli-0.5.4}/agentsentinel_cli/mcp_client.py +0 -0
- {agentsentinel_cli-0.5.2 → agentsentinel_cli-0.5.4}/agentsentinel_cli/mcp_report.py +0 -0
- {agentsentinel_cli-0.5.2 → agentsentinel_cli-0.5.4}/agentsentinel_cli/mcp_rules.py +0 -0
- {agentsentinel_cli-0.5.2 → agentsentinel_cli-0.5.4}/agentsentinel_cli/probe.py +0 -0
- {agentsentinel_cli-0.5.2 → agentsentinel_cli-0.5.4}/agentsentinel_cli/probe_report.py +0 -0
- {agentsentinel_cli-0.5.2 → agentsentinel_cli-0.5.4}/agentsentinel_cli/report.py +0 -0
- {agentsentinel_cli-0.5.2 → agentsentinel_cli-0.5.4}/agentsentinel_cli/rules.py +0 -0
- {agentsentinel_cli-0.5.2 → agentsentinel_cli-0.5.4}/agentsentinel_cli/scanner.py +0 -0
- {agentsentinel_cli-0.5.2 → agentsentinel_cli-0.5.4}/agentsentinel_cli/target.py +0 -0
|
@@ -15,6 +15,7 @@ No server required. No Docker. Works on any Python agent file or live HTTP endpo
|
|
|
15
15
|
- [Commands](#commands)
|
|
16
16
|
- [sentinel inspect](#sentinel-inspect)
|
|
17
17
|
- [sentinel scan](#sentinel-scan)
|
|
18
|
+
- [sentinel secrets](#sentinel-secrets)
|
|
18
19
|
- [sentinel discover](#sentinel-discover)
|
|
19
20
|
- [sentinel mcp scan](#sentinel-mcp-scan)
|
|
20
21
|
- [sentinel probe](#sentinel-probe)
|
|
@@ -68,7 +69,7 @@ sentinel --version
|
|
|
68
69
|
|
|
69
70
|
## Quick Start
|
|
70
71
|
|
|
71
|
-
|
|
72
|
+
Six commands that cover the full picture in under 10 minutes:
|
|
72
73
|
|
|
73
74
|
```bash
|
|
74
75
|
# 1. What is this agent? (fingerprint + plain English summary)
|
|
@@ -77,13 +78,16 @@ sentinel inspect my_agent.py
|
|
|
77
78
|
# 2. Does it have dangerous permissions? (posture audit)
|
|
78
79
|
sentinel scan my_agent.py
|
|
79
80
|
|
|
80
|
-
# 3.
|
|
81
|
+
# 3. Has it leaked credentials or customer PII into memory files?
|
|
82
|
+
sentinel secrets .
|
|
83
|
+
|
|
84
|
+
# 4. Is the MCP server it connects to secure?
|
|
81
85
|
sentinel mcp scan http://localhost:3000
|
|
82
86
|
|
|
83
|
-
#
|
|
87
|
+
# 5. Can it be jailbroken? (42-payload attack battery)
|
|
84
88
|
sentinel probe http://my-agent.com/chat
|
|
85
89
|
|
|
86
|
-
#
|
|
90
|
+
# 6. Deep red-team with Claude as the attacker (needs ANTHROPIC_API_KEY)
|
|
87
91
|
sentinel ai-probe http://my-agent.com/chat
|
|
88
92
|
```
|
|
89
93
|
|
|
@@ -281,6 +285,323 @@ sentinel scan my_agent.py --connect http://localhost:9000 --api-key $AGENTSENTIN
|
|
|
281
285
|
|
|
282
286
|
---
|
|
283
287
|
|
|
288
|
+
### sentinel secrets
|
|
289
|
+
|
|
290
|
+
**What problem it solves:** AI agents process sensitive data — customer records, credentials,
|
|
291
|
+
system prompts — and many frameworks persist this to local memory files (`.md`, `.json`,
|
|
292
|
+
conversation logs). Developers commit these files to git without realising they contain
|
|
293
|
+
customer NRICs, email addresses, or API keys captured from tool call results.
|
|
294
|
+
`sentinel secrets` finds what leaked where — before an attacker does.
|
|
295
|
+
|
|
296
|
+
Zero extra dependencies. Fully offline. No API calls.
|
|
297
|
+
|
|
298
|
+
```
|
|
299
|
+
sentinel secrets [TARGET] [OPTIONS]
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
TARGET defaults to `.` (current directory, scanned recursively).
|
|
303
|
+
|
|
304
|
+
#### Running it for the first time
|
|
305
|
+
|
|
306
|
+
Start with the broadest scan — current directory, default severity (MEDIUM and above):
|
|
307
|
+
|
|
308
|
+
```bash
|
|
309
|
+
cd your-agent-project/
|
|
310
|
+
sentinel secrets .
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
If you get no output, your project is clean at MEDIUM+. Run with `--severity LOW` to see
|
|
314
|
+
everything including low-confidence findings.
|
|
315
|
+
|
|
316
|
+
If you get findings, work through them top to bottom — CRITICAL first. Credentials must be
|
|
317
|
+
rotated immediately. PII in memory files needs to be purged and the source (which tool call
|
|
318
|
+
produced it) investigated.
|
|
319
|
+
|
|
320
|
+
**Recommended scan order for a new project:**
|
|
321
|
+
|
|
322
|
+
```bash
|
|
323
|
+
# 1. Full scan, see the big picture
|
|
324
|
+
sentinel secrets .
|
|
325
|
+
|
|
326
|
+
# 2. Narrow to memory files — this is where PII most often hides
|
|
327
|
+
sentinel secrets . --scope memory --severity LOW
|
|
328
|
+
|
|
329
|
+
# 3. Check configs separately (credential focus)
|
|
330
|
+
sentinel secrets . --scope config
|
|
331
|
+
|
|
332
|
+
# 4. Scan Claude Code's own memory for this project
|
|
333
|
+
sentinel secrets ~/.claude/projects/ --severity LOW
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
#### Options
|
|
337
|
+
|
|
338
|
+
| Flag | Default | Description |
|
|
339
|
+
|------|---------|-------------|
|
|
340
|
+
| `--scope [all\|memory\|config]` | `all` | Restrict scan to memory files, config/env files, or both |
|
|
341
|
+
| `--severity [CRITICAL\|HIGH\|MEDIUM\|LOW]` | `MEDIUM` | Minimum severity level to display |
|
|
342
|
+
| `--format [text\|json]` | `text` | Output format |
|
|
343
|
+
| `--fail-on [CRITICAL\|HIGH\|MEDIUM\|LOW]` | — | Exit code 1 if findings reach this severity |
|
|
344
|
+
| `--no-redact` | off | Show full matched values instead of masking them |
|
|
345
|
+
|
|
346
|
+
#### Choosing the right scope
|
|
347
|
+
|
|
348
|
+
| Scope | What it scans | When to use |
|
|
349
|
+
|-------|--------------|-------------|
|
|
350
|
+
| `all` (default) | Memory files + config files + source files | First run, CI/CD gate, general audit |
|
|
351
|
+
| `memory` | Agent memory files only (`.md`, `.json` in memory dirs, conversation logs) | Daily monitoring, post-session audit, fastest scan |
|
|
352
|
+
| `config` | `.env`, `*.yaml`, `*.toml`, `docker-compose.yml`, etc. | Pre-commit hook on config changes, credential audit |
|
|
353
|
+
|
|
354
|
+
Source files (`.py`, `.js`) are always scanned for credentials regardless of scope — a
|
|
355
|
+
hardcoded `sk-ant-...` in Python source is CRITICAL no matter what scope is selected.
|
|
356
|
+
|
|
357
|
+
#### Detection layers
|
|
358
|
+
|
|
359
|
+
**Layer 1 — Credentials** (all file types)
|
|
360
|
+
|
|
361
|
+
| Rule ID | Severity | Pattern |
|
|
362
|
+
|---------|----------|---------|
|
|
363
|
+
| `ANTHROPIC_KEY` | CRITICAL | `sk-ant-api03-...` |
|
|
364
|
+
| `OPENAI_KEY` | CRITICAL | `sk-...` / `sk-proj-...` |
|
|
365
|
+
| `AWS_ACCESS_KEY` | CRITICAL | `AKIA[16 chars]` |
|
|
366
|
+
| `GITHUB_TOKEN` | CRITICAL | `ghp_...` / `github_pat_...` |
|
|
367
|
+
| `STRIPE_SECRET` | CRITICAL | `sk_live_...` |
|
|
368
|
+
| `PRIVATE_KEY_BLOCK` | CRITICAL | `-----BEGIN ... PRIVATE KEY-----` |
|
|
369
|
+
| `SLACK_TOKEN` | HIGH | `xoxb-...` / `xoxp-...` |
|
|
370
|
+
| `GOOGLE_API_KEY` | HIGH | `AIza[35 chars]` |
|
|
371
|
+
| `HUGGINGFACE_TOKEN` | HIGH | `hf_[34 chars]` |
|
|
372
|
+
| `DATABASE_URL` | HIGH | `postgresql://user:pass@host` |
|
|
373
|
+
| `JWT_TOKEN` | MEDIUM | `eyJ...eyJ...` (memory + config only) |
|
|
374
|
+
| `GENERIC_API_KEY` | MEDIUM | `api_key = "..."` (config files only) |
|
|
375
|
+
| `GENERIC_PASSWORD` | MEDIUM | `password = "..."` (config files only) |
|
|
376
|
+
|
|
377
|
+
> **Note:** Any credential found inside an agent memory file is automatically upgraded to
|
|
378
|
+
> CRITICAL severity. Memory files are routinely committed to git with no secrets management.
|
|
379
|
+
|
|
380
|
+
**Layer 2 — PII (global)** (memory + config files)
|
|
381
|
+
|
|
382
|
+
| Rule ID | Severity | Description |
|
|
383
|
+
|---------|----------|-------------|
|
|
384
|
+
| `EMAIL_ADDRESS` | MEDIUM | Email addresses (`user@domain.tld`) |
|
|
385
|
+
| `CREDIT_CARD` | HIGH | Visa / MC / Amex / Discover — Luhn-validated |
|
|
386
|
+
| `US_SSN` | HIGH | US Social Security Number (`DDD-DD-DDDD`) — structurally validated |
|
|
387
|
+
| `US_PHONE` | LOW | US phone numbers (memory files only) |
|
|
388
|
+
|
|
389
|
+
**Layer 2 — PII (Singapore / PDPA)** (memory + config files unless noted)
|
|
390
|
+
|
|
391
|
+
| Rule ID | Severity | Description |
|
|
392
|
+
|---------|----------|-------------|
|
|
393
|
+
| `SG_NRIC` | HIGH | NRIC/FIN — weighted mod-11 checksum validated (S/T/F/G/M prefix). Scans all file types. |
|
|
394
|
+
| `SG_PASSPORT` | HIGH | Singapore passport (E/K series). Scans all file types. |
|
|
395
|
+
| `SG_PHONE_MOBILE` | MEDIUM | Mobile number (`+65 8xxx xxxx` / `+65 9xxx xxxx`) |
|
|
396
|
+
| `SG_PHONE_LANDLINE` | LOW | Landline — requires explicit `+65` prefix to reduce false positives |
|
|
397
|
+
| `SG_UEN` | LOW | Unique Entity Number (business registration) |
|
|
398
|
+
| `SG_ADDRESS_POSTAL` | LOW | `Singapore XXXXXX` postal address |
|
|
399
|
+
|
|
400
|
+
**Layer 3 — Memory contamination** (memory files only)
|
|
401
|
+
|
|
402
|
+
These compound rules look at file content holistically, not line by line.
|
|
403
|
+
|
|
404
|
+
| Rule ID | Severity | Trigger condition |
|
|
405
|
+
|---------|----------|-------------------|
|
|
406
|
+
| `CONVERSATION_PII` | HIGH | Email + NRIC (SGP) **or** Email + SSN (USA) within 5 lines of each other. Strong indicator that a raw CRM or database tool call result leaked into memory. |
|
|
407
|
+
| `SYSTEM_PROMPT_IN_MEMORY` | MEDIUM | "You are a..." / "Your instructions are..." patterns in the first 30 lines of a memory file. System prompts in memory reveal agent instructions if the file is committed to git. |
|
|
408
|
+
|
|
409
|
+
#### Memory path registry
|
|
410
|
+
|
|
411
|
+
`sentinel secrets` knows where agent frameworks store memory and automatically classifies these
|
|
412
|
+
as high-sensitivity memory files:
|
|
413
|
+
|
|
414
|
+
| Framework | Paths scanned |
|
|
415
|
+
|-----------|--------------|
|
|
416
|
+
| Claude Code | `~/.claude/projects/*/memory/` |
|
|
417
|
+
| LangChain | `.langchain/`, `memory/*.json`, `langchain_cache/` |
|
|
418
|
+
| AutoGen | `.autogen/`, `autogen_cache/` |
|
|
419
|
+
| CrewAI | `crew_workspace/`, `.crewai/` |
|
|
420
|
+
| Mem0 | `.mem0/`, `mem0_storage/` |
|
|
421
|
+
| OpenAI Agents | `.openai_agents/`, `agent_workspace/` |
|
|
422
|
+
| Generic | `memory/`, `*_memory.md`, `conversation_history*.json`, `agent_logs/` |
|
|
423
|
+
|
|
424
|
+
Any file inside one of these directories is treated as a memory file and scanned with all
|
|
425
|
+
three detection layers. Config files (`.env`, `*.yaml`, `*.toml`, etc.) receive credential
|
|
426
|
+
and PII scanning. Source files receive credential scanning only (to avoid false positives
|
|
427
|
+
from example data in docstrings and comments).
|
|
428
|
+
|
|
429
|
+
#### .gitignore check
|
|
430
|
+
|
|
431
|
+
`sentinel secrets` warns if agent memory directories are not covered by `.gitignore`, since
|
|
432
|
+
memory files often contain the most sensitive data in an AI project.
|
|
433
|
+
|
|
434
|
+
#### Examples
|
|
435
|
+
|
|
436
|
+
```bash
|
|
437
|
+
# Scan everything in the current directory
|
|
438
|
+
sentinel secrets .
|
|
439
|
+
|
|
440
|
+
# Scan your Claude Code agent memory for leaked PII
|
|
441
|
+
sentinel secrets ~/.claude/projects/
|
|
442
|
+
|
|
443
|
+
# Memory files only (fastest, most sensitive findings)
|
|
444
|
+
sentinel secrets . --scope memory
|
|
445
|
+
|
|
446
|
+
# Config and env files only (credential scan)
|
|
447
|
+
sentinel secrets . --scope config
|
|
448
|
+
|
|
449
|
+
# Only show HIGH and CRITICAL (for daily monitoring)
|
|
450
|
+
sentinel secrets . --severity HIGH
|
|
451
|
+
|
|
452
|
+
# CI gate — break the build if HIGH+ findings exist
|
|
453
|
+
sentinel secrets . --fail-on HIGH
|
|
454
|
+
|
|
455
|
+
# Machine-readable output for SIEM or dashboards
|
|
456
|
+
sentinel secrets . --format json
|
|
457
|
+
|
|
458
|
+
# Show full matched values (for investigation — use carefully)
|
|
459
|
+
sentinel secrets . --no-redact
|
|
460
|
+
|
|
461
|
+
# Scan a specific agent workspace
|
|
462
|
+
sentinel secrets /path/to/my-agent/ --severity LOW
|
|
463
|
+
|
|
464
|
+
# JSON output, extract only Singapore PII findings
|
|
465
|
+
sentinel secrets . --format json | jq '.findings[] | select(.jurisdiction == "SGP")'
|
|
466
|
+
|
|
467
|
+
# Extract all CRITICAL findings with file locations
|
|
468
|
+
sentinel secrets . --format json | jq '.findings[] | select(.severity == "CRITICAL") | {rule_id, file, line}'
|
|
469
|
+
```
|
|
470
|
+
|
|
471
|
+
#### Example output
|
|
472
|
+
|
|
473
|
+
```
|
|
474
|
+
╭──────────────────────────────────────────────────╮
|
|
475
|
+
│ AgentSentinel Secrets │
|
|
476
|
+
│ Target: /my-agent/ │
|
|
477
|
+
╰──────────────────────────────────────────────────╯
|
|
478
|
+
|
|
479
|
+
──────────────── CREDENTIALS ─────────────────────
|
|
480
|
+
|
|
481
|
+
● CRITICAL ANTHROPIC_KEY ✓validated memory/session_42.md:14
|
|
482
|
+
sk-ant[REDACTED]
|
|
483
|
+
→ Rotate at console.anthropic.com/settings/api-keys
|
|
484
|
+
|
|
485
|
+
● HIGH DATABASE_URL ✓validated .env:3
|
|
486
|
+
postgr[REDACTED]
|
|
487
|
+
→ Move database credentials to environment variables
|
|
488
|
+
|
|
489
|
+
──────────────────── PII ─────────────────────────
|
|
490
|
+
|
|
491
|
+
● HIGH SG_NRIC (SGP — PDPA) ✓validated memory/session_42.md:23
|
|
492
|
+
S12345[REDACTED]
|
|
493
|
+
NRIC: S123[REDACTED]
|
|
494
|
+
→ NRIC/FIN is protected under Singapore PDPA. Purge from memory.
|
|
495
|
+
|
|
496
|
+
● MEDIUM EMAIL_ADDRESS memory/session_42.md:24
|
|
497
|
+
john.t[REDACTED]
|
|
498
|
+
→ Remove personal email from agent memory files.
|
|
499
|
+
|
|
500
|
+
──────────────── MEMORY CONTAMINATION ────────────
|
|
501
|
+
|
|
502
|
+
● HIGH CONVERSATION_PII (SGP — PDPA) ✓validated memory/session_42.md:23
|
|
503
|
+
[email + NRIC cluster]
|
|
504
|
+
Email line 24, NRIC line 23
|
|
505
|
+
→ Singapore customer PII cluster — likely leaked from CRM tool call.
|
|
506
|
+
|
|
507
|
+
● MEDIUM SYSTEM_PROMPT_IN_MEMORY ✓validated memory/session_42.md:1
|
|
508
|
+
You are a helpful customer service assistant for...
|
|
509
|
+
→ System prompt content in memory file. Will be committed to git.
|
|
510
|
+
|
|
511
|
+
──────────────── WARNINGS ────────────────────────
|
|
512
|
+
|
|
513
|
+
⚠ memory/ is not covered by .gitignore — memory files may be committed to git
|
|
514
|
+
|
|
515
|
+
──────────────────────────────────────────────────
|
|
516
|
+
12 files scanned (4 memory · 3 config) · CRITICAL:1 HIGH:3 MEDIUM:2 LOW:0 · 0.1s
|
|
517
|
+
```
|
|
518
|
+
|
|
519
|
+
#### Understanding the output
|
|
520
|
+
|
|
521
|
+
Each finding block has four lines:
|
|
522
|
+
|
|
523
|
+
```
|
|
524
|
+
● CRITICAL SG_NRIC (SGP — PDPA) ✓validated memory/session_42.md:23
|
|
525
|
+
S12345[REDACTED]
|
|
526
|
+
NRIC: S123[REDACTED]
|
|
527
|
+
→ NRIC/FIN is protected under Singapore PDPA. Purge from memory.
|
|
528
|
+
```
|
|
529
|
+
|
|
530
|
+
| Part | Meaning |
|
|
531
|
+
|------|---------|
|
|
532
|
+
| `●` + colour | Severity: red = CRITICAL, orange = HIGH, yellow = MEDIUM, dim = LOW |
|
|
533
|
+
| `SG_NRIC` | Rule ID — matches the rule tables above |
|
|
534
|
+
| `(SGP — PDPA)` | Jurisdiction tag — tells you which privacy law applies. `(SGP — PDPA)` = Singapore Personal Data Protection Act; no tag = globally applicable |
|
|
535
|
+
| `✓validated` | The match passed a checksum or structural validator (NRIC mod-11, Luhn for credit cards, area-code check for SSNs). A validated finding is a confirmed true positive — not just a regex match. Absence of `✓validated` means the rule relies on pattern alone and has a higher false positive rate. |
|
|
536
|
+
| `memory/session_42.md:23` | File path and line number — click to open directly in most editors |
|
|
537
|
+
| `S12345[REDACTED]` | First 6 characters of the match + `[REDACTED]`. Enough to identify the type, not enough to reconstruct the secret. Use `--no-redact` to see the full value during investigation. |
|
|
538
|
+
| `NRIC: S123[REDACTED]` | The surrounding line of text, with the sensitive part masked — gives context for where the data came from |
|
|
539
|
+
| `→ ...` | Recommended remediation action |
|
|
540
|
+
|
|
541
|
+
The **WARNINGS** section at the bottom is separate from findings — it reports structural
|
|
542
|
+
problems like memory directories not covered by `.gitignore`.
|
|
543
|
+
|
|
544
|
+
The **summary bar** shows total files scanned broken down by type, finding counts by severity,
|
|
545
|
+
and scan duration.
|
|
546
|
+
|
|
547
|
+
#### What to do when you find something
|
|
548
|
+
|
|
549
|
+
**CRITICAL — credentials**
|
|
550
|
+
|
|
551
|
+
Act immediately. A leaked API key is live until you rotate it.
|
|
552
|
+
|
|
553
|
+
1. Rotate the credential first — do not wait. Links are in the `→` line of each finding.
|
|
554
|
+
2. Check if the key appeared in git history: `git log --all -p | grep sk-ant-` — if yes, the history is compromised even if the file is deleted.
|
|
555
|
+
3. Audit usage logs (Anthropic Console, AWS CloudTrail, GitHub audit log) for activity you did not authorise.
|
|
556
|
+
4. Add the file or directory to `.gitignore` and remove the secret from the file.
|
|
557
|
+
5. Consider using a secrets manager (AWS Secrets Manager, HashiCorp Vault, Doppler) to prevent recurrence.
|
|
558
|
+
|
|
559
|
+
**HIGH — PII (NRIC, credit card, SSN)**
|
|
560
|
+
|
|
561
|
+
1. Identify which tool call produced this data — look at the surrounding lines in the file for context (tool name, timestamp, query).
|
|
562
|
+
2. Delete or purge the memory file contents: `echo "" > memory/session_42.md` or delete the file if the session is complete.
|
|
563
|
+
3. If the file was ever committed to git, the PII is in history. Consider a history rewrite with `git filter-repo` or treat the repo as compromised for that data type.
|
|
564
|
+
4. Review your agent's tool definitions — if a CRM or database tool is returning full customer records (including NRIC/SSN), add field filtering to return only what the agent needs.
|
|
565
|
+
5. For Singapore NRIC under PDPA: if the data was accessed without consent or leaked outside the system, a data breach notification may be required.
|
|
566
|
+
|
|
567
|
+
**MEDIUM — email addresses, system prompt leakage**
|
|
568
|
+
|
|
569
|
+
1. Email addresses in memory files are lower urgency but indicate your agent is retaining more data than it needs. Check if memory retention is configured and reduce the session window.
|
|
570
|
+
2. `SYSTEM_PROMPT_IN_MEMORY` is usually intentional (the agent wrote its own instructions to memory) but is a problem if the file gets committed — add `memory/` to `.gitignore`.
|
|
571
|
+
|
|
572
|
+
**Memory contamination (`CONVERSATION_PII`)**
|
|
573
|
+
|
|
574
|
+
This finding fires when an email address and an NRIC (or SSN) appear within 5 lines of
|
|
575
|
+
each other in a memory file — a strong signal that a raw database or CRM record was written
|
|
576
|
+
to memory by a tool call. The record contains at minimum two linked PII fields, which is
|
|
577
|
+
more serious than either in isolation.
|
|
578
|
+
|
|
579
|
+
Steps:
|
|
580
|
+
1. Open the file at the reported line. Read the surrounding context to identify the tool that produced the data.
|
|
581
|
+
2. Determine whether the tool call was authorised and whether the data was needed.
|
|
582
|
+
3. Purge the memory file.
|
|
583
|
+
4. If the tool legitimately needs customer records, modify it to return only the fields required (not full rows).
|
|
584
|
+
|
|
585
|
+
#### False positives
|
|
586
|
+
|
|
587
|
+
`✓validated` findings are rarely false positives — the validators are conservative by design.
|
|
588
|
+
Findings without `✓validated` have a higher false positive rate.
|
|
589
|
+
|
|
590
|
+
Common false positives and how to handle them:
|
|
591
|
+
|
|
592
|
+
| Finding | Common false positive cause | How to confirm |
|
|
593
|
+
|---------|----------------------------|----------------|
|
|
594
|
+
| `SG_PHONE_MOBILE` | Version numbers, port numbers like `8080 9000` | Use `--no-redact` and read the full match. A Singapore mobile is always 8 digits starting with 8 or 9. |
|
|
595
|
+
| `EMAIL_ADDRESS` | Example emails in documentation (`user@example.com`) | Read the context line — documentation examples are usually surrounded by descriptive text |
|
|
596
|
+
| `GENERIC_API_KEY` | Example keys in comments or README snippets | Check if the value looks like a real key (random alphanumeric, 20+ chars) vs a placeholder (`your-api-key-here`) |
|
|
597
|
+
| `SG_UEN` | 9-digit numbers that happen to end in a letter | UENs are common in business documents — confirm the surrounding context |
|
|
598
|
+
|
|
599
|
+
If a finding is a confirmed false positive, it does not affect the finding count for `--fail-on`
|
|
600
|
+
evaluation — you still need to address it or suppress it by restructuring the content.
|
|
601
|
+
Suppression via ignore-lists is not yet implemented (planned for v0.6).
|
|
602
|
+
|
|
603
|
+
---
|
|
604
|
+
|
|
284
605
|
### sentinel discover
|
|
285
606
|
|
|
286
607
|
**What problem it solves:** Most organisations don't have a complete inventory of their AI
|
|
@@ -674,15 +995,18 @@ sentinel inspect ./my_agent.py
|
|
|
674
995
|
# Step 2 — static posture check
|
|
675
996
|
sentinel scan ./my_agent.py --fail-on HIGH
|
|
676
997
|
|
|
677
|
-
# Step 3 —
|
|
998
|
+
# Step 3 — check for leaked secrets or PII in the workspace
|
|
999
|
+
sentinel secrets . --fail-on HIGH
|
|
1000
|
+
|
|
1001
|
+
# Step 4 — start the agent locally, probe it
|
|
678
1002
|
sentinel probe http://localhost:8000/chat --attacks injection,jailbreak,extraction
|
|
679
1003
|
|
|
680
|
-
# Step
|
|
1004
|
+
# Step 5 — deep AI red-team
|
|
681
1005
|
sentinel ai-probe http://localhost:8000/chat \
|
|
682
1006
|
--context "Customer-facing chatbot for e-commerce, handles order history and returns" \
|
|
683
1007
|
--max-probes 30
|
|
684
1008
|
|
|
685
|
-
# Step
|
|
1009
|
+
# Step 6 — if it has an MCP server, audit that too
|
|
686
1010
|
sentinel mcp scan http://localhost:3000 --fail-on CRITICAL
|
|
687
1011
|
```
|
|
688
1012
|
|
|
@@ -736,6 +1060,9 @@ Run daily or on every deployment.
|
|
|
736
1060
|
|
|
737
1061
|
set -e
|
|
738
1062
|
|
|
1063
|
+
echo "=== Secrets and PII Scan ==="
|
|
1064
|
+
sentinel secrets . --fail-on HIGH --format json >> reports/secrets-$(date +%Y%m%d).json
|
|
1065
|
+
|
|
739
1066
|
echo "=== Agent Posture Scan ==="
|
|
740
1067
|
sentinel scan ./agents/ --fail-on CRITICAL --format json >> reports/scan-$(date +%Y%m%d).json
|
|
741
1068
|
|
|
@@ -752,6 +1079,33 @@ echo "Done."
|
|
|
752
1079
|
|
|
753
1080
|
---
|
|
754
1081
|
|
|
1082
|
+
### Workflow 6: Singapore PDPA compliance check
|
|
1083
|
+
|
|
1084
|
+
Your agent processes customer data under Singapore's Personal Data Protection Act.
|
|
1085
|
+
|
|
1086
|
+
```bash
|
|
1087
|
+
# Scan for any Singapore PII that leaked into agent memory or configs
|
|
1088
|
+
sentinel secrets . --format json \
|
|
1089
|
+
| jq '.findings[] | select(.jurisdiction == "SGP")' \
|
|
1090
|
+
> pdpa-findings.json
|
|
1091
|
+
|
|
1092
|
+
# Count NRIC exposures specifically
|
|
1093
|
+
sentinel secrets . --format json \
|
|
1094
|
+
| jq '[.findings[] | select(.rule_id == "SG_NRIC")] | length'
|
|
1095
|
+
|
|
1096
|
+
# Full audit — memory files only, all severity levels
|
|
1097
|
+
sentinel secrets . --scope memory --severity LOW
|
|
1098
|
+
|
|
1099
|
+
# Fail CI if any Singapore PII found in memory files
|
|
1100
|
+
sentinel secrets . --scope memory --fail-on MEDIUM
|
|
1101
|
+
```
|
|
1102
|
+
|
|
1103
|
+
NRICs are validated using the official Singapore weighted mod-11 checksum algorithm before
|
|
1104
|
+
being reported — false positive rate is negligible. Any `SG_NRIC` finding with
|
|
1105
|
+
`"validated": true` in JSON output is a structurally valid identity number.
|
|
1106
|
+
|
|
1107
|
+
---
|
|
1108
|
+
|
|
755
1109
|
## CI/CD Integration
|
|
756
1110
|
|
|
757
1111
|
### GitHub Actions
|
|
@@ -774,6 +1128,9 @@ jobs:
|
|
|
774
1128
|
- name: Inspect agents
|
|
775
1129
|
run: sentinel inspect ./agents/ --no-ai --format json
|
|
776
1130
|
|
|
1131
|
+
- name: Secrets and PII scan — fail on HIGH
|
|
1132
|
+
run: sentinel secrets . --fail-on HIGH
|
|
1133
|
+
|
|
777
1134
|
- name: Posture scan — fail on CRITICAL
|
|
778
1135
|
run: sentinel scan ./agents/ --fail-on CRITICAL
|
|
779
1136
|
|
|
@@ -797,6 +1154,7 @@ agent-security:
|
|
|
797
1154
|
before_script:
|
|
798
1155
|
- pip install "agentsentinel-cli[all]"
|
|
799
1156
|
script:
|
|
1157
|
+
- sentinel secrets . --fail-on HIGH
|
|
800
1158
|
- sentinel scan ./agents/ --fail-on CRITICAL
|
|
801
1159
|
- sentinel mcp scan http://mcp-server:3000 --fail-on HIGH
|
|
802
1160
|
artifacts:
|
|
@@ -809,6 +1167,7 @@ agent-security:
|
|
|
809
1167
|
```bash
|
|
810
1168
|
#!/bin/bash
|
|
811
1169
|
# .git/hooks/pre-commit
|
|
1170
|
+
sentinel secrets . --fail-on HIGH # catch leaked keys/PII before they hit git history
|
|
812
1171
|
sentinel scan . --fail-on CRITICAL
|
|
813
1172
|
```
|
|
814
1173
|
|
|
@@ -821,9 +1180,9 @@ sentinel scan . --fail-on CRITICAL
|
|
|
821
1180
|
| OWASP LLM | Risk | sentinel command |
|
|
822
1181
|
|-----------|------|-----------------|
|
|
823
1182
|
| LLM01 Prompt Injection | Attackers manipulate agent via crafted inputs | `sentinel probe`, `sentinel ai-probe` |
|
|
824
|
-
| LLM02 Sensitive Info Disclosure | Agent leaks
|
|
1183
|
+
| LLM02 Sensitive Info Disclosure | Agent leaks credentials, PII, or customer data | `sentinel secrets`, `sentinel probe --attacks extraction` |
|
|
825
1184
|
| LLM06 Excessive Agency | Agent has more permissions than needed | `sentinel scan`, `sentinel discover` |
|
|
826
|
-
| LLM07 System Prompt Leakage | System prompt extracted
|
|
1185
|
+
| LLM07 System Prompt Leakage | System prompt extracted or persisted to memory | `sentinel secrets` (memory contamination), `sentinel probe --attacks extraction` |
|
|
827
1186
|
| LLM08 Vector/Embedding Weaknesses | MCP servers expose vector DB tools unsafely | `sentinel mcp scan` |
|
|
828
1187
|
|
|
829
1188
|
---
|
|
@@ -912,6 +1271,7 @@ sentinel mcp scan http://mcp-server.internal:3000 --format json \
|
|
|
912
1271
|
sentinel --help
|
|
913
1272
|
sentinel inspect --help
|
|
914
1273
|
sentinel scan --help
|
|
1274
|
+
sentinel secrets --help
|
|
915
1275
|
sentinel discover --help
|
|
916
1276
|
sentinel mcp scan --help
|
|
917
1277
|
sentinel probe --help
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: agentsentinel-cli
|
|
3
|
-
Version: 0.5.
|
|
3
|
+
Version: 0.5.4
|
|
4
4
|
Summary: Security scanner, red-team tool, and agent intelligence CLI — inspect, probe, MCP audit, and discovery for AI agents
|
|
5
5
|
Project-URL: Homepage, https://github.com/jaydenaung/agentsentinel
|
|
6
6
|
Project-URL: Repository, https://github.com/jaydenaung/agentsentinel
|
|
@@ -44,6 +44,7 @@ Security scanner, red-team tool, and MCP auditor for AI agents. No server, no Do
|
|
|
44
44
|
pipx install "agentsentinel-cli[all]"
|
|
45
45
|
sentinel inspect my_agent.py # what is this agent? plain English
|
|
46
46
|
sentinel scan my_agent.py # posture audit
|
|
47
|
+
sentinel secrets . # scan for leaked keys, PII, Singapore NRIC
|
|
47
48
|
sentinel probe http://localhost:3000 # 42-payload attack battery
|
|
48
49
|
sentinel ai-probe http://localhost:3000 # Claude-driven autonomous red-team
|
|
49
50
|
sentinel mcp scan http://localhost:3001 # MCP server security audit
|
|
@@ -77,6 +78,7 @@ pip install "agentsentinel-cli[all]" # everything
|
|
|
77
78
|
| **Posture** — what can it do? | `sentinel scan` | Static AST analysis, 12 rules, CI gate |
|
|
78
79
|
| **Posture** — what's running? | `sentinel discover` | Find unknown agents in processes, containers, subnets |
|
|
79
80
|
| **Posture** — MCP exposure? | `sentinel mcp scan` | Enumerate and audit any MCP server |
|
|
81
|
+
| **Secrets & PII** | `sentinel secrets` | Credentials, global PII, Singapore NRIC/FIN, memory contamination |
|
|
80
82
|
| **Vulnerability** — static | `sentinel probe` | 42-payload attack battery, no API key required |
|
|
81
83
|
| **Vulnerability** — AI-driven | `sentinel ai-probe` | Claude Opus as autonomous red-team agent |
|
|
82
84
|
|
|
@@ -275,6 +277,103 @@ that trigger every finding.
|
|
|
275
277
|
|
|
276
278
|
---
|
|
277
279
|
|
|
280
|
+
### `sentinel secrets` — scan for exposed secrets, API keys, and PII
|
|
281
|
+
|
|
282
|
+
AI agents process sensitive data — customer records, credentials, system prompts — and
|
|
283
|
+
many frameworks persist this to local memory files (`.md`, `.json`, conversation logs).
|
|
284
|
+
`sentinel secrets` finds what leaked where, before an attacker does.
|
|
285
|
+
|
|
286
|
+
Three detection layers:
|
|
287
|
+
- **Credentials** — 13 patterns: Anthropic, OpenAI, AWS, GitHub, Stripe, Google, HuggingFace, Slack, database URLs, JWT tokens, private key blocks
|
|
288
|
+
- **PII (global)** — email addresses, credit cards (Luhn-validated), US SSNs
|
|
289
|
+
- **PII (Singapore)** — NRIC/FIN with weighted mod-11 checksum validation, passport, mobile (+65 8xxx/9xxx), landline, UEN, postal codes
|
|
290
|
+
- **Memory contamination** — PII clusters from tool call results, system prompt leakage in memory files
|
|
291
|
+
|
|
292
|
+
```bash
|
|
293
|
+
# Scan current directory (all file types)
|
|
294
|
+
sentinel secrets .
|
|
295
|
+
|
|
296
|
+
# Scan Claude Code agent memory
|
|
297
|
+
sentinel secrets ~/.claude/projects/
|
|
298
|
+
|
|
299
|
+
# Memory files only (conversation logs, agent memory dirs)
|
|
300
|
+
sentinel secrets . --scope memory
|
|
301
|
+
|
|
302
|
+
# Config and env files only
|
|
303
|
+
sentinel secrets . --scope config
|
|
304
|
+
|
|
305
|
+
# Show only HIGH and CRITICAL
|
|
306
|
+
sentinel secrets . --severity HIGH
|
|
307
|
+
|
|
308
|
+
# Machine-readable output for SIEM
|
|
309
|
+
sentinel secrets . --format json
|
|
310
|
+
|
|
311
|
+
# CI gate — fail build if HIGH+ findings exist
|
|
312
|
+
sentinel secrets . --fail-on HIGH
|
|
313
|
+
|
|
314
|
+
# Show full matched values (no masking)
|
|
315
|
+
sentinel secrets . --no-redact
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
**Flags:**
|
|
319
|
+
|
|
320
|
+
| Flag | Default | Description |
|
|
321
|
+
|------|---------|-------------|
|
|
322
|
+
| `--scope all\|memory\|config` | `all` | Restrict scan to memory files, config files, or both |
|
|
323
|
+
| `--severity` | `MEDIUM` | Minimum severity to display |
|
|
324
|
+
| `--format text\|json` | `text` | Output format |
|
|
325
|
+
| `--fail-on` | — | Exit code 1 if findings at this severity or above |
|
|
326
|
+
| `--no-redact` | off | Show full matched values instead of masking them |
|
|
327
|
+
|
|
328
|
+
**Credential patterns detected:**
|
|
329
|
+
|
|
330
|
+
| Rule ID | Severity | Pattern |
|
|
331
|
+
|---------|----------|---------|
|
|
332
|
+
| `ANTHROPIC_KEY` | CRITICAL | `sk-ant-...` |
|
|
333
|
+
| `OPENAI_KEY` | CRITICAL | `sk-...` / `sk-proj-...` |
|
|
334
|
+
| `AWS_ACCESS_KEY` | CRITICAL | `AKIA...` |
|
|
335
|
+
| `GITHUB_TOKEN` | CRITICAL | `ghp_...` / `github_pat_...` |
|
|
336
|
+
| `STRIPE_SECRET` | CRITICAL | `sk_live_...` |
|
|
337
|
+
| `PRIVATE_KEY_BLOCK` | CRITICAL | `-----BEGIN ... PRIVATE KEY-----` |
|
|
338
|
+
| `SLACK_TOKEN` | HIGH | `xoxb-...` / `xoxp-...` |
|
|
339
|
+
| `GOOGLE_API_KEY` | HIGH | `AIza...` |
|
|
340
|
+
| `HUGGINGFACE_TOKEN` | HIGH | `hf_...` |
|
|
341
|
+
| `DATABASE_URL` | HIGH | `postgresql://user:pass@host` |
|
|
342
|
+
| `JWT_TOKEN` | MEDIUM | `eyJ...eyJ...` (memory + config files only) |
|
|
343
|
+
| `GENERIC_API_KEY` | MEDIUM | `api_key = "..."` (config files only) |
|
|
344
|
+
| `GENERIC_PASSWORD` | MEDIUM | `password = "..."` (config files only) |
|
|
345
|
+
|
|
346
|
+
Note: credentials found inside agent memory files are automatically upgraded to CRITICAL
|
|
347
|
+
severity — memory files are commonly committed to git with no secrets management in place.
|
|
348
|
+
|
|
349
|
+
**Singapore PII (PDPA-sensitive):**
|
|
350
|
+
|
|
351
|
+
| Rule ID | Severity | Description |
|
|
352
|
+
|---------|----------|-------------|
|
|
353
|
+
| `SG_NRIC` | HIGH | NRIC/FIN — checksum-validated (S/T/F/G/M prefix + weighted mod-11) |
|
|
354
|
+
| `SG_PASSPORT` | HIGH | Singapore passport number (E/K series) |
|
|
355
|
+
| `SG_PHONE_MOBILE` | MEDIUM | Mobile (+65 8xxx / 9xxx) |
|
|
356
|
+
| `SG_PHONE_LANDLINE` | LOW | Landline with explicit `+65` prefix |
|
|
357
|
+
| `SG_UEN` | LOW | Business Unique Entity Number |
|
|
358
|
+
| `SG_ADDRESS_POSTAL` | LOW | "Singapore XXXXXX" postal code |
|
|
359
|
+
|
|
360
|
+
**Memory contamination rules:**
|
|
361
|
+
|
|
362
|
+
| Rule ID | Severity | Trigger |
|
|
363
|
+
|---------|----------|---------|
|
|
364
|
+
| `CONVERSATION_PII` | HIGH | Email + NRIC (SGP) or Email + SSN (USA) within 5 lines — strong indicator of a raw tool call result leaked into memory |
|
|
365
|
+
| `SYSTEM_PROMPT_IN_MEMORY` | MEDIUM | "You are a..." / "Your instructions are..." patterns in memory files — system prompts reveal agent instructions if memory committed to git |
|
|
366
|
+
|
|
367
|
+
**Exit codes:**
|
|
368
|
+
|
|
369
|
+
| Code | Meaning |
|
|
370
|
+
|------|---------|
|
|
371
|
+
| 0 | No findings at `--fail-on` threshold |
|
|
372
|
+
| 1 | Findings at or above `--fail-on` severity |
|
|
373
|
+
| 2 | Scan error (permission denied, no readable files) |
|
|
374
|
+
|
|
375
|
+
---
|
|
376
|
+
|
|
278
377
|
### `sentinel discover` — find AI agents in your environment
|
|
279
378
|
|
|
280
379
|
```bash
|
|
@@ -292,9 +391,9 @@ sentinel discover --format json # machine-readable output
|
|
|
292
391
|
| OWASP LLM | sentinel command |
|
|
293
392
|
|-----------|-----------------|
|
|
294
393
|
| LLM01 Prompt Injection | `sentinel probe`, `sentinel ai-probe` |
|
|
295
|
-
| LLM02 Sensitive Info Disclosure | `sentinel probe` (extraction
|
|
394
|
+
| LLM02 Sensitive Info Disclosure | `sentinel secrets`, `sentinel probe` (extraction) |
|
|
296
395
|
| LLM06 Excessive Agency | `sentinel scan`, `sentinel discover` |
|
|
297
|
-
| LLM07 System Prompt Leakage | `sentinel
|
|
396
|
+
| LLM07 System Prompt Leakage | `sentinel secrets` (memory contamination), `sentinel probe` (extraction) |
|
|
298
397
|
| LLM08 Vector/Embedding Weaknesses | `sentinel mcp scan` |
|
|
299
398
|
|
|
300
399
|
---
|
|
@@ -303,6 +402,11 @@ sentinel discover --format json # machine-readable output
|
|
|
303
402
|
|
|
304
403
|
```yaml
|
|
305
404
|
# .github/workflows/security.yml
|
|
405
|
+
- name: Scan for secrets and PII in agent memory
|
|
406
|
+
run: |
|
|
407
|
+
pip install agentsentinel-cli
|
|
408
|
+
sentinel secrets . --fail-on HIGH
|
|
409
|
+
|
|
306
410
|
- name: Audit agent posture
|
|
307
411
|
run: |
|
|
308
412
|
pip install agentsentinel-cli
|
|
@@ -319,6 +423,8 @@ sentinel discover --format json # machine-readable output
|
|
|
319
423
|
sentinel mcp scan http://localhost:3001 --fail-on CRITICAL
|
|
320
424
|
```
|
|
321
425
|
|
|
426
|
+
`sentinel secrets` requires no extra dependencies — it's included in the base install.
|
|
427
|
+
|
|
322
428
|
---
|
|
323
429
|
|
|
324
430
|
## Tool detection (`sentinel scan`)
|
|
@@ -334,9 +440,11 @@ The scanner detects tools defined via:
|
|
|
334
440
|
## Requirements
|
|
335
441
|
|
|
336
442
|
- Python 3.10+
|
|
337
|
-
- No API key required for `sentinel scan`, `sentinel inspect --no-ai`, `sentinel probe`
|
|
443
|
+
- No API key required for `sentinel scan`, `sentinel secrets`, `sentinel inspect --no-ai`, `sentinel probe`
|
|
338
444
|
- `ANTHROPIC_API_KEY` required for AI summary (`sentinel inspect`), `sentinel ai-probe`
|
|
339
445
|
- `httpx` required for live endpoint inspection: `pip install "agentsentinel-cli[inspect]"`
|
|
340
446
|
- `httpx` required for HTTP MCP scanning: `pip install "agentsentinel-cli[mcp]"`
|
|
341
447
|
- `psutil` + `httpx` required for `sentinel discover`: `pip install "agentsentinel-cli[discover]"`
|
|
342
448
|
- `httpx` + `anthropic` required for `sentinel ai-probe`: `pip install "agentsentinel-cli[ai-probe]"`
|
|
449
|
+
|
|
450
|
+
`sentinel secrets` has zero extra dependencies — regex-based, fully offline, no API calls.
|
|
@@ -6,6 +6,7 @@ Security scanner, red-team tool, and MCP auditor for AI agents. No server, no Do
|
|
|
6
6
|
pipx install "agentsentinel-cli[all]"
|
|
7
7
|
sentinel inspect my_agent.py # what is this agent? plain English
|
|
8
8
|
sentinel scan my_agent.py # posture audit
|
|
9
|
+
sentinel secrets . # scan for leaked keys, PII, Singapore NRIC
|
|
9
10
|
sentinel probe http://localhost:3000 # 42-payload attack battery
|
|
10
11
|
sentinel ai-probe http://localhost:3000 # Claude-driven autonomous red-team
|
|
11
12
|
sentinel mcp scan http://localhost:3001 # MCP server security audit
|
|
@@ -39,6 +40,7 @@ pip install "agentsentinel-cli[all]" # everything
|
|
|
39
40
|
| **Posture** — what can it do? | `sentinel scan` | Static AST analysis, 12 rules, CI gate |
|
|
40
41
|
| **Posture** — what's running? | `sentinel discover` | Find unknown agents in processes, containers, subnets |
|
|
41
42
|
| **Posture** — MCP exposure? | `sentinel mcp scan` | Enumerate and audit any MCP server |
|
|
43
|
+
| **Secrets & PII** | `sentinel secrets` | Credentials, global PII, Singapore NRIC/FIN, memory contamination |
|
|
42
44
|
| **Vulnerability** — static | `sentinel probe` | 42-payload attack battery, no API key required |
|
|
43
45
|
| **Vulnerability** — AI-driven | `sentinel ai-probe` | Claude Opus as autonomous red-team agent |
|
|
44
46
|
|
|
@@ -237,6 +239,103 @@ that trigger every finding.
|
|
|
237
239
|
|
|
238
240
|
---
|
|
239
241
|
|
|
242
|
+
### `sentinel secrets` — scan for exposed secrets, API keys, and PII
|
|
243
|
+
|
|
244
|
+
AI agents process sensitive data — customer records, credentials, system prompts — and
|
|
245
|
+
many frameworks persist this to local memory files (`.md`, `.json`, conversation logs).
|
|
246
|
+
`sentinel secrets` finds what leaked where, before an attacker does.
|
|
247
|
+
|
|
248
|
+
Three detection layers:
|
|
249
|
+
- **Credentials** — 13 patterns: Anthropic, OpenAI, AWS, GitHub, Stripe, Google, HuggingFace, Slack, database URLs, JWT tokens, private key blocks
|
|
250
|
+
- **PII (global)** — email addresses, credit cards (Luhn-validated), US SSNs
|
|
251
|
+
- **PII (Singapore)** — NRIC/FIN with weighted mod-11 checksum validation, passport, mobile (+65 8xxx/9xxx), landline, UEN, postal codes
|
|
252
|
+
- **Memory contamination** — PII clusters from tool call results, system prompt leakage in memory files
|
|
253
|
+
|
|
254
|
+
```bash
|
|
255
|
+
# Scan current directory (all file types)
|
|
256
|
+
sentinel secrets .
|
|
257
|
+
|
|
258
|
+
# Scan Claude Code agent memory
|
|
259
|
+
sentinel secrets ~/.claude/projects/
|
|
260
|
+
|
|
261
|
+
# Memory files only (conversation logs, agent memory dirs)
|
|
262
|
+
sentinel secrets . --scope memory
|
|
263
|
+
|
|
264
|
+
# Config and env files only
|
|
265
|
+
sentinel secrets . --scope config
|
|
266
|
+
|
|
267
|
+
# Show only HIGH and CRITICAL
|
|
268
|
+
sentinel secrets . --severity HIGH
|
|
269
|
+
|
|
270
|
+
# Machine-readable output for SIEM
|
|
271
|
+
sentinel secrets . --format json
|
|
272
|
+
|
|
273
|
+
# CI gate — fail build if HIGH+ findings exist
|
|
274
|
+
sentinel secrets . --fail-on HIGH
|
|
275
|
+
|
|
276
|
+
# Show full matched values (no masking)
|
|
277
|
+
sentinel secrets . --no-redact
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
**Flags:**
|
|
281
|
+
|
|
282
|
+
| Flag | Default | Description |
|
|
283
|
+
|------|---------|-------------|
|
|
284
|
+
| `--scope all\|memory\|config` | `all` | Restrict scan to memory files, config files, or both |
|
|
285
|
+
| `--severity` | `MEDIUM` | Minimum severity to display |
|
|
286
|
+
| `--format text\|json` | `text` | Output format |
|
|
287
|
+
| `--fail-on` | — | Exit code 1 if findings at this severity or above |
|
|
288
|
+
| `--no-redact` | off | Show full matched values instead of masking them |
|
|
289
|
+
|
|
290
|
+
**Credential patterns detected:**
|
|
291
|
+
|
|
292
|
+
| Rule ID | Severity | Pattern |
|
|
293
|
+
|---------|----------|---------|
|
|
294
|
+
| `ANTHROPIC_KEY` | CRITICAL | `sk-ant-...` |
|
|
295
|
+
| `OPENAI_KEY` | CRITICAL | `sk-...` / `sk-proj-...` |
|
|
296
|
+
| `AWS_ACCESS_KEY` | CRITICAL | `AKIA...` |
|
|
297
|
+
| `GITHUB_TOKEN` | CRITICAL | `ghp_...` / `github_pat_...` |
|
|
298
|
+
| `STRIPE_SECRET` | CRITICAL | `sk_live_...` |
|
|
299
|
+
| `PRIVATE_KEY_BLOCK` | CRITICAL | `-----BEGIN ... PRIVATE KEY-----` |
|
|
300
|
+
| `SLACK_TOKEN` | HIGH | `xoxb-...` / `xoxp-...` |
|
|
301
|
+
| `GOOGLE_API_KEY` | HIGH | `AIza...` |
|
|
302
|
+
| `HUGGINGFACE_TOKEN` | HIGH | `hf_...` |
|
|
303
|
+
| `DATABASE_URL` | HIGH | `postgresql://user:pass@host` |
|
|
304
|
+
| `JWT_TOKEN` | MEDIUM | `eyJ...eyJ...` (memory + config files only) |
|
|
305
|
+
| `GENERIC_API_KEY` | MEDIUM | `api_key = "..."` (config files only) |
|
|
306
|
+
| `GENERIC_PASSWORD` | MEDIUM | `password = "..."` (config files only) |
|
|
307
|
+
|
|
308
|
+
Note: credentials found inside agent memory files are automatically upgraded to CRITICAL
|
|
309
|
+
severity — memory files are commonly committed to git with no secrets management in place.
|
|
310
|
+
|
|
311
|
+
**Singapore PII (PDPA-sensitive):**
|
|
312
|
+
|
|
313
|
+
| Rule ID | Severity | Description |
|
|
314
|
+
|---------|----------|-------------|
|
|
315
|
+
| `SG_NRIC` | HIGH | NRIC/FIN — checksum-validated (S/T/F/G/M prefix + weighted mod-11) |
|
|
316
|
+
| `SG_PASSPORT` | HIGH | Singapore passport number (E/K series) |
|
|
317
|
+
| `SG_PHONE_MOBILE` | MEDIUM | Mobile (+65 8xxx / 9xxx) |
|
|
318
|
+
| `SG_PHONE_LANDLINE` | LOW | Landline with explicit `+65` prefix |
|
|
319
|
+
| `SG_UEN` | LOW | Business Unique Entity Number |
|
|
320
|
+
| `SG_ADDRESS_POSTAL` | LOW | "Singapore XXXXXX" postal code |
|
|
321
|
+
|
|
322
|
+
**Memory contamination rules:**
|
|
323
|
+
|
|
324
|
+
| Rule ID | Severity | Trigger |
|
|
325
|
+
|---------|----------|---------|
|
|
326
|
+
| `CONVERSATION_PII` | HIGH | Email + NRIC (SGP) or Email + SSN (USA) within 5 lines — strong indicator of a raw tool call result leaked into memory |
|
|
327
|
+
| `SYSTEM_PROMPT_IN_MEMORY` | MEDIUM | "You are a..." / "Your instructions are..." patterns in memory files — system prompts reveal agent instructions if memory committed to git |
|
|
328
|
+
|
|
329
|
+
**Exit codes:**
|
|
330
|
+
|
|
331
|
+
| Code | Meaning |
|
|
332
|
+
|------|---------|
|
|
333
|
+
| 0 | No findings at `--fail-on` threshold |
|
|
334
|
+
| 1 | Findings at or above `--fail-on` severity |
|
|
335
|
+
| 2 | Scan error (permission denied, no readable files) |
|
|
336
|
+
|
|
337
|
+
---
|
|
338
|
+
|
|
240
339
|
### `sentinel discover` — find AI agents in your environment
|
|
241
340
|
|
|
242
341
|
```bash
|
|
@@ -254,9 +353,9 @@ sentinel discover --format json # machine-readable output
|
|
|
254
353
|
| OWASP LLM | sentinel command |
|
|
255
354
|
|-----------|-----------------|
|
|
256
355
|
| LLM01 Prompt Injection | `sentinel probe`, `sentinel ai-probe` |
|
|
257
|
-
| LLM02 Sensitive Info Disclosure | `sentinel probe` (extraction
|
|
356
|
+
| LLM02 Sensitive Info Disclosure | `sentinel secrets`, `sentinel probe` (extraction) |
|
|
258
357
|
| LLM06 Excessive Agency | `sentinel scan`, `sentinel discover` |
|
|
259
|
-
| LLM07 System Prompt Leakage | `sentinel
|
|
358
|
+
| LLM07 System Prompt Leakage | `sentinel secrets` (memory contamination), `sentinel probe` (extraction) |
|
|
260
359
|
| LLM08 Vector/Embedding Weaknesses | `sentinel mcp scan` |
|
|
261
360
|
|
|
262
361
|
---
|
|
@@ -265,6 +364,11 @@ sentinel discover --format json # machine-readable output
|
|
|
265
364
|
|
|
266
365
|
```yaml
|
|
267
366
|
# .github/workflows/security.yml
|
|
367
|
+
- name: Scan for secrets and PII in agent memory
|
|
368
|
+
run: |
|
|
369
|
+
pip install agentsentinel-cli
|
|
370
|
+
sentinel secrets . --fail-on HIGH
|
|
371
|
+
|
|
268
372
|
- name: Audit agent posture
|
|
269
373
|
run: |
|
|
270
374
|
pip install agentsentinel-cli
|
|
@@ -281,6 +385,8 @@ sentinel discover --format json # machine-readable output
|
|
|
281
385
|
sentinel mcp scan http://localhost:3001 --fail-on CRITICAL
|
|
282
386
|
```
|
|
283
387
|
|
|
388
|
+
`sentinel secrets` requires no extra dependencies — it's included in the base install.
|
|
389
|
+
|
|
284
390
|
---
|
|
285
391
|
|
|
286
392
|
## Tool detection (`sentinel scan`)
|
|
@@ -296,9 +402,11 @@ The scanner detects tools defined via:
|
|
|
296
402
|
## Requirements
|
|
297
403
|
|
|
298
404
|
- Python 3.10+
|
|
299
|
-
- No API key required for `sentinel scan`, `sentinel inspect --no-ai`, `sentinel probe`
|
|
405
|
+
- No API key required for `sentinel scan`, `sentinel secrets`, `sentinel inspect --no-ai`, `sentinel probe`
|
|
300
406
|
- `ANTHROPIC_API_KEY` required for AI summary (`sentinel inspect`), `sentinel ai-probe`
|
|
301
407
|
- `httpx` required for live endpoint inspection: `pip install "agentsentinel-cli[inspect]"`
|
|
302
408
|
- `httpx` required for HTTP MCP scanning: `pip install "agentsentinel-cli[mcp]"`
|
|
303
409
|
- `psutil` + `httpx` required for `sentinel discover`: `pip install "agentsentinel-cli[discover]"`
|
|
304
410
|
- `httpx` + `anthropic` required for `sentinel ai-probe`: `pip install "agentsentinel-cli[ai-probe]"`
|
|
411
|
+
|
|
412
|
+
`sentinel secrets` has zero extra dependencies — regex-based, fully offline, no API calls.
|
|
@@ -664,8 +664,28 @@ def secrets(
|
|
|
664
664
|
"""
|
|
665
665
|
from agentsentinel_cli.secrets import scan_secrets
|
|
666
666
|
from agentsentinel_cli.secrets_report import print_secrets_result, as_secrets_json
|
|
667
|
+
from rich.progress import Progress, SpinnerColumn, TextColumn, TimeElapsedColumn
|
|
667
668
|
|
|
668
|
-
|
|
669
|
+
_report_holder: list = []
|
|
670
|
+
|
|
671
|
+
with Progress(
|
|
672
|
+
SpinnerColumn(),
|
|
673
|
+
TextColumn("[dim]{task.description}[/dim]"),
|
|
674
|
+
TimeElapsedColumn(),
|
|
675
|
+
console=console,
|
|
676
|
+
transient=True, # clears the progress line when done
|
|
677
|
+
) as progress:
|
|
678
|
+
task = progress.add_task("Scanning...", total=None)
|
|
679
|
+
|
|
680
|
+
def _on_progress(n: int, current: str) -> None:
|
|
681
|
+
short = current[-50:] if len(current) > 50 else current
|
|
682
|
+
progress.update(task, description=f"Scanning [bold]{n}[/bold] files [dim]{short}[/dim]")
|
|
683
|
+
|
|
684
|
+
_report_holder.append(
|
|
685
|
+
scan_secrets(target, scope=scope, redact=not no_redact, progress_cb=_on_progress)
|
|
686
|
+
)
|
|
687
|
+
|
|
688
|
+
report = _report_holder[0]
|
|
669
689
|
|
|
670
690
|
if fmt == "json":
|
|
671
691
|
click.echo(as_secrets_json(report))
|
|
@@ -6,7 +6,9 @@ credentials are found inside agent memory paths (higher impact — often git-com
|
|
|
6
6
|
"""
|
|
7
7
|
|
|
8
8
|
import dataclasses
|
|
9
|
+
import os
|
|
9
10
|
import time
|
|
11
|
+
from collections.abc import Callable, Iterator
|
|
10
12
|
from pathlib import Path
|
|
11
13
|
|
|
12
14
|
from agentsentinel_cli.secrets_rules import (
|
|
@@ -53,7 +55,7 @@ _MEMORY_NAME_KW: frozenset[str] = frozenset({
|
|
|
53
55
|
"memory", "conversation", "session", "history", "cache", "agent_log", "chat_log",
|
|
54
56
|
})
|
|
55
57
|
|
|
56
|
-
_MEMORY_EXTS: frozenset[str] = frozenset({".md", ".txt", ".json", ""})
|
|
58
|
+
_MEMORY_EXTS: frozenset[str] = frozenset({".md", ".txt", ".json", ".log", ".csv", ".tsv", ""})
|
|
57
59
|
_CONFIG_EXTS: frozenset[str] = frozenset({".yaml", ".yml", ".toml", ".ini", ".cfg", ".conf"})
|
|
58
60
|
_CONFIG_NAMES: frozenset[str] = frozenset({
|
|
59
61
|
".env", "config.json", "settings.json", "secrets.json",
|
|
@@ -68,13 +70,72 @@ _SKIP_EXTS: frozenset[str] = frozenset({
|
|
|
68
70
|
".dylib", ".zip", ".tar", ".gz", ".bz2", ".7z", ".pdf",
|
|
69
71
|
".pkl", ".pt", ".onnx", ".safetensors", ".parquet", ".arrow",
|
|
70
72
|
})
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
73
|
+
|
|
74
|
+
# Directories pruned at walk time — os.walk never descends into these.
|
|
75
|
+
# This is what makes the scan fast: rglob("*") traverses everything first;
|
|
76
|
+
# os.walk with pruning skips entire subtrees like node_modules and .venv.
|
|
77
|
+
_PRUNE_DIRS: frozenset[str] = frozenset({
|
|
78
|
+
# Version control
|
|
79
|
+
".git", ".svn", ".hg",
|
|
80
|
+
# Python virtual environments
|
|
81
|
+
"venv", ".venv", "env",
|
|
82
|
+
# Python build / caches
|
|
83
|
+
"__pycache__", ".mypy_cache", ".pytest_cache", ".ruff_cache",
|
|
84
|
+
".tox", ".eggs", "site-packages", "dist", "build",
|
|
85
|
+
# JavaScript / frontend
|
|
86
|
+
"node_modules", ".next", ".nuxt", ".parcel-cache",
|
|
87
|
+
# Other language build outputs
|
|
88
|
+
"target", # Rust
|
|
89
|
+
"vendor", # Go / Ruby
|
|
90
|
+
# Test coverage
|
|
91
|
+
"htmlcov", ".coverage",
|
|
74
92
|
})
|
|
75
93
|
|
|
76
94
|
_MAX_FILE_BYTES = 1_000_000 # skip files larger than 1 MB
|
|
77
95
|
|
|
96
|
+
# Binary file types that cannot be text-scanned but warrant a warning when
|
|
97
|
+
# found inside agent memory directories (serialized memory, SQLite stores, etc.)
|
|
98
|
+
_BINARY_MEMORY_EXTS: frozenset[str] = frozenset({
|
|
99
|
+
".pkl", ".joblib", # Python serialized objects (LangChain memory, sklearn)
|
|
100
|
+
".pt", ".pth", # PyTorch tensors / model checkpoints
|
|
101
|
+
".db", ".sqlite", ".sqlite3", # SQLite databases (common agent memory backend)
|
|
102
|
+
})
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def _iter_files(root: Path) -> Iterator[Path]:
|
|
106
|
+
"""Walk root using os.walk with directory pruning.
|
|
107
|
+
|
|
108
|
+
Prunes entire subtrees (node_modules, .venv, .git, etc.) before any file
|
|
109
|
+
enumeration. Also yields binary memory files from known memory directories
|
|
110
|
+
so they receive a BINARY_MEMORY_STORE warning finding.
|
|
111
|
+
"""
|
|
112
|
+
for dirpath, dirnames, filenames in os.walk(root, followlinks=False):
|
|
113
|
+
dirnames[:] = [
|
|
114
|
+
d for d in dirnames
|
|
115
|
+
if d not in _PRUNE_DIRS
|
|
116
|
+
and not d.endswith(".egg-info")
|
|
117
|
+
]
|
|
118
|
+
# Check once per directory whether we're inside a known memory path
|
|
119
|
+
dir_parts = set(Path(dirpath).parts)
|
|
120
|
+
in_memory_dir = bool(dir_parts & _MEMORY_DIRS)
|
|
121
|
+
|
|
122
|
+
for filename in filenames:
|
|
123
|
+
path = Path(dirpath) / filename
|
|
124
|
+
ext = path.suffix.lower()
|
|
125
|
+
|
|
126
|
+
# Yield binary memory files for a warning — skip the size check
|
|
127
|
+
# since we won't read their content anyway
|
|
128
|
+
if ext in _BINARY_MEMORY_EXTS and in_memory_dir:
|
|
129
|
+
yield path
|
|
130
|
+
continue
|
|
131
|
+
|
|
132
|
+
if ext not in _SKIP_EXTS:
|
|
133
|
+
try:
|
|
134
|
+
if path.stat().st_size <= _MAX_FILE_BYTES:
|
|
135
|
+
yield path
|
|
136
|
+
except OSError:
|
|
137
|
+
pass
|
|
138
|
+
|
|
78
139
|
|
|
79
140
|
def _classify_file(path: Path) -> str:
|
|
80
141
|
"""Classify a file as 'memory', 'config', 'source', or 'other'."""
|
|
@@ -105,18 +166,13 @@ def _classify_file(path: Path) -> str:
|
|
|
105
166
|
|
|
106
167
|
|
|
107
168
|
def _should_skip(path: Path) -> bool:
|
|
108
|
-
"""Return True
|
|
169
|
+
"""Return True for a single file that should not be scanned.
|
|
170
|
+
|
|
171
|
+
Directory-level pruning is handled by _iter_files; this function only needs
|
|
172
|
+
to check extension and size for individual files.
|
|
173
|
+
"""
|
|
109
174
|
if path.suffix.lower() in _SKIP_EXTS:
|
|
110
175
|
return True
|
|
111
|
-
parts = set(path.parts)
|
|
112
|
-
if parts & _SKIP_DIRS:
|
|
113
|
-
return True
|
|
114
|
-
# Skip hidden directories except .env files and .claude
|
|
115
|
-
for part in path.parts[:-1]:
|
|
116
|
-
if part.startswith(".") and part not in {".claude", ".env", ".langchain",
|
|
117
|
-
".autogen", ".crewai", ".mem0",
|
|
118
|
-
".openai_agents"}:
|
|
119
|
-
return True
|
|
120
176
|
try:
|
|
121
177
|
return path.stat().st_size > _MAX_FILE_BYTES
|
|
122
178
|
except OSError:
|
|
@@ -145,6 +201,25 @@ def _scan_file(
|
|
|
145
201
|
if scope == "config" and file_type not in {"memory", "config"}:
|
|
146
202
|
return []
|
|
147
203
|
|
|
204
|
+
# Binary memory stores can't be text-scanned — emit a single advisory finding
|
|
205
|
+
if path.suffix.lower() in _BINARY_MEMORY_EXTS:
|
|
206
|
+
return [SecretFinding(
|
|
207
|
+
rule_id="BINARY_MEMORY_STORE",
|
|
208
|
+
severity="MEDIUM",
|
|
209
|
+
category="memory_contamination",
|
|
210
|
+
jurisdiction="global",
|
|
211
|
+
file=path,
|
|
212
|
+
line=0,
|
|
213
|
+
match_preview=path.name,
|
|
214
|
+
context_line=f"Binary {path.suffix} file in agent memory directory — cannot be text-scanned",
|
|
215
|
+
recommendation=(
|
|
216
|
+
f"Serialized memory store found ({path.suffix}). May contain customer PII or "
|
|
217
|
+
"credentials captured from tool calls. "
|
|
218
|
+
"Inspect manually or delete if the session data is no longer needed."
|
|
219
|
+
),
|
|
220
|
+
validated=False,
|
|
221
|
+
)]
|
|
222
|
+
|
|
148
223
|
lines = _read_lines(path)
|
|
149
224
|
if lines is None:
|
|
150
225
|
return []
|
|
@@ -264,25 +339,30 @@ def scan_secrets(
|
|
|
264
339
|
target: Path,
|
|
265
340
|
scope: str = "all",
|
|
266
341
|
redact: bool = True,
|
|
342
|
+
progress_cb: Callable[[int, str], None] | None = None,
|
|
267
343
|
) -> SecretsReport:
|
|
268
344
|
"""Scan target path for secrets, PII, and AI memory contamination.
|
|
269
345
|
|
|
270
346
|
Args:
|
|
271
|
-
target:
|
|
272
|
-
scope:
|
|
273
|
-
redact:
|
|
347
|
+
target: File or directory to scan.
|
|
348
|
+
scope: 'all' | 'memory' | 'config' — restricts which file types are scanned.
|
|
349
|
+
redact: If True (default), match previews are partially masked in the report.
|
|
350
|
+
progress_cb: Optional callable(n_files_done, current_file_path) for live progress.
|
|
274
351
|
"""
|
|
275
352
|
t0 = time.monotonic()
|
|
276
353
|
target = target.resolve()
|
|
277
354
|
|
|
278
|
-
candidates = list(target.rglob("*")) if target.is_dir() else [target]
|
|
279
|
-
files = [f for f in candidates if f.is_file() and not _should_skip(f)]
|
|
280
|
-
|
|
281
355
|
findings: list[SecretFinding] = []
|
|
282
356
|
memory_files: list[Path] = []
|
|
283
|
-
n_memory = n_config = 0
|
|
357
|
+
n_scanned = n_memory = n_config = 0
|
|
358
|
+
|
|
359
|
+
file_iter = _iter_files(target) if target.is_dir() else iter([target])
|
|
360
|
+
|
|
361
|
+
for f in file_iter:
|
|
362
|
+
n_scanned += 1
|
|
363
|
+
if progress_cb:
|
|
364
|
+
progress_cb(n_scanned, str(f))
|
|
284
365
|
|
|
285
|
-
for f in files:
|
|
286
366
|
ft = _classify_file(f)
|
|
287
367
|
if ft == "memory":
|
|
288
368
|
memory_files.append(f)
|
|
@@ -294,13 +374,12 @@ def scan_secrets(
|
|
|
294
374
|
root = target if target.is_dir() else target.parent
|
|
295
375
|
gitignore_warnings = _check_gitignore(root, memory_files)
|
|
296
376
|
|
|
297
|
-
# Sort by severity rank, then file path, then line number
|
|
298
377
|
_rank = {"CRITICAL": 0, "HIGH": 1, "MEDIUM": 2, "LOW": 3}
|
|
299
378
|
findings.sort(key=lambda x: (_rank.get(x.severity, 4), str(x.file), x.line))
|
|
300
379
|
|
|
301
380
|
return SecretsReport(
|
|
302
381
|
target=target,
|
|
303
|
-
files_scanned=
|
|
382
|
+
files_scanned=n_scanned,
|
|
304
383
|
memory_files_scanned=n_memory,
|
|
305
384
|
config_files_scanned=n_config,
|
|
306
385
|
findings=findings,
|
|
@@ -74,11 +74,12 @@ def _print_finding(f: SecretFinding) -> None:
|
|
|
74
74
|
color = _SEV_COLOR.get(f.severity, "white")
|
|
75
75
|
jtag = _JURISDICTION_TAG.get(f.jurisdiction, "")
|
|
76
76
|
val_mark = " [dim green]✓validated[/dim green]" if f.validated else ""
|
|
77
|
+
location = str(f.file) if f.line == 0 else f"{f.file}:{f.line}"
|
|
77
78
|
|
|
78
79
|
console.print(
|
|
79
80
|
f" [{color}]● {f.severity:<8}[/{color}] "
|
|
80
81
|
f"[bold white]{f.rule_id}[/bold white]{jtag}{val_mark}"
|
|
81
|
-
f" [dim]{
|
|
82
|
+
f" [dim]{location}[/dim]"
|
|
82
83
|
)
|
|
83
84
|
if f.match_preview:
|
|
84
85
|
console.print(f" [dim]{'':11}{f.match_preview}[/dim]")
|
|
@@ -37,6 +37,7 @@ class SecretFinding:
|
|
|
37
37
|
|
|
38
38
|
_ALL: frozenset[str] = frozenset({"memory", "config", "source", "other"})
|
|
39
39
|
_MEM_CFG: frozenset[str] = frozenset({"memory", "config"})
|
|
40
|
+
_MEM_CFG_OTHER: frozenset[str] = frozenset({"memory", "config", "other"}) # includes logs, CSVs
|
|
40
41
|
_MEM_ONLY: frozenset[str] = frozenset({"memory"})
|
|
41
42
|
_CFG_ONLY: frozenset[str] = frozenset({"config"})
|
|
42
43
|
|
|
@@ -171,7 +172,7 @@ _PII_RULES: list[_PiiRule] = [
|
|
|
171
172
|
# Negative lookbehind: must not be preceded by alphanumeric/URL chars
|
|
172
173
|
# This prevents matching mid-word substrings like password@host in DB URLs
|
|
173
174
|
re.compile(r"(?<![a-zA-Z0-9._%+\-/:])[a-zA-Z0-9._%+\-]+@[a-zA-Z0-9.\-]+\.[a-zA-Z]{2,}"),
|
|
174
|
-
|
|
175
|
+
_MEM_CFG_OTHER, # also covers log files and CSV exports
|
|
175
176
|
"Remove personal email addresses from agent memory. "
|
|
176
177
|
"Audit which tool call produced this."),
|
|
177
178
|
_PiiRule("CREDIT_CARD", "HIGH", "global",
|
|
@@ -179,14 +180,14 @@ _PII_RULES: list[_PiiRule] = [
|
|
|
179
180
|
r"\b(?:4[0-9]{12}(?:[0-9]{3})?|5[1-5][0-9]{14}"
|
|
180
181
|
r"|3[47][0-9]{13}|6(?:011|5[0-9]{2})[0-9]{12})\b"
|
|
181
182
|
),
|
|
182
|
-
|
|
183
|
-
"Credit card numbers must not appear in agent
|
|
183
|
+
_MEM_CFG_OTHER, # also covers data export files
|
|
184
|
+
"Credit card numbers must not appear in agent files. "
|
|
184
185
|
"Purge and audit tool call history.",
|
|
185
186
|
validator=lambda m: _luhn_check(re.sub(r"\D", "", m))),
|
|
186
187
|
# USA
|
|
187
188
|
_PiiRule("US_SSN", "HIGH", "USA",
|
|
188
189
|
re.compile(r"\b\d{3}-\d{2}-\d{4}\b"),
|
|
189
|
-
|
|
190
|
+
_MEM_CFG_OTHER, # also covers log files and data exports
|
|
190
191
|
"US SSNs are protected under US privacy law. "
|
|
191
192
|
"Purge from memory files and audit data flows.",
|
|
192
193
|
validator=_valid_ssn),
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "agentsentinel-cli"
|
|
7
|
-
version = "0.5.
|
|
7
|
+
version = "0.5.4"
|
|
8
8
|
description = "Security scanner, red-team tool, and agent intelligence CLI — inspect, probe, MCP audit, and discovery for AI agents"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.10"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|