This document provides guidance for AI assistants working on the PassFX codebase.
- NEVER log, print, or expose passwords, keys, PINs, CVVs, or master passwords
- NEVER use
randommodule for security operations - usesecretsonly - NEVER implement custom cryptography - use
cryptographylibrary - NEVER store master passwords on disk (even encrypted)
- NEVER implement password recovery mechanisms (security by design)
- NEVER use
picklefor credential serialization - JSON only - NEVER use weak KDF parameters (<480k PBKDF2 iterations)
Claude must ALWAYS work inside a Python virtual environment.
Strict Rules:
-
Check for an existing virtual environment
- If a
.venv/(or any venv) already exists before the conversation starts, it must be deleted - This ensures a clean, deterministic environment with no hidden state
- If a
-
Create a fresh virtual environment
python -m venv .venv
-
Activate the virtual environment
source .venv/bin/activate -
All Python operations must occur inside the venv
- Installing dependencies
- Running scripts
- Running tests
- Running linters, formatters, and security tools
-
Never install packages globally
- No
pip installoutside the venv - No reliance on system Python packages
- No
🚫 Hard Rules
- Do not reuse existing virtual environments
- Do not proceed if the venv is not activated
- Do not assume system-level packages are available
- If the venv becomes corrupted or misconfigured, delete it and recreate it
- NEVER commit to GitHub without explicit user confirmation of the commit message
- Never mention "Claude", "AI", or "assistant" in commit messages
- Write commit messages as if a human developer made the changes
- Use conventional commit format:
type(scope): description - Always show the proposed commit message and proceed to commit
Claude must ALWAYS follow this exact workflow — no deviations:
-
Start on
main- Ensure working from the latest code:
git checkout main git pull origin main
- Ensure working from the latest code:
-
Proceed with the assigned task
- Make all code changes locally
- Do NOT commit during this phase
- Do NOT create a branch yet
-
Create and switch to an appropriate feature branch BEFORE committing
- Branch name must reflect the task just completed:
git checkout -b feature/<scope>-<short-description>
- Examples:
feature/crypto-key-derivationfeature/ui-password-searchfeature/vault-lockingsecurity/pbkdf2-hardeninghotfix/vault-corruption-fix
- Branch name must reflect the task just completed:
-
Commit ONLY from the feature branch
- Commits on
mainordevelopare STRICTLY FORBIDDEN - If the current branch is
mainordevelop, ABORT COMMIT IMMEDIATELY
- Commits on
-
Before committing
- Show the proposed commit message
- Wait for explicit user approval
- Only then proceed with
git commit
-
After committing
- Always create a PR
- Use the github PR template and fill it out (.github/pull_request_template.md)
- Do NOT mention Claude or AI when filling out the PR template
🚫 Hard Rules
- Never commit directly on
main - Never commit directly on
develop - Never create generic branches (e.g.
feature/update,feature/fix) - Branch naming must clearly map to the work performed
Before pushing to GitHub, and before providing a summary:
- Run
ruff check passfx/(linting) - Run
mypy passfx/(type checking) - Run
bandit -r passfx/(security audit) - If any check fails, fix the errors before proceeding
- Report any issues clearly
- If new features were added, make sure to check if unit tests need to updated/added
- Update any readme files when information changes to ensure correctness
| Requirement | Specification |
|---|---|
| Encryption | Fernet (AES-128-CBC + HMAC-SHA256) |
| Key Derivation | PBKDF2-HMAC-SHA256 |
| KDF Iterations | 480,000 minimum |
| Salt Length | 32 bytes |
| RNG | secrets module only |
| File/Directory | Permission |
|---|---|
~/.passfx/ |
0o700 |
vault.enc |
0o600 |
salt |
0o600 |
logs/ |
0o700 |
*.log |
0o600 |
# Pattern for sensitive data handling
import ctypes
def secure_delete(data: str) -> None:
"""Overwrite string in memory before deletion."""
if not data:
return
buffer = (ctypes.c_char * len(data)).from_buffer_copy(data.encode())
ctypes.memset(ctypes.addressof(buffer), 0, len(data))| Never Do This | Why |
|---|---|
print(password) |
Exposes secrets in logs |
import random |
Not cryptographically secure |
pickle.dump(creds) |
Arbitrary code execution risk |
| Store keys in env vars | Accessible to child processes |
| Password hints/recovery | Defeats security model |
- Use
pyproject.tomlas the single source of truth - Keep packages flat and explicit
- Include
tests/at project root - Add a clear
README.md
- Target Python 3.11+ (3.12 if deps allow)
- Use type hints everywhere
- Prefer
dataclasses(frozen) orpydanticfor models - Avoid implicit globals and magic behavior
- Explicit is better than implicit
| Tool | Purpose |
|---|---|
| Black | Code formatting (88 char lines) |
| isort | Import sorting |
| Ruff | Fast linting |
Never format code manually. Let tooling handle it.
- No unused imports or variables
- No wildcard imports (
from x import *) - No bare
except:- catch specific exceptions - No commented-out code (delete it)
- No
print()in production code - Low cyclomatic complexity
- Fully typed functions and methods
- Use
TypedDict,Protocol,Literalwhere appropriate - Avoid
Anyunless unavoidable (document why) - Pass
mypy --strictcleanly
# Good
def derive_key(password: str, salt: bytes) -> bytes: ...
# Bad
def derive_key(password, salt): ...- Create custom exception classes in
core/exceptions.py - Catch specific exceptions only
- Never swallow exceptions silently
- Re-raise with context:
raise NewError(...) from e - On crypto errors: lock vault, clear sensitive data
# Good
try:
data = decrypt(ciphertext)
except InvalidToken as e:
self._lock_vault()
raise DecryptionError("Invalid master password") from e
# Bad
try:
data = decrypt(ciphertext)
except:
pass- Use
loggingmodule exclusively - Prefer structured logs (JSON for services)
- Log events, not thoughts
- NEVER log secrets, passwords, or PII
# Good
logger.info("Vault unlocked", extra={"user_id": user_id})
# Bad
print(f"Password is {password}")
logger.debug(f"Master password: {master_password}")- Functions do one thing (single responsibility)
- Side effects are explicit and documented
- Prefer pure functions where possible
- Keep functions under ~40 lines
- Separate business logic from I/O
# Good - pure function
def calculate_strength(password: str) -> int:
return zxcvbn(password)["score"]
# Good - explicit side effect
def save_credential(cred: Credential) -> None:
"""Writes credential to vault. Side effect: disk I/O."""
self._vault.write(cred)| Requirement | Action |
|---|---|
| Production deps | Pin exact versions |
| Dev deps | Separate in [project.optional-dependencies] |
| Lock file | Use pip-tools or poetry.lock |
| Audit | Run pip-audit regularly |
| Minimize | Fewer deps = smaller attack surface |
- Readability over cleverness
- Avoid premature optimization
- Use standard library (
pathlib,itertools,functools) - Avoid deep nesting (max 3 levels)
- Prefer clarity over terseness
# Good
vault_path = Path.home() / ".passfx" / "vault.enc"
# Bad
vault_path = os.path.join(os.path.expanduser("~"), ".passfx", "vault.enc")| Flag | Problem |
|---|---|
| No type hints | Maintenance nightmare |
print() debugging |
Unprofessional, security risk |
| God functions (100+ lines) | Untestable, unmaintainable |
| Silent exception handling | Hides bugs |
| No tests | Unverifiable behavior |
| Manual formatting | Inconsistent style |
| Hidden global state | Unpredictable behavior |
Any types everywhere |
Type safety defeated |
- Write production-grade code for a security-critical application
- Code should be self-documenting through clear naming
- Every line touching credentials requires security-first review
- Prioritize security > correctness > maintainability > performance
File-Level Comments:
- Every file must have ONE block comment at the top (2-4 lines max)
- Describe purpose and role in security architecture
- No implementation details
Function/Method Comments:
- Only when the "why" is non-obvious
- Document security implications, edge cases, business constraints
- Never explain what code does (code should be self-explanatory)
Inline Comments:
- Use sparingly for critical security context only
- Explain crypto decisions, security tradeoffs, non-obvious constraints
Forbidden:
- Emojis in code/comments
- Casual/conversational tone
- Obvious restatements (
# encrypt the data) - Commented-out code (delete it)
- Unscoped TODOs without tickets/context
- Debugging leftovers (
print(),# testing)
| Type | Convention | Example |
|---|---|---|
| Functions | verbs | encrypt_vault, derive_key, validate_input |
| Variables | nouns | vault_data, is_locked, credential_count |
| Classes | PascalCase | CryptoManager, VaultError, EmailCredential |
| Constants | UPPER_SNAKE | ITERATIONS, SALT_LENGTH, AUTO_LOCK_MINUTES |
| Private | underscore | _fernet, _last_activity, _load_salt |
PassFX is a production-grade terminal-based password manager built with Python and Textual. Fernet authenticated encryption (AES-128-CBC + HMAC-SHA256) with a cyberpunk-themed TUI. Security, data integrity, and user privacy are paramount.
| Layer | Technology |
|---|---|
| Framework | Textual (TUI) |
| Language | Python 3.11+ (strict typing) |
| Encryption | cryptography (Fernet/AES-128) |
| Key Derivation | PBKDF2 (480k iterations) |
| Styling | Textual CSS (.tcss) |
| Clipboard | pyperclip |
| Strength | zxcvbn |
passfx/
├── app.py # Textual App entry point
├── cli.py # CLI entry point
├── core/
│ ├── crypto.py # Encryption operations (CRITICAL)
│ ├── vault.py # Encrypted storage (CRITICAL)
│ ├── models.py # Credential dataclasses
│ └── exceptions.py # Custom exceptions
├── screens/
│ ├── login.py # Master password entry
│ ├── main_menu.py # Primary navigation
│ ├── passwords.py # Email credentials
│ ├── phones.py # Phone PINs
│ ├── cards.py # Credit cards
│ ├── notes.py # Secure notes
│ ├── envs.py # Environment variables
│ ├── generator.py # Password generator
│ ├── settings.py # Configuration
│ └── recovery.py # Recovery codes
├── ui/
│ ├── styles.py # Style constants
│ ├── logo.py # ASCII art
│ └── menu.py # Menu components
├── utils/
│ ├── generator.py # Secure random generation
│ ├── clipboard.py # Clipboard with auto-clear
│ ├── strength.py # Password analysis
│ └── io.py # File I/O utilities
└── widgets/
└── terminal.py # Custom widgets
- Core Layer (
core/): Zero dependencies on UI, pure security logic - Screens (
screens/): Textual screens, lazy-loaded - Utils (
utils/): Stateless helpers, no side effects - Widgets (
widgets/): Reusable UI components
login.py -> main_menu.py -> [passwords | phones | cards | notes | envs | generator | settings]
|
All screens pop back to menu via ESC
# Setup
python -m venv .venv && source .venv/bin/activate
pip install -e ".[dev]"
pre-commit install
# Run
passfx # Production
python -m passfx # Development
# Quality (run before every commit)
black passfx/ # Format
isort passfx/ # Sort imports
ruff check passfx/ --fix # Lint
mypy passfx/ # Type check
# Security
bandit -r passfx/ # Security audit
pip-audit # Dependency audit
# Test
pytest tests/ --cov=passfx --cov-report=html| Component | Required Coverage |
|---|---|
core/crypto.py |
100% |
core/vault.py |
100% |
core/models.py |
95% |
utils/generator.py |
95% |
| Overall | 90% minimum |
- Use
pytestexclusively - Prioritize unit tests over integration tests
- Test edge cases and error paths
- Avoid flaky tests (no timing-dependent assertions)
- Mock only at boundaries (I/O, external services)
- Unit Tests: Pure function testing, no I/O
- Integration Tests: Vault operations, encryption round-trips
- Security Tests: Password strength, randomness validation
def on_mount(self) -> None:
"""Initialize after mounting - load data, setup table."""
self._setup_table()
self._load_data()
def on_unmount(self) -> None:
"""Cleanup - save pending, clear sensitive data."""
self._save_pending_changes()
self._clear_sensitive_data() # CRITICALBINDINGS = [
Binding("ctrl+q", "quit", "Quit", priority=True),
Binding("escape", "pop_screen", "Back"),
Binding("a", "add", "Add"),
Binding("e", "edit", "Edit"),
Binding("d", "delete", "Delete"),
Binding("c", "copy", "Copy"),
]$pfx-primary: #00ff41; /* Matrix green */
$pfx-secondary: #ff006e; /* Neon pink */
$pfx-background: #0a0e27; /* Deep blue-black */
$pfx-surface: #151b3d; /* Panel background */
$pfx-text: #e0e0e0; /* Light gray */
$pfx-border: #00ff41 50%; /* Green with opacity */
All checks must pass before merge:
# Example GitHub Actions workflow
- run: black --check passfx/
- run: isort --check passfx/
- run: ruff check passfx/
- run: mypy passfx/
- run: bandit -r passfx/
- run: pytest tests/ --cov=passfx --cov-fail-under=90Enable pre-commit hooks for automated quality checks:
pre-commit install- All PRs require at least one review
- Security-critical changes (
core/) require security-focused review
Before creating a GitHub release, ensure these files have matching versions:
| File | Location | Example |
|---|---|---|
pyproject.toml |
version = "X.Y.Z" |
version = "1.0.3" |
passfx/cli.py |
__version__ = "X.Y.Z" |
__version__ = "1.0.3" |
tests/install/validate_install.sh |
EXPECTED_VERSION="X.Y.Z" |
EXPECTED_VERSION="1.0.3" |
homebrew/passfx.rb |
assert_match "passfx X.Y.Z" |
assert_match "passfx 1.0.3" |
Run Docker-based installation tests locally before release:
# Run all installation tests
./tests/install/run_install_tests.sh
# Run specific methods
./tests/install/run_install_tests.sh pip
./tests/install/run_install_tests.sh pipx
./tests/install/run_install_tests.sh brew
# Test with specific Python version
./tests/install/run_install_tests.sh --python 3.12 pip
# Test specific version (for verifying older releases)
./tests/install/run_install_tests.sh --version 1.0.2 pipThe CI pipeline handles releases automatically:
1. Create GitHub Release (tag: vX.Y.Z)
↓
2. publish.yml triggers → Builds and uploads to PyPI
↓
3. install-tests.yml triggers (via workflow_run)
- Waits for publish.yml to complete successfully
- Runs pip tests (Python 3.10, 3.11, 3.12)
- Runs pipx tests
- Runs Homebrew tests (Linuxbrew + macOS)
↓
4. Verify all tests pass
Key point: Install tests trigger via workflow_run after PyPI publish completes. This prevents race conditions where tests run before the package is available on PyPI.
To test the install workflow without creating a release:
# Trigger via GitHub CLI
gh workflow run install-tests.yml
# Watch the run
gh run list --workflow=install-tests.yml --limit 1
gh run watch <run-id>After a PyPI release, update the Homebrew tap at dinesh-git17/homebrew-passfx:
-
Get the new tarball URL and SHA256:
# URL format https://files.pythonhosted.org/packages/.../passfx-X.Y.Z.tar.gz # Get SHA256 curl -sL <tarball-url> | shasum -a 256
-
Update
Formula/passfx.rb:url "https://files.pythonhosted.org/packages/.../passfx-X.Y.Z.tar.gz" sha256 "<new-sha256>"
-
Update the test block version:
test do assert_match "passfx X.Y.Z", shell_output("#{bin}/passfx --version") end
-
Commit and push to the tap repository.
After release, verify installation works:
# Test pip install
pip install passfx --upgrade
passfx --version
# Test pipx install
pipx install passfx --force
passfx --version
# Test Homebrew (after formula update)
brew update
brew upgrade passfx
passfx --versionIf a release has critical issues:
- PyPI: Use
pip install passfx==X.Y.Zto pin to previous version (PyPI doesn't allow deletion) - Homebrew: Revert the formula in the tap repository
- GitHub: Mark the release as pre-release or delete the tag
type(scope): description (imperative mood, max 72 chars)
- Explain what changed and why
- Reference issues with Fixes #123
Types: feat, fix, security, refactor, perf, test, docs, style
Scopes: core, crypto, vault, ui, cli, utils, tests
security(crypto): increase PBKDF2 iterations to 480k
- Updated key derivation to meet OWASP 2024 recommendations
- Maintains backward compatibility with existing vaults
- Added migration for older vault formats
Fixes #42
fix(vault): prevent data corruption on concurrent writes
Implemented file locking to prevent race conditions when
multiple processes write to vault simultaneously.
- fcntl-based locking for Unix
- msvcrt-based locking for Windows
- Added integration tests
Fixes #89
main: Production releases onlydevelop: Integration branchfeature/*: New featuressecurity/*: Security fixes (expedited)hotfix/*: Critical production fixes
| Operation | Target |
|---|---|
| Vault unlock | <500ms |
| Credential search | <100ms |
| Screen transitions | <50ms |
| Memory baseline | <50MB |
- No hardcoded credentials or test passwords
- Sensitive data cleared from memory after use
- Proper error handling without information leakage
- Cryptographic operations use
cryptographylibrary - File permissions set correctly (0600/0700)
- No logging of passwords, keys, or PII
- Input validation on all user data
- Secure deletion implemented where needed
- Type hints on all functions and methods
- No
Anytypes (or documented exception) - Custom exceptions used appropriately
- No
print()statements - No commented-out code
- Functions under 40 lines
- Cyclomatic complexity acceptable
- Docstrings on public APIs (explain why, not what)
- Unit tests with >90% coverage
- Error messages are user-friendly (no stack traces to user)
- No circular imports
- Lazy loading for screens
- Keyboard navigation works
- Focus management implemented
- Confirmation dialogs for destructive actions
- Consistent styling with passfx.tcss
- Responsive to terminal resize
- Defense in Depth: Encryption + file permissions + auto-lock
- Fail Securely: On error, lock vault and clear sensitive data
- Least Privilege: Minimal permissions, no unnecessary access
- No Recovery: By design, no master password recovery
- Audit Trail: Log access patterns, never secrets
- Sound like a sharp senior security engineer
- Direct, precise, security-focused
- Flag security risks immediately
- No corporate fluff, no casual tone
- Call out security issues clearly
- Be decisive about security tradeoffs
- Push for secure defaults
- No emojis in code contexts
Instead of: "This implementation looks okay but might have some edge cases."
Say: "This is solid. No timing attacks, proper constant-time comparison. Ship it."
Security first. Think like an attacker. Let's build.
You are not writing code for a tutorial or demo. You are writing code for a password manager that protects users' most sensitive data. Every line touching credentials must be reviewed with a security-first mindset. When in doubt, prioritize security over convenience, performance, or features.