Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 45 additions & 29 deletions satorivault.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,18 @@
import os
import sys
import yaml
import base64
import argparse
import msvcrt
from datetime import datetime
"""SatoriVault CLI for managing Satori Network neuron vault passwords."""

import argparse
import base64
import getpass
import os
import sys
from datetime import datetime

import yaml

try: # pragma: no cover - platform specific
import msvcrt # type: ignore[attr-defined]
except ImportError: # pragma: no cover - non-Windows fallback
msvcrt = None # type: ignore[assignment]
try:
from Crypto.Cipher import AES
from Crypto.Protocol.KDF import PBKDF2
Expand Down Expand Up @@ -42,7 +50,7 @@
'UNDERLINE': '\033[4m'
}

def print_color(text, color, silent=False):
def print_color(text, color, silent=False):
"""Print text in specified color"""
if not silent:
print(f"{COLORS[color]}{text}{COLORS['ENDC']}")
Expand Down Expand Up @@ -95,23 +103,31 @@ def handle_error(message, silent=False):
input("\nPress Enter to return to main menu...")
return False

def get_password(prompt):
"""Get password with asterisk display"""
print(prompt, end='', flush=True)
password = []
while True:
char = msvcrt.getch()
if char == b'\r' or char == b'\n': # Enter
print()
break
elif char == b'\x08': # Backspace
if password:
password.pop()
print('\b \b', end='', flush=True)
else:
password.append(char.decode())
print('*', end='', flush=True)
return ''.join(password)
def get_password(prompt):
"""Return a password entered by the user, masking when possible."""
if msvcrt is None or not sys.stdin.isatty():
return getpass.getpass(prompt)

print(prompt, end='', flush=True)
password = []
while True:
char = msvcrt.getch()
if char in {b'\r', b'\n'}: # Enter
print()
break
if char == b'\x08': # Backspace
if password:
password.pop()
print('\b \b', end='', flush=True)
continue

try:
decoded = char.decode("utf-8")
except UnicodeDecodeError:
continue
password.append(decoded)
print('*', end='', flush=True)
return ''.join(password)

def create_backup(vault_path, silent=False):
"""Create backup of vault file"""
Expand All @@ -133,8 +149,8 @@ def create_backup(vault_path, silent=False):
def save_decrypted_data(data, filename, silent=False):
"""Save decrypted data to file"""
try:
with open(filename, 'w') as f:
yaml.dump(data, f, default_flow_style=False)
with open(filename, 'w') as f:
yaml.safe_dump(data, f, default_flow_style=False, sort_keys=False)
print_success(f"Decrypted data saved to {filename}", silent)
return True
except Exception as e:
Expand Down Expand Up @@ -198,8 +214,8 @@ def load_yaml(path: str):

def save_yaml(data: dict, path: str):
"""Save data to YAML file"""
with open(path, 'w') as f:
yaml.dump(data, f, default_flow_style=False)
with open(path, 'w') as f:
yaml.safe_dump(data, f, default_flow_style=False, sort_keys=False)

def decrypt_vault(vault_path, password, silent=False):
"""Attempt to decrypt vault.yaml file with given password"""
Expand Down