ai-secret-scout 2.3.1 → 2.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.fr.md +6 -10
- package/README.md +7 -7
- package/ai_secret_scout.py +132 -25
- package/bin/aiscout.js +2 -1
- package/bin/check-environment.js +10 -1
- package/package.json +1 -1
package/README.fr.md
CHANGED
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
<img src="https://img.shields.io/badge/D%C3%A9pendances-Zero%20External-22c55e?logo=python&logoColor=white" alt="Zéro Dépendance">
|
|
19
19
|
<img src="https://img.shields.io/badge/Watchdog-Temps%20R%C3%A9el%20%26%20Desktop%20Alerts-F59E0B?logo=airplayaudio&logoColor=white" alt="Watchdog Temps Réel">
|
|
20
20
|
<img src="https://img.shields.io/badge/Safe%20Restore-Gestionnaire%20Backups-7C3AED?logo=securityscorecard&logoColor=white" alt="Safe Restore">
|
|
21
|
-
<img src="https://img.shields.io/badge/Plateforme-Linux%20%7C%
|
|
21
|
+
<img src="https://img.shields.io/badge/Plateforme-Linux%20%7C%20Windows%20%7C%20WSL-0078D6?logo=windows&logoColor=white" alt="Linux | Windows | WSL">
|
|
22
22
|
</p>
|
|
23
23
|
|
|
24
24
|
<p align="center">
|
|
@@ -502,13 +502,9 @@ aiscout --home-dir /home/autre_utilisateur
|
|
|
502
502
|
|
|
503
503
|
## 🏗️ Architecture Technique
|
|
504
504
|
|
|
505
|
-
* **Langage** : Python 3.10+ standard pur (`os`, `sys`, `re`, `json`, `
|
|
506
|
-
* **Zéro Dépendance Externe** : Aucun package tiers `pip`. Conçu
|
|
507
|
-
* **Systèmes Supportés** : **Linux** (natif Wayland / X11)
|
|
508
|
-
* **Notification
|
|
509
|
-
* **
|
|
510
|
-
* **Configuration** : `~/.config/aiscout/rules.json`
|
|
511
|
-
* **Liens symboliques exécutables** :
|
|
512
|
-
- `~/.local/bin/aiscout`
|
|
513
|
-
- `~/.local/bin/ai-secret-scout`
|
|
505
|
+
* **Langage** : Python 3.10+ standard pur (`os`, `sys`, `re`, `json`, `unicodedata`, `shutil`, `argparse`, `subprocess`, avec `msvcrt` sous Windows et `termios`/`tty`/`select` sous POSIX).
|
|
506
|
+
* **Zéro Dépendance Externe** : Aucun package tiers `pip`. Conçu exclusivement avec la bibliothèque standard Python 3.
|
|
507
|
+
* **Systèmes Supportés** : **Linux** (natif Wayland / X11), **Windows 10/11** (natif PowerShell, CMD, Windows Terminal) et **WSL / WSL2**.
|
|
508
|
+
* **Système de Notification** : Détection automatique de `notify-send` sous Linux et toasts natifs PowerShell sous Windows.
|
|
509
|
+
* **Configuration** : `~/.config/aiscout/rules.json` (ou `%APPDATA%\aiscout\rules.json` sous Windows).
|
|
514
510
|
* **Confidentialité absolue** : 100% local, aucun flux réseau sortant, zéro télémétrie.
|
package/README.md
CHANGED
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
<img src="https://img.shields.io/badge/Dependencies-Zero%20External-22c55e?logo=python&logoColor=white" alt="Zero Dependencies">
|
|
19
19
|
<img src="https://img.shields.io/badge/Watchdog-Real--Time%20%26%20Desktop%20Alerts-F59E0B?logo=airplayaudio&logoColor=white" alt="Real-Time Watchdog">
|
|
20
20
|
<img src="https://img.shields.io/badge/Safe%20Restore-Backup%20Manager-7C3AED?logo=securityscorecard&logoColor=white" alt="Safe Restore">
|
|
21
|
-
<img src="https://img.shields.io/badge/Platform-Linux%20%7C%
|
|
21
|
+
<img src="https://img.shields.io/badge/Platform-Linux%20%7C%20Windows%20%7C%20WSL-0078D6?logo=windows&logoColor=white" alt="Linux | Windows | WSL">
|
|
22
22
|
</p>
|
|
23
23
|
|
|
24
24
|
<p align="center">
|
|
@@ -107,8 +107,8 @@ When using autonomous coding assistants (**Claude Code**, **Google Antigravity /
|
|
|
107
107
|
|
|
108
108
|
## 🚀 Quickstart
|
|
109
109
|
|
|
110
|
-
> [!
|
|
111
|
-
> **Platform Support**: `aiscout`
|
|
110
|
+
> [!TIP]
|
|
111
|
+
> **Cross-Platform Support**: `aiscout` runs natively on **Linux** (Wayland & X11), **Windows 10/11** (PowerShell, Command Prompt, Windows Terminal), and **WSL / WSL2**. Zero external dependencies required.
|
|
112
112
|
|
|
113
113
|
`aiscout` can be executed instantly without installation or installed globally:
|
|
114
114
|
|
|
@@ -505,11 +505,11 @@ aiscout --home-dir /home/other_user
|
|
|
505
505
|
|
|
506
506
|
## 🏗️ Technical Architecture
|
|
507
507
|
|
|
508
|
-
* **Core Engine**: Pure Python 3.10+ standard library (`os`, `sys`, `re`, `json`, `
|
|
508
|
+
* **Core Engine**: Pure Python 3.10+ standard library (`os`, `sys`, `re`, `json`, `unicodedata`, `shutil`, `argparse`, `subprocess`, with `msvcrt` on Windows and `termios`/`tty`/`select` on POSIX).
|
|
509
509
|
* **Zero External Dependencies**: Zero `pip` dependencies required.
|
|
510
|
-
* **Supported Platforms**: **Linux** (native Wayland / X11)
|
|
511
|
-
* **Notification System**: Auto-detects `notify-send`
|
|
512
|
-
* **Configuration Path**: `~/.config/aiscout/rules.json`
|
|
510
|
+
* **Supported Platforms**: **Linux** (native Wayland / X11), **Windows 10/11** (native PowerShell, CMD, Windows Terminal), and **WSL / WSL2**.
|
|
511
|
+
* **Notification System**: Auto-detects `notify-send` on Linux and native PowerShell toast notifications on Windows.
|
|
512
|
+
* **Configuration Path**: `~/.config/aiscout/rules.json` (or `%APPDATA%\aiscout\rules.json` on Windows).
|
|
513
513
|
* **Zero Telemetry**: 100% local execution, zero outbound network traffic, complete privacy guarantee.
|
|
514
514
|
|
|
515
515
|
---
|
package/ai_secret_scout.py
CHANGED
|
@@ -16,9 +16,7 @@ import time
|
|
|
16
16
|
import json
|
|
17
17
|
import math
|
|
18
18
|
import shutil
|
|
19
|
-
import
|
|
20
|
-
import termios
|
|
21
|
-
import tty
|
|
19
|
+
import platform
|
|
22
20
|
import argparse
|
|
23
21
|
import subprocess
|
|
24
22
|
import unicodedata
|
|
@@ -27,7 +25,23 @@ from datetime import datetime
|
|
|
27
25
|
from collections import Counter
|
|
28
26
|
from typing import List, Dict, Any, Optional, Tuple
|
|
29
27
|
|
|
30
|
-
|
|
28
|
+
IS_WINDOWS = sys.platform == "win32"
|
|
29
|
+
|
|
30
|
+
if IS_WINDOWS:
|
|
31
|
+
import msvcrt
|
|
32
|
+
# Active les séquences d'échappement VT100 / ANSI sous console Windows
|
|
33
|
+
os.system("")
|
|
34
|
+
try:
|
|
35
|
+
sys.stdout.reconfigure(encoding="utf-8")
|
|
36
|
+
sys.stdin.reconfigure(encoding="utf-8")
|
|
37
|
+
except Exception:
|
|
38
|
+
pass
|
|
39
|
+
else:
|
|
40
|
+
import select
|
|
41
|
+
import termios
|
|
42
|
+
import tty
|
|
43
|
+
|
|
44
|
+
VERSION = "2.4.0"
|
|
31
45
|
|
|
32
46
|
# --- INTERNATIONALISATION (I18N) ---
|
|
33
47
|
def detect_default_lang() -> str:
|
|
@@ -554,12 +568,15 @@ PATTERNS = {
|
|
|
554
568
|
}
|
|
555
569
|
}
|
|
556
570
|
|
|
557
|
-
# --- CONFIGURATION ET RÈGLES PERSONNALISÉES (
|
|
558
|
-
|
|
571
|
+
# --- CONFIGURATION ET RÈGLES PERSONNALISÉES (rules.json) ---
|
|
572
|
+
if IS_WINDOWS and os.getenv("APPDATA"):
|
|
573
|
+
CONFIG_DIR = Path(os.getenv("APPDATA")) / "aiscout"
|
|
574
|
+
else:
|
|
575
|
+
CONFIG_DIR = Path.home() / ".config" / "aiscout"
|
|
559
576
|
CUSTOM_RULES_FILE = CONFIG_DIR / "rules.json"
|
|
560
577
|
|
|
561
578
|
def load_custom_rules() -> Dict[str, Dict[str, str]]:
|
|
562
|
-
"""Charge les règles personnalisées de l'utilisateur depuis
|
|
579
|
+
"""Charge les règles personnalisées de l'utilisateur depuis rules.json."""
|
|
563
580
|
if not CUSTOM_RULES_FILE.exists():
|
|
564
581
|
try:
|
|
565
582
|
CONFIG_DIR.mkdir(parents=True, exist_ok=True)
|
|
@@ -596,7 +613,33 @@ def load_custom_rules() -> Dict[str, Dict[str, str]]:
|
|
|
596
613
|
|
|
597
614
|
|
|
598
615
|
def send_desktop_notification(title: str, message: str, severity: str = "normal"):
|
|
599
|
-
"""Envoie une notification de bureau native sous Linux / KDE
|
|
616
|
+
"""Envoie une notification de bureau native sous Linux / KDE / GNOME ou Windows 10/11."""
|
|
617
|
+
if IS_WINDOWS:
|
|
618
|
+
clean_title = title.replace('"', '`"').replace("'", "''")
|
|
619
|
+
clean_msg = message.replace('"', '`"').replace("'", "''").replace("\n", " ")
|
|
620
|
+
ps_script = (
|
|
621
|
+
f'[void] [System.Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms"); '
|
|
622
|
+
f'$objNotifyIcon = New-Object System.Windows.Forms.NotifyIcon; '
|
|
623
|
+
f'$objNotifyIcon.Icon = [System.Drawing.SystemIcons]::Shield; '
|
|
624
|
+
f'$objNotifyIcon.BalloonTipIcon = "Warning"; '
|
|
625
|
+
f'$objNotifyIcon.BalloonTipTitle = "{clean_title}"; '
|
|
626
|
+
f'$objNotifyIcon.BalloonTipText = "{clean_msg}"; '
|
|
627
|
+
f'$objNotifyIcon.Visible = $True; '
|
|
628
|
+
f'$objNotifyIcon.ShowBalloonTip(5000); '
|
|
629
|
+
f'Start-Sleep -Seconds 1; '
|
|
630
|
+
f'$objNotifyIcon.Dispose()'
|
|
631
|
+
)
|
|
632
|
+
try:
|
|
633
|
+
subprocess.Popen(
|
|
634
|
+
["powershell", "-NoProfile", "-NonInteractive", "-Command", ps_script],
|
|
635
|
+
creationflags=subprocess.CREATE_NO_WINDOW if hasattr(subprocess, "CREATE_NO_WINDOW") else 0,
|
|
636
|
+
stdout=subprocess.DEVNULL,
|
|
637
|
+
stderr=subprocess.DEVNULL
|
|
638
|
+
)
|
|
639
|
+
except Exception:
|
|
640
|
+
pass
|
|
641
|
+
return
|
|
642
|
+
|
|
600
643
|
if shutil.which("notify-send"):
|
|
601
644
|
urgency = "critical" if severity in ("CRITIQUE", "ÉLEVÉ") else "normal"
|
|
602
645
|
try:
|
|
@@ -667,7 +710,40 @@ def display_len(s: str) -> int:
|
|
|
667
710
|
|
|
668
711
|
|
|
669
712
|
def read_key() -> str:
|
|
670
|
-
"""Lecture robuste des frappes clavier
|
|
713
|
+
"""Lecture robuste des frappes clavier (Linux / macOS / Windows natif)."""
|
|
714
|
+
if IS_WINDOWS:
|
|
715
|
+
while not msvcrt.kbhit():
|
|
716
|
+
time.sleep(0.02)
|
|
717
|
+
ch = msvcrt.getwch()
|
|
718
|
+
if ch in ("\x00", "\xe0"):
|
|
719
|
+
# Touche spéciale sous Windows (flèches, pagination, etc.)
|
|
720
|
+
code = msvcrt.getwch()
|
|
721
|
+
arrows = {
|
|
722
|
+
"H": "UP",
|
|
723
|
+
"P": "DOWN",
|
|
724
|
+
"K": "LEFT",
|
|
725
|
+
"M": "RIGHT",
|
|
726
|
+
"I": "PAGE_UP",
|
|
727
|
+
"Q": "PAGE_DOWN",
|
|
728
|
+
"G": "HOME",
|
|
729
|
+
"O": "END",
|
|
730
|
+
"S": "DELETE"
|
|
731
|
+
}
|
|
732
|
+
return arrows.get(code, "")
|
|
733
|
+
if ch in ("\r", "\n"):
|
|
734
|
+
return "ENTER"
|
|
735
|
+
if ch == " ":
|
|
736
|
+
return "SPACE"
|
|
737
|
+
if ch == "\t":
|
|
738
|
+
return "TAB"
|
|
739
|
+
if ch in ("\x08", "\x7f"):
|
|
740
|
+
return "BACKSPACE"
|
|
741
|
+
if ch == "\x1b":
|
|
742
|
+
return "ESC"
|
|
743
|
+
if ch == "\x03":
|
|
744
|
+
return "CTRL_C"
|
|
745
|
+
return ch
|
|
746
|
+
|
|
671
747
|
fd = sys.stdin.fileno()
|
|
672
748
|
old = termios.tcgetattr(fd)
|
|
673
749
|
try:
|
|
@@ -813,11 +889,26 @@ class ScoutEngine:
|
|
|
813
889
|
self.home / ".continue" / "sessions",
|
|
814
890
|
self.home / ".aider.chat.history.md",
|
|
815
891
|
]
|
|
892
|
+
# Emplacements Windows spécifiques (%APPDATA% et %USERPROFILE%)
|
|
893
|
+
appdata = os.getenv("APPDATA")
|
|
894
|
+
if appdata:
|
|
895
|
+
ad = Path(appdata)
|
|
896
|
+
targets.extend([
|
|
897
|
+
ad / "Cursor" / "User" / "workspaceStorage",
|
|
898
|
+
ad / "Claude" / "projects",
|
|
899
|
+
ad / "Code" / "User" / "workspaceStorage"
|
|
900
|
+
])
|
|
901
|
+
localappdata = os.getenv("LOCALAPPDATA")
|
|
902
|
+
if localappdata:
|
|
903
|
+
lad = Path(localappdata)
|
|
904
|
+
targets.extend([
|
|
905
|
+
lad / "Programs" / "cursor" / "resources"
|
|
906
|
+
])
|
|
816
907
|
return [p for p in targets if p.exists()]
|
|
817
908
|
|
|
818
909
|
def identify_tool(self, file_path: str) -> str:
|
|
819
|
-
fp = str(file_path)
|
|
820
|
-
if ".claude" in fp:
|
|
910
|
+
fp = str(file_path).lower().replace("\\", "/")
|
|
911
|
+
if ".claude" in fp or "/claude/" in fp:
|
|
821
912
|
return "Claude Code"
|
|
822
913
|
elif ".gemini" in fp or "antigravity" in fp:
|
|
823
914
|
return "Antigravity (Gemini)"
|
|
@@ -825,7 +916,7 @@ class ScoutEngine:
|
|
|
825
916
|
return "Codex"
|
|
826
917
|
elif ".copilot" in fp:
|
|
827
918
|
return "GitHub Copilot"
|
|
828
|
-
elif ".cursor" in fp:
|
|
919
|
+
elif ".cursor" in fp or "/cursor/" in fp:
|
|
829
920
|
return "Cursor"
|
|
830
921
|
elif ".continue" in fp:
|
|
831
922
|
return "Continue.dev"
|
|
@@ -1203,8 +1294,8 @@ class ScoutTUI:
|
|
|
1203
1294
|
return " " * pad + text
|
|
1204
1295
|
|
|
1205
1296
|
def build_top_bar(self, width: int) -> str:
|
|
1206
|
-
user = os.getenv("USER"
|
|
1207
|
-
host =
|
|
1297
|
+
user = os.getenv("USER") or os.getenv("USERNAME") or "user"
|
|
1298
|
+
host = platform.node() or "localhost"
|
|
1208
1299
|
date_str = datetime.now().strftime("%d/%m/%Y %H:%M")
|
|
1209
1300
|
u_lbl = t("user_label")
|
|
1210
1301
|
h_lbl = t("host_label")
|
|
@@ -1899,11 +1990,12 @@ class ScoutTUI:
|
|
|
1899
1990
|
|
|
1900
1991
|
def export_markdown(self, path: Path, items: List[Finding]):
|
|
1901
1992
|
is_fr = (get_lang() == "fr")
|
|
1993
|
+
user_name = os.getenv("USER") or os.getenv("USERNAME") or "user"
|
|
1902
1994
|
with open(path, "w", encoding="utf-8") as f:
|
|
1903
1995
|
if is_fr:
|
|
1904
1996
|
f.write(f"# Rapport d'Audit de Sécurité — Transcriptions d'IA\n\n")
|
|
1905
1997
|
f.write(f"- **Généré le** : {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n")
|
|
1906
|
-
f.write(f"- **Utilisateur** : `{
|
|
1998
|
+
f.write(f"- **Utilisateur** : `{user_name}`\n")
|
|
1907
1999
|
f.write(f"- **Total secrets identifiés** : {len(items)}\n\n")
|
|
1908
2000
|
f.write("## Synthèse par Niveau de Sévérité\n\n")
|
|
1909
2001
|
f.write(f"- **Critique** : {sum(1 for x in items if x.severity in ('CRITIQUE', 'CRITICAL'))}\n")
|
|
@@ -1917,7 +2009,7 @@ class ScoutTUI:
|
|
|
1917
2009
|
else:
|
|
1918
2010
|
f.write(f"# Security Audit Report — AI Assistant Histories\n\n")
|
|
1919
2011
|
f.write(f"- **Generated on** : {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n")
|
|
1920
|
-
f.write(f"- **User** : `{
|
|
2012
|
+
f.write(f"- **User** : `{user_name}`\n")
|
|
1921
2013
|
f.write(f"- **Total exposed secrets** : {len(items)}\n\n")
|
|
1922
2014
|
f.write("## Summary by Severity Level\n\n")
|
|
1923
2015
|
f.write(f"- **Critical** : {sum(1 for x in items if x.severity in ('CRITIQUE', 'CRITICAL'))}\n")
|
|
@@ -2147,10 +2239,12 @@ class ScoutTUI:
|
|
|
2147
2239
|
scans_count = 0
|
|
2148
2240
|
pulse = False
|
|
2149
2241
|
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
|
|
2242
|
+
if not IS_WINDOWS:
|
|
2243
|
+
fd = sys.stdin.fileno()
|
|
2244
|
+
old_term = termios.tcgetattr(fd)
|
|
2153
2245
|
tty.setraw(fd)
|
|
2246
|
+
|
|
2247
|
+
try:
|
|
2154
2248
|
while True:
|
|
2155
2249
|
pulse = not pulse
|
|
2156
2250
|
width = self.get_width()
|
|
@@ -2207,11 +2301,23 @@ class ScoutTUI:
|
|
|
2207
2301
|
sys.stdout.flush()
|
|
2208
2302
|
|
|
2209
2303
|
# Attente non-bloquante d'une frappe clavier (1.0 seconde)
|
|
2210
|
-
|
|
2211
|
-
if
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
|
|
2304
|
+
interrupted = False
|
|
2305
|
+
if IS_WINDOWS:
|
|
2306
|
+
for _ in range(10):
|
|
2307
|
+
if msvcrt.kbhit():
|
|
2308
|
+
ch = msvcrt.getwch()
|
|
2309
|
+
if ch in ("q", "Q", "\x1b", "\x03"):
|
|
2310
|
+
interrupted = True
|
|
2311
|
+
break
|
|
2312
|
+
time.sleep(0.1)
|
|
2313
|
+
else:
|
|
2314
|
+
r, _, _ = select.select([fd], [], [], 1.0)
|
|
2315
|
+
if r:
|
|
2316
|
+
key_bytes = os.read(fd, 32)
|
|
2317
|
+
if key_bytes in (b"q", b"Q", b"\x1b", b"\x03"):
|
|
2318
|
+
interrupted = True
|
|
2319
|
+
if interrupted:
|
|
2320
|
+
break
|
|
2215
2321
|
|
|
2216
2322
|
# Analyse des répertoires pour détecter fichiers nouveaux ou modifiés
|
|
2217
2323
|
current_files = []
|
|
@@ -2262,7 +2368,8 @@ class ScoutTUI:
|
|
|
2262
2368
|
events_log = events_log[-50:]
|
|
2263
2369
|
|
|
2264
2370
|
finally:
|
|
2265
|
-
|
|
2371
|
+
if not IS_WINDOWS:
|
|
2372
|
+
termios.tcsetattr(fd, termios.TCSADRAIN, old_term)
|
|
2266
2373
|
|
|
2267
2374
|
|
|
2268
2375
|
# --- POINT D'ENTRÉE ---
|
package/bin/aiscout.js
CHANGED
|
@@ -11,7 +11,8 @@ const pyScript = path.join(__dirname, '..', 'ai_secret_scout.py');
|
|
|
11
11
|
|
|
12
12
|
const child = spawn(pyCmd, [pyScript, ...process.argv.slice(2)], {
|
|
13
13
|
stdio: 'inherit',
|
|
14
|
-
env: process.env
|
|
14
|
+
env: process.env,
|
|
15
|
+
shell: process.platform === 'win32'
|
|
15
16
|
});
|
|
16
17
|
|
|
17
18
|
child.on('error', (err) => {
|
package/bin/check-environment.js
CHANGED
|
@@ -19,7 +19,9 @@ function checkNodeVersion() {
|
|
|
19
19
|
}
|
|
20
20
|
|
|
21
21
|
function checkPythonVersion() {
|
|
22
|
-
const candidates =
|
|
22
|
+
const candidates = process.platform === 'win32'
|
|
23
|
+
? ['python', 'py', 'python3']
|
|
24
|
+
: ['python3', 'python'];
|
|
23
25
|
|
|
24
26
|
for (const cmd of candidates) {
|
|
25
27
|
try {
|
|
@@ -62,6 +64,13 @@ function checkPythonVersion() {
|
|
|
62
64
|
}
|
|
63
65
|
|
|
64
66
|
function getInstallSuggestion(isFr) {
|
|
67
|
+
if (process.platform === 'win32') {
|
|
68
|
+
if (isFr) {
|
|
69
|
+
return '👉 Sous Windows, vous pouvez installer Python 3.10+ avec winget ou depuis le site officiel :\n winget install Python.Python.3.12\n ou téléchargez l\'installeur : https://www.python.org/downloads/';
|
|
70
|
+
}
|
|
71
|
+
return '👉 On Windows, you can install Python 3.10+ via winget or from the official website:\n winget install Python.Python.3.12\n or download the installer from: https://www.python.org/downloads/';
|
|
72
|
+
}
|
|
73
|
+
|
|
65
74
|
let pkgManager = 'apt';
|
|
66
75
|
try {
|
|
67
76
|
if (fs.existsSync('/etc/os-release')) {
|
package/package.json
CHANGED