Merge pull request #602 from PR0M3TH3AN/beta

Beta
This commit is contained in:
thePR0M3TH3AN
2025-07-17 10:38:45 -04:00
committed by GitHub
10 changed files with 137 additions and 18 deletions

5
.gitignore vendored
View File

@@ -40,4 +40,7 @@ src/seedpass.egg-info/PKG-INFO
src/seedpass.egg-info/SOURCES.txt
src/seedpass.egg-info/dependency_links.txt
src/seedpass.egg-info/entry_points.txt
src/seedpass.egg-info/top_level.txt
src/seedpass.egg-info/top_level.txt
# Allow vendored dependencies to be committed
!src/vendor/

View File

@@ -31,6 +31,7 @@ SeedPass now uses the `portalocker` library for cross-platform file locking. No
- [Running the Application](#running-the-application)
- [Managing Multiple Seeds](#managing-multiple-seeds)
- [Additional Entry Types](#additional-entry-types)
- [Building a standalone executable](#building-a-standalone-executable)
- [Security Considerations](#security-considerations)
- [Contributing](#contributing)
- [License](#license)
@@ -502,6 +503,41 @@ python -m mutmut results
```
Mutation testing is disabled in the GitHub workflow due to reliability issues and should be run on a desktop environment instead.
## Development Workflow
1. Install all development dependencies:
```bash
pip install -r src/requirements.txt
```
2. When `src/runtime_requirements.txt` changes, rerun:
```bash
scripts/vendor_dependencies.sh
```
Commit the updated `src/vendor/` directory. The application automatically adds this folder to `sys.path` so the bundled packages are found.
3. Before committing, format and test the code:
```bash
black .
pytest
```
## Building a standalone executable
1. Run the vendoring script to bundle runtime dependencies:
```bash
scripts/vendor_dependencies.sh
```
2. Build the binary with PyInstaller:
```bash
pyinstaller SeedPass.spec
```
The standalone executable will appear in the `dist/` directory. This process works on Windows, macOS and Linux but you must build on each platform for a native binary.
## Security Considerations

38
SeedPass.spec Normal file
View File

@@ -0,0 +1,38 @@
# -*- mode: python ; coding: utf-8 -*-
a = Analysis(
['src/main.py'],
pathex=['src', 'src/vendor'],
binaries=[],
datas=[],
hiddenimports=[],
hookspath=[],
hooksconfig={},
runtime_hooks=[],
excludes=[],
noarchive=False,
optimize=0,
)
pyz = PYZ(a.pure)
exe = EXE(
pyz,
a.scripts,
a.binaries,
a.datas,
[],
name='SeedPass',
debug=False,
bootloader_ignore_signals=False,
strip=False,
upx=True,
upx_exclude=[],
runtime_tmpdir=None,
console=True,
disable_windowed_traceback=False,
argv_emulation=False,
target_arch=None,
codesign_identity=None,
entitlements_file=None,
)

12
scripts/vendor_dependencies.sh Executable file
View File

@@ -0,0 +1,12 @@
#!/usr/bin/env bash
set -euo pipefail
VENDOR_DIR="src/vendor"
# Clean vendor directory
rm -rf "$VENDOR_DIR"
mkdir -p "$VENDOR_DIR"
pip download --no-binary :all: -r src/runtime_requirements.txt -d "$VENDOR_DIR"
echo "Vendored dependencies installed in $VENDOR_DIR"

View File

@@ -1,10 +1,15 @@
# main.py
import os
from pathlib import Path
import sys
# Add bundled vendor directory to sys.path so bundled dependencies can be imported
vendor_dir = Path(__file__).parent / "vendor"
if vendor_dir.exists():
sys.path.insert(0, str(vendor_dir))
import os
import logging
import signal
import getpass
import time
import argparse
import asyncio

View File

@@ -6,7 +6,7 @@ import logging
from pathlib import Path
from typing import List, Optional
import getpass
from utils.seed_prompt import masked_input
import bcrypt
@@ -93,7 +93,7 @@ class ConfigManager:
self.save_config(data)
if require_pin and data.get("pin_hash"):
for _ in range(3):
pin = getpass.getpass("Enter settings PIN: ").strip()
pin = masked_input("Enter settings PIN: ").strip()
if bcrypt.checkpw(pin.encode(), data["pin_hash"].encode()):
break
print("Invalid PIN")

View File

@@ -12,7 +12,6 @@ with the password manager functionalities.
import sys
import json
import logging
import getpass
import os
import hashlib
from typing import Optional, Literal
@@ -668,8 +667,8 @@ class PasswordManager:
Prompts the user for the master password to decrypt the seed.
"""
try:
# Prompt for password
password = getpass.getpass(prompt="Enter your login password: ").strip()
# Prompt for password using masked input
password = prompt_existing_password("Enter your login password: ")
# Derive encryption key from password
iterations = (

View File

@@ -0,0 +1,29 @@
# Runtime dependencies for vendoring/packaging only
# Generated from requirements.txt with all test-only packages removed
colorama>=0.4.6
termcolor>=1.1.0
cryptography>=40.0.2
bip-utils>=2.5.0
bech32==1.2.0
coincurve>=18.0.0
mnemonic
aiohttp>=3.12.14
bcrypt
portalocker>=2.8
nostr-sdk>=0.42.1
websocket-client==1.7.0
websockets>=15.0.0
tomli
pgpy==0.6.0
pyotp>=2.8.0
pyperclip
qrcode>=8.2
typer>=0.12.3
fastapi>=0.116.0
uvicorn>=0.35.0
httpx>=0.28.1
requests>=2.32
python-multipart
orjson
argon2-cffi

View File

@@ -9,16 +9,14 @@ from utils import password_prompt
def test_prompt_new_password(monkeypatch):
responses = cycle(["goodpass", "goodpass"])
monkeypatch.setattr(
password_prompt.getpass, "getpass", lambda prompt: next(responses)
)
monkeypatch.setattr(password_prompt, "masked_input", lambda prompt: next(responses))
result = password_prompt.prompt_new_password()
assert result == "goodpass"
def test_prompt_new_password_retry(monkeypatch, caplog):
seq = iter(["pass1", "pass2", "passgood", "passgood"])
monkeypatch.setattr(password_prompt.getpass, "getpass", lambda prompt: next(seq))
monkeypatch.setattr(password_prompt, "masked_input", lambda prompt: next(seq))
caplog.set_level(logging.WARNING)
result = password_prompt.prompt_new_password()
assert "User entered a password shorter" in caplog.text
@@ -26,7 +24,7 @@ def test_prompt_new_password_retry(monkeypatch, caplog):
def test_prompt_existing_password(monkeypatch):
monkeypatch.setattr(password_prompt.getpass, "getpass", lambda prompt: "mypassword")
monkeypatch.setattr(password_prompt, "masked_input", lambda prompt: "mypassword")
assert password_prompt.prompt_existing_password() == "mypassword"

View File

@@ -11,11 +11,10 @@ this module enhances code reuse, security, and maintainability across the applic
Ensure that all dependencies are installed and properly configured in your environment.
"""
import getpass
from utils.seed_prompt import masked_input
import logging
import sys
import unicodedata
import traceback
from termcolor import colored
from colorama import init as colorama_init
@@ -53,8 +52,8 @@ def prompt_new_password() -> str:
while attempts < max_retries:
try:
password = getpass.getpass(prompt="Enter a new password: ").strip()
confirm_password = getpass.getpass(prompt="Confirm your password: ").strip()
password = masked_input("Enter a new password: ").strip()
confirm_password = masked_input("Confirm your password: ").strip()
if not password:
print(
@@ -128,7 +127,7 @@ def prompt_existing_password(
attempts = 0
while attempts < max_retries:
try:
password = getpass.getpass(prompt=prompt_message).strip()
password = masked_input(prompt_message).strip()
if not password:
print(