feat: replay visuel VLM-first, worker séparé, package Léa, AZERTY, sécurité HTTPS
Pipeline replay visuel : - VLM-first : l'agent appelle Ollama directement pour trouver les éléments - Template matching en fallback (seuil strict 0.90) - Stop immédiat si élément non trouvé (pas de clic blind) - Replay depuis session brute (/replay-session) sans attendre le VLM - Vérification post-action (screenshot hash avant/après) - Gestion des popups (Enter/Escape/Tab+Enter) Worker VLM séparé : - run_worker.py : process distinct du serveur HTTP - Communication par fichiers (_worker_queue.txt + _replay_active.lock) - Le serveur HTTP ne fait plus jamais de VLM → toujours réactif - Service systemd rpa-worker.service Capture clavier : - raw_keys (vk + press/release) pour replay exact indépendant du layout - Fix AZERTY : ToUnicodeEx + AltGr detection - Enter capturé comme \n, Tab comme \t - Filtrage modificateurs seuls (Ctrl/Alt/Shift parasites) - Fusion text_input consécutifs, dédup key_combo Sécurité & Internet : - HTTPS Let's Encrypt (lea.labs + vwb.labs.laurinebazin.design) - Token API fixe dans .env.local - HTTP Basic Auth sur VWB - Security headers (HSTS, CSP, nosniff) - CORS domaines publics, plus de wildcard Infrastructure : - DPI awareness (SetProcessDpiAwareness) Python + Rust - Métadonnées système (dpi_scale, window_bounds, monitors, os_theme) - Template matching multi-scale [0.5, 2.0] - Résolution dynamique (plus de hardcode 1920x1080) - VLM prefill fix (47x speedup, 3.5s au lieu de 180s) Modules : - core/auth/ : credential vault (Fernet AES), TOTP (RFC 6238), auth handler - core/federation/ : LearningPack export/import anonymisé, FAISS global - deploy/ : package Léa (config.txt, Lea.bat, install.bat, LISEZMOI.txt) UX : - Filtrage OS (VWB + Chat montrent que les workflows de l'OS courant) - Bibliothèque persistante (cache local + SQLite) - Clustering hybride (titre fenêtre + DBSCAN) - EdgeConstraints + PostConditions peuplés - GraphBuilder compound actions (toutes les frappes) Agent Rust : - Token Bearer auth (network.rs) - sysinfo.rs (DPI, résolution, window bounds via Win32 API) - config.txt lu automatiquement - Support Chrome/Brave/Firefox (pas que Edge) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
213
core/auth/manage_vault.py
Normal file
213
core/auth/manage_vault.py
Normal file
@@ -0,0 +1,213 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
CLI de gestion du coffre-fort de credentials (vault).
|
||||
|
||||
Usage :
|
||||
# Ajouter un login
|
||||
python -m core.auth.manage_vault --vault /path/to/vault.enc --action add \
|
||||
--app "DPI_Crossway" --type login \
|
||||
--username "robot_lea" --password "xxx"
|
||||
|
||||
# Ajouter un seed TOTP
|
||||
python -m core.auth.manage_vault --vault /path/to/vault.enc --action add \
|
||||
--app "DPI_Crossway" --type totp_seed \
|
||||
--secret "JBSWY3DPEHPK3PXP"
|
||||
|
||||
# Lister les applications configurées
|
||||
python -m core.auth.manage_vault --vault /path/to/vault.enc --action list
|
||||
|
||||
# Générer un code TOTP
|
||||
python -m core.auth.manage_vault --vault /path/to/vault.enc --action generate-totp \
|
||||
--app "DPI_Crossway"
|
||||
|
||||
# Supprimer un credential
|
||||
python -m core.auth.manage_vault --vault /path/to/vault.enc --action remove \
|
||||
--app "DPI_Crossway" --type login
|
||||
|
||||
Le mot de passe maître est demandé interactivement via getpass.
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import getpass
|
||||
import sys
|
||||
|
||||
from .credential_vault import CredentialVault
|
||||
from .totp_generator import TOTPGenerator
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Gestionnaire de coffre-fort de credentials pour Léa.",
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
epilog=__doc__,
|
||||
)
|
||||
parser.add_argument(
|
||||
"--vault",
|
||||
required=True,
|
||||
help="Chemin du fichier vault chiffré",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--action",
|
||||
required=True,
|
||||
choices=["add", "list", "remove", "generate-totp", "show"],
|
||||
help="Action à effectuer",
|
||||
)
|
||||
parser.add_argument("--app", help="Nom de l'application")
|
||||
parser.add_argument(
|
||||
"--type",
|
||||
dest="cred_type",
|
||||
choices=["login", "totp_seed", "session_token", "certificate"],
|
||||
help="Type de credential",
|
||||
)
|
||||
# Champs pour le type "login"
|
||||
parser.add_argument("--username", help="Nom d'utilisateur (type login)")
|
||||
parser.add_argument("--password", help="Mot de passe (type login)")
|
||||
parser.add_argument("--domain", help="Domaine Windows (type login, optionnel)")
|
||||
# Champs pour le type "totp_seed"
|
||||
parser.add_argument("--secret", help="Secret base32 (type totp_seed)")
|
||||
parser.add_argument(
|
||||
"--digits", type=int, default=6, help="Nombre de chiffres TOTP (défaut: 6)"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--interval", type=int, default=30, help="Intervalle TOTP en secondes (défaut: 30)"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--algorithm", default="SHA1", help="Algorithme HMAC (défaut: SHA1)"
|
||||
)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
# Demander le mot de passe maître
|
||||
master_password = getpass.getpass("Mot de passe maître : ")
|
||||
if not master_password:
|
||||
print("Erreur : mot de passe maître requis.", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
try:
|
||||
vault = CredentialVault(args.vault, master_password)
|
||||
except ValueError as e:
|
||||
print(f"Erreur d'ouverture du vault : {e}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
# ---- Actions ----
|
||||
|
||||
if args.action == "list":
|
||||
apps = vault.list_apps()
|
||||
if not apps:
|
||||
print("Vault vide — aucune application configurée.")
|
||||
else:
|
||||
print(f"Applications configurées ({len(apps)}) :")
|
||||
for app in apps:
|
||||
types = vault.list_credential_types(app)
|
||||
print(f" {app} : {', '.join(types)}")
|
||||
|
||||
elif args.action == "add":
|
||||
if not args.app:
|
||||
print("Erreur : --app requis pour l'action 'add'.", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
if not args.cred_type:
|
||||
print("Erreur : --type requis pour l'action 'add'.", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
if args.cred_type == "login":
|
||||
if not args.username:
|
||||
args.username = input("Username : ")
|
||||
if not args.password:
|
||||
args.password = getpass.getpass("Password : ")
|
||||
data = {"username": args.username, "password": args.password}
|
||||
if args.domain:
|
||||
data["domain"] = args.domain
|
||||
|
||||
elif args.cred_type == "totp_seed":
|
||||
if not args.secret:
|
||||
args.secret = input("Secret base32 : ")
|
||||
data = {
|
||||
"secret": args.secret,
|
||||
"digits": args.digits,
|
||||
"interval": args.interval,
|
||||
"algorithm": args.algorithm,
|
||||
}
|
||||
|
||||
elif args.cred_type == "session_token":
|
||||
token = input("Token de session : ")
|
||||
data = {"token": token}
|
||||
|
||||
elif args.cred_type == "certificate":
|
||||
cert_path = input("Chemin du certificat : ")
|
||||
key_path = input("Chemin de la clé privée : ")
|
||||
data = {"cert_path": cert_path, "key_path": key_path}
|
||||
|
||||
else:
|
||||
print(f"Type non géré : {args.cred_type}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
vault.add_credential(args.app, args.cred_type, data)
|
||||
vault.save()
|
||||
print(f"Credential ajouté : {args.app} / {args.cred_type}")
|
||||
|
||||
elif args.action == "remove":
|
||||
if not args.app or not args.cred_type:
|
||||
print(
|
||||
"Erreur : --app et --type requis pour l'action 'remove'.",
|
||||
file=sys.stderr,
|
||||
)
|
||||
sys.exit(1)
|
||||
removed = vault.remove_credential(args.app, args.cred_type)
|
||||
if removed:
|
||||
vault.save()
|
||||
print(f"Credential supprimé : {args.app} / {args.cred_type}")
|
||||
else:
|
||||
print(f"Credential non trouvé : {args.app} / {args.cred_type}")
|
||||
|
||||
elif args.action == "generate-totp":
|
||||
if not args.app:
|
||||
print(
|
||||
"Erreur : --app requis pour l'action 'generate-totp'.",
|
||||
file=sys.stderr,
|
||||
)
|
||||
sys.exit(1)
|
||||
totp_creds = vault.get_credential(args.app, "totp_seed")
|
||||
if not totp_creds:
|
||||
print(
|
||||
f"Pas de seed TOTP configuré pour '{args.app}'.",
|
||||
file=sys.stderr,
|
||||
)
|
||||
sys.exit(1)
|
||||
|
||||
totp = TOTPGenerator(
|
||||
secret=totp_creds["secret"],
|
||||
digits=totp_creds.get("digits", 6),
|
||||
interval=totp_creds.get("interval", 30),
|
||||
algorithm=totp_creds.get("algorithm", "SHA1"),
|
||||
)
|
||||
code = totp.generate()
|
||||
remaining = totp.time_remaining()
|
||||
print(f"Code TOTP : {code}")
|
||||
print(f"Expire dans : {remaining}s")
|
||||
|
||||
elif args.action == "show":
|
||||
if not args.app:
|
||||
print(
|
||||
"Erreur : --app requis pour l'action 'show'.",
|
||||
file=sys.stderr,
|
||||
)
|
||||
sys.exit(1)
|
||||
types = vault.list_credential_types(args.app)
|
||||
if not types:
|
||||
print(f"Aucun credential pour '{args.app}'.")
|
||||
else:
|
||||
print(f"Credentials pour '{args.app}' :")
|
||||
for cred_type in types:
|
||||
cred = vault.get_credential(args.app, cred_type)
|
||||
# Masquer les mots de passe et secrets
|
||||
display = {}
|
||||
for k, v in (cred or {}).items():
|
||||
if k in ("password", "secret", "token"):
|
||||
display[k] = v[:3] + "***" if len(str(v)) > 3 else "***"
|
||||
else:
|
||||
display[k] = v
|
||||
print(f" {cred_type} : {display}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user