zenithpasskeys 1.1.0__tar.gz

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.
Files changed (50) hide show
  1. zenithpasskeys-1.1.0/.gitignore +80 -0
  2. zenithpasskeys-1.1.0/PKG-INFO +204 -0
  3. zenithpasskeys-1.1.0/README.md +169 -0
  4. zenithpasskeys-1.1.0/pyproject.toml +55 -0
  5. zenithpasskeys-1.1.0/src/zenith_passkeys/__init__.py +1 -0
  6. zenithpasskeys-1.1.0/src/zenith_passkeys/__main__.py +4 -0
  7. zenithpasskeys-1.1.0/src/zenith_passkeys/backup/__init__.py +4 -0
  8. zenithpasskeys-1.1.0/src/zenith_passkeys/commands/__init__.py +0 -0
  9. zenithpasskeys-1.1.0/src/zenith_passkeys/commands/_shared.py +43 -0
  10. zenithpasskeys-1.1.0/src/zenith_passkeys/commands/audit_cmd.py +93 -0
  11. zenithpasskeys-1.1.0/src/zenith_passkeys/commands/backup_cmd.py +163 -0
  12. zenithpasskeys-1.1.0/src/zenith_passkeys/commands/change_password_cmd.py +34 -0
  13. zenithpasskeys-1.1.0/src/zenith_passkeys/commands/config_cmd.py +35 -0
  14. zenithpasskeys-1.1.0/src/zenith_passkeys/commands/delete_cmd.py +60 -0
  15. zenithpasskeys-1.1.0/src/zenith_passkeys/commands/export_cmd.py +79 -0
  16. zenithpasskeys-1.1.0/src/zenith_passkeys/commands/import_cmd.py +166 -0
  17. zenithpasskeys-1.1.0/src/zenith_passkeys/commands/init_cmd.py +76 -0
  18. zenithpasskeys-1.1.0/src/zenith_passkeys/commands/list_cmd.py +74 -0
  19. zenithpasskeys-1.1.0/src/zenith_passkeys/commands/lock_cmd.py +26 -0
  20. zenithpasskeys-1.1.0/src/zenith_passkeys/commands/logs_cmd.py +47 -0
  21. zenithpasskeys-1.1.0/src/zenith_passkeys/commands/recover_cmd.py +48 -0
  22. zenithpasskeys-1.1.0/src/zenith_passkeys/core/__init__.py +0 -0
  23. zenithpasskeys-1.1.0/src/zenith_passkeys/core/credentials.py +43 -0
  24. zenithpasskeys-1.1.0/src/zenith_passkeys/core/crypto.py +119 -0
  25. zenithpasskeys-1.1.0/src/zenith_passkeys/core/errors.py +38 -0
  26. zenithpasskeys-1.1.0/src/zenith_passkeys/core/paths.py +32 -0
  27. zenithpasskeys-1.1.0/src/zenith_passkeys/core/psl.py +192 -0
  28. zenithpasskeys-1.1.0/src/zenith_passkeys/core/recovery.py +26 -0
  29. zenithpasskeys-1.1.0/src/zenith_passkeys/core/schema.py +57 -0
  30. zenithpasskeys-1.1.0/src/zenith_passkeys/core/vault.py +247 -0
  31. zenithpasskeys-1.1.0/src/zenith_passkeys/core/webauthn.py +129 -0
  32. zenithpasskeys-1.1.0/src/zenith_passkeys/main.py +76 -0
  33. zenithpasskeys-1.1.0/src/zenith_passkeys/native_host/__init__.py +0 -0
  34. zenithpasskeys-1.1.0/src/zenith_passkeys/native_host/host.py +636 -0
  35. zenithpasskeys-1.1.0/src/zenith_passkeys/native_host/host_wrapper.py +40 -0
  36. zenithpasskeys-1.1.0/src/zenith_passkeys/native_host/lock_sentinel.py +68 -0
  37. zenithpasskeys-1.1.0/src/zenith_passkeys/native_host/manifest.py +168 -0
  38. zenithpasskeys-1.1.0/src/zenith_passkeys/native_host/protocol.py +32 -0
  39. zenithpasskeys-1.1.0/tests/__init__.py +0 -0
  40. zenithpasskeys-1.1.0/tests/audit_test.py +738 -0
  41. zenithpasskeys-1.1.0/tests/test_backup.py +106 -0
  42. zenithpasskeys-1.1.0/tests/test_cli_standalone.py +53 -0
  43. zenithpasskeys-1.1.0/tests/test_commands.py +37 -0
  44. zenithpasskeys-1.1.0/tests/test_crypto.py +89 -0
  45. zenithpasskeys-1.1.0/tests/test_host_updates.py +114 -0
  46. zenithpasskeys-1.1.0/tests/test_native_host.py +51 -0
  47. zenithpasskeys-1.1.0/tests/test_native_host_extended.py +102 -0
  48. zenithpasskeys-1.1.0/tests/test_psl.py +117 -0
  49. zenithpasskeys-1.1.0/tests/test_vault.py +101 -0
  50. zenithpasskeys-1.1.0/tests/test_webauthn.py +57 -0
@@ -0,0 +1,80 @@
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+ *.so
6
+ .Python
7
+ build/
8
+ develop-eggs/
9
+ dist/
10
+ downloads/
11
+ eggs/
12
+ .eggs/
13
+ lib/
14
+ lib64/
15
+ parts/
16
+ sdist/
17
+ var/
18
+ wheels/
19
+ share/python-wheels/
20
+ *.egg-info/
21
+ .installed.cfg
22
+ *.egg
23
+ MANIFEST
24
+
25
+ # Virtual Environments
26
+ venv/
27
+ .venv/
28
+ env/
29
+ .env/
30
+ env.bak/
31
+ venv.bak/
32
+
33
+ # Environment Variables
34
+ .env
35
+ .env.local
36
+
37
+ # Unit test / coverage reports
38
+ htmlcov/
39
+ .tox/
40
+ .nox/
41
+ .coverage
42
+ .coverage.*
43
+ .cache
44
+ nosetests.xml
45
+ coverage.xml
46
+ *.cover
47
+ *.py,cover
48
+ .hypothesis/
49
+ .pytest_cache/
50
+ cover/
51
+
52
+ # IDE / Editor
53
+ .vscode/
54
+ .idea/
55
+ *.swp
56
+ *.swo
57
+ .DS_Store
58
+ Thumbs.db
59
+ *.bak
60
+ *.tmp
61
+ *~
62
+
63
+ # Project specific
64
+ /plan.md
65
+ /audit_test.py
66
+ /cli/tests/audit_test.py
67
+
68
+ # Sensitive files — NEVER commit these
69
+ vault.enc
70
+ *.enc
71
+ gdrive_token.json
72
+ host_debug.log
73
+
74
+ # Debug/temp scripts
75
+ cli/fix_tests.py
76
+ cli/test_debug.py
77
+ cli/test_host.py
78
+
79
+ # Native host manifests (generated per-machine)
80
+ native-manifests/
@@ -0,0 +1,204 @@
1
+ Metadata-Version: 2.3
2
+ Name: zenithpasskeys
3
+ Version: 1.1.0
4
+ Summary: Local-first, air-gapped passkey manager with encrypted vault
5
+ Project-URL: Homepage, https://github.com/roshhellwett/projectwarden
6
+ Project-URL: Repository, https://github.com/roshhellwett/projectwarden
7
+ Project-URL: Documentation, https://github.com/roshhellwett/projectwarden/blob/main/docs/SETUP.md
8
+ Project-URL: Issues, https://github.com/roshhellwett/projectwarden/issues
9
+ Author-email: Zenith Open Source Projects <zenithprojects@icloud.com>
10
+ License: MIT
11
+ Keywords: cryptography,fido2,passkeys,password-manager,security,webauthn
12
+ Classifier: Development Status :: 4 - Beta
13
+ Classifier: Environment :: Console
14
+ Classifier: License :: OSI Approved :: MIT License
15
+ Classifier: Operating System :: OS Independent
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3.10
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Programming Language :: Python :: 3.13
21
+ Classifier: Programming Language :: Python :: 3.14
22
+ Classifier: Topic :: Security
23
+ Requires-Python: >=3.10
24
+ Requires-Dist: cbor2>=5.6
25
+ Requires-Dist: click>=8.1
26
+ Requires-Dist: cryptography>=43.0
27
+ Requires-Dist: filelock>=3.13
28
+ Requires-Dist: platformdirs>=4.0
29
+ Requires-Dist: rich>=13.0
30
+ Provides-Extra: dev
31
+ Requires-Dist: pytest-cov>=5.0; extra == 'dev'
32
+ Requires-Dist: pytest>=8.0; extra == 'dev'
33
+ Requires-Dist: ruff>=0.5; extra == 'dev'
34
+ Description-Content-Type: text/markdown
35
+
36
+ # Zenith Passkeys (`zenithpasskeys`)
37
+
38
+ [![PyPI version](https://img.shields.io/pypi/v/zenithpasskeys.svg)](https://pypi.org/project/zenithpasskeys/)
39
+ [![Python versions](https://img.shields.io/pypi/pyversions/zenithpasskeys.svg)](https://pypi.org/project/zenithpasskeys/)
40
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)
41
+
42
+ > **Local-first, air-gapped passkey (WebAuthn & FIDO2) manager with an encrypted vault.**
43
+ > Built and managed by **Zenith Open Source Projects**.
44
+
45
+ ---
46
+
47
+ ## Highlights
48
+
49
+ - 🔒 **100% Local-First & Air-Gapped:** Zero cloud accounts, zero telemetry, zero external network requests. Your private keys never touch the internet.
50
+ - 🛡️ **Hardware-Grade Cryptography:** Master Encryption Key (MEK) protected by **Argon2id** (64MB RAM, 3 iterations, 4 lanes) and **AES-256-GCM** authenticated encryption.
51
+ - 🔑 **Dual-Slot Emergency Recovery:** Instant vault recovery using a 32-character Crockford Base32 emergency key (`XXXX-XXXX-...`). Re-wraps the MEK under a new password without losing any passkeys.
52
+ - ⚡ **Zero Setup Required:** Runs out of the box in any environment without manual virtualenv activation or system compilation.
53
+ - 🌐 **Dual Standalone or Browser Mode:** Use purely from the terminal as an independent passkey vault, or pair seamlessly with the Zenith Passkeys Chrome/Edge/Firefox Web Extension.
54
+
55
+ ---
56
+
57
+ ## Installation
58
+
59
+ Install in any environment via `pip`:
60
+
61
+ ```bash
62
+ pip install zenithpasskeys
63
+ ```
64
+
65
+ Or install cleanly using `pipx` (recommended for isolated CLI tools with zero venv management):
66
+
67
+ ```bash
68
+ pipx install zenithpasskeys
69
+ ```
70
+
71
+ Once installed, you can launch the CLI using either command:
72
+
73
+ ```bash
74
+ zenith-passkeys
75
+ # or
76
+ zenithpasskeys
77
+ ```
78
+
79
+ ---
80
+
81
+ ## Quickstart Guide
82
+
83
+ ### 1. Initialize Your Secure Vault
84
+ Create a new encrypted vault and generate your 32-character Emergency Recovery Key:
85
+
86
+ ```bash
87
+ zenith-passkeys init
88
+ ```
89
+
90
+ Save the printed recovery key in a physical notebook or cold storage.
91
+
92
+ ### 2. View Stored Passkeys
93
+ List all passkey credentials stored in your local vault:
94
+
95
+ ```bash
96
+ zenith-passkeys list
97
+
98
+ # Filter by domain:
99
+ zenith-passkeys list --domain github.com
100
+ ```
101
+
102
+ ### 3. Run a Security Audit
103
+ Perform an offline diagnostic check for duplicate credentials or stale passkeys:
104
+
105
+ ```bash
106
+ zenith-passkeys audit
107
+ ```
108
+
109
+ ### 4. Export & Import Credentials
110
+ Securely export your credentials (encrypted with a dedicated export password):
111
+
112
+ ```bash
113
+ # Encrypted export (recommended):
114
+ zenith-passkeys export --encrypt
115
+
116
+ # Plaintext export:
117
+ zenith-passkeys export
118
+ ```
119
+
120
+ Import credentials from another Zenith instance or migration file:
121
+
122
+ ```bash
123
+ zenith-passkeys import export.json
124
+ ```
125
+
126
+ ### 5. Create Local Backup Snapshots
127
+ Create an encrypted backup snapshot directly to an external drive, USB stick, or backup directory:
128
+
129
+ ```bash
130
+ zenith-passkeys backup create /path/to/backup/zenith-vault-backup.enc
131
+ ```
132
+
133
+ ### 6. Emergency Recovery
134
+ Forgot your master password? Recover your vault and set a new password using your Emergency Recovery Key:
135
+
136
+ ```bash
137
+ zenith-passkeys recover
138
+ ```
139
+
140
+ ### 7. Instant Lock
141
+ Clear active authentication sessions and lock the vault across all processes:
142
+
143
+ ```bash
144
+ zenith-passkeys lock
145
+ ```
146
+
147
+ ---
148
+
149
+ ## Command Reference
150
+
151
+ | Command | Description |
152
+ |---|---|
153
+ | `zenith-passkeys init` | Initialize a new vault and register native host messaging |
154
+ | `zenith-passkeys list` | Display stored passkeys with rich metadata and domain filtering |
155
+ | `zenith-passkeys audit` | Run offline security diagnostics and health scoring |
156
+ | `zenith-passkeys export` | Export credentials to encrypted or plaintext JSON |
157
+ | `zenith-passkeys import` | Import credentials from encrypted or plaintext export files |
158
+ | `zenith-passkeys backup` | Create or restore encrypted vault backup snapshots |
159
+ | `zenith-passkeys recover` | Reset master password using 32-character Emergency Recovery Key |
160
+ | `zenith-passkeys change-password` | Update master password and re-wrap vault master key |
161
+ | `zenith-passkeys lock` | Clear active memory session and lock the vault |
162
+ | `zenith-passkeys config` | Configure auto-lock timeout (inactivity minutes) |
163
+ | `zenith-passkeys delete` | Permanently delete a credential by domain |
164
+ | `zenith-passkeys logs` | View internal diagnostic action audit trail |
165
+
166
+ ---
167
+
168
+ ## Pairing with the Browser Extension (Optional)
169
+
170
+ Zenith Passkeys works as a standalone terminal manager, but also pairs with the **Zenith Passkeys Browser Extension** for passwordless web logins.
171
+
172
+ When you run:
173
+
174
+ ```bash
175
+ zenith-passkeys init
176
+ ```
177
+
178
+ it automatically writes the Native Messaging manifest for Google Chrome, Microsoft Edge, and Mozilla Firefox. The browser extension can then securely communicate with the local `zenith-passkeys-host` daemon to sign WebAuthn ceremonies on sites like GitHub, Google, and Amazon.
179
+
180
+ ---
181
+
182
+ ## Security Architecture
183
+
184
+ 1. **Argon2id Key Derivation:**
185
+ - Memory: 64 MB (`m=65536`)
186
+ - Iterations: 3 (`t=3`)
187
+ - Parallelism: 4 lanes (`p=4`)
188
+ 2. **Dual-Slot Key Wrapping:**
189
+ - Slot 0 (`pwWrapped`): Master Encryption Key encrypted under master password.
190
+ - Slot 1 (`recWrapped`): Master Encryption Key encrypted under the 32-character recovery key.
191
+ - Password changes re-wrap Slot 0 while keeping the recovery key permanently functional.
192
+ 3. **Atomic Operations & Integrity:**
193
+ - Atomic file replace using tempfile swaps to eliminate corruption risks.
194
+ - File locks via `filelock` prevent concurrent write race conditions.
195
+ - Public Suffix List (PSL) validation prevents cross-subdomain credential spoofing.
196
+
197
+ ---
198
+
199
+ ## License & Support
200
+
201
+ - **License:** Open source under the [MIT License](https://opensource.org/licenses/MIT).
202
+ - **Repository:** [GitHub](https://github.com/roshhellwett/projectwarden)
203
+ - **Support & Security Disclosures:** `zenithprojects@icloud.com`
204
+ - **Maintained by:** **Zenith Open Source Projects**
@@ -0,0 +1,169 @@
1
+ # Zenith Passkeys (`zenithpasskeys`)
2
+
3
+ [![PyPI version](https://img.shields.io/pypi/v/zenithpasskeys.svg)](https://pypi.org/project/zenithpasskeys/)
4
+ [![Python versions](https://img.shields.io/pypi/pyversions/zenithpasskeys.svg)](https://pypi.org/project/zenithpasskeys/)
5
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)
6
+
7
+ > **Local-first, air-gapped passkey (WebAuthn & FIDO2) manager with an encrypted vault.**
8
+ > Built and managed by **Zenith Open Source Projects**.
9
+
10
+ ---
11
+
12
+ ## Highlights
13
+
14
+ - 🔒 **100% Local-First & Air-Gapped:** Zero cloud accounts, zero telemetry, zero external network requests. Your private keys never touch the internet.
15
+ - 🛡️ **Hardware-Grade Cryptography:** Master Encryption Key (MEK) protected by **Argon2id** (64MB RAM, 3 iterations, 4 lanes) and **AES-256-GCM** authenticated encryption.
16
+ - 🔑 **Dual-Slot Emergency Recovery:** Instant vault recovery using a 32-character Crockford Base32 emergency key (`XXXX-XXXX-...`). Re-wraps the MEK under a new password without losing any passkeys.
17
+ - ⚡ **Zero Setup Required:** Runs out of the box in any environment without manual virtualenv activation or system compilation.
18
+ - 🌐 **Dual Standalone or Browser Mode:** Use purely from the terminal as an independent passkey vault, or pair seamlessly with the Zenith Passkeys Chrome/Edge/Firefox Web Extension.
19
+
20
+ ---
21
+
22
+ ## Installation
23
+
24
+ Install in any environment via `pip`:
25
+
26
+ ```bash
27
+ pip install zenithpasskeys
28
+ ```
29
+
30
+ Or install cleanly using `pipx` (recommended for isolated CLI tools with zero venv management):
31
+
32
+ ```bash
33
+ pipx install zenithpasskeys
34
+ ```
35
+
36
+ Once installed, you can launch the CLI using either command:
37
+
38
+ ```bash
39
+ zenith-passkeys
40
+ # or
41
+ zenithpasskeys
42
+ ```
43
+
44
+ ---
45
+
46
+ ## Quickstart Guide
47
+
48
+ ### 1. Initialize Your Secure Vault
49
+ Create a new encrypted vault and generate your 32-character Emergency Recovery Key:
50
+
51
+ ```bash
52
+ zenith-passkeys init
53
+ ```
54
+
55
+ Save the printed recovery key in a physical notebook or cold storage.
56
+
57
+ ### 2. View Stored Passkeys
58
+ List all passkey credentials stored in your local vault:
59
+
60
+ ```bash
61
+ zenith-passkeys list
62
+
63
+ # Filter by domain:
64
+ zenith-passkeys list --domain github.com
65
+ ```
66
+
67
+ ### 3. Run a Security Audit
68
+ Perform an offline diagnostic check for duplicate credentials or stale passkeys:
69
+
70
+ ```bash
71
+ zenith-passkeys audit
72
+ ```
73
+
74
+ ### 4. Export & Import Credentials
75
+ Securely export your credentials (encrypted with a dedicated export password):
76
+
77
+ ```bash
78
+ # Encrypted export (recommended):
79
+ zenith-passkeys export --encrypt
80
+
81
+ # Plaintext export:
82
+ zenith-passkeys export
83
+ ```
84
+
85
+ Import credentials from another Zenith instance or migration file:
86
+
87
+ ```bash
88
+ zenith-passkeys import export.json
89
+ ```
90
+
91
+ ### 5. Create Local Backup Snapshots
92
+ Create an encrypted backup snapshot directly to an external drive, USB stick, or backup directory:
93
+
94
+ ```bash
95
+ zenith-passkeys backup create /path/to/backup/zenith-vault-backup.enc
96
+ ```
97
+
98
+ ### 6. Emergency Recovery
99
+ Forgot your master password? Recover your vault and set a new password using your Emergency Recovery Key:
100
+
101
+ ```bash
102
+ zenith-passkeys recover
103
+ ```
104
+
105
+ ### 7. Instant Lock
106
+ Clear active authentication sessions and lock the vault across all processes:
107
+
108
+ ```bash
109
+ zenith-passkeys lock
110
+ ```
111
+
112
+ ---
113
+
114
+ ## Command Reference
115
+
116
+ | Command | Description |
117
+ |---|---|
118
+ | `zenith-passkeys init` | Initialize a new vault and register native host messaging |
119
+ | `zenith-passkeys list` | Display stored passkeys with rich metadata and domain filtering |
120
+ | `zenith-passkeys audit` | Run offline security diagnostics and health scoring |
121
+ | `zenith-passkeys export` | Export credentials to encrypted or plaintext JSON |
122
+ | `zenith-passkeys import` | Import credentials from encrypted or plaintext export files |
123
+ | `zenith-passkeys backup` | Create or restore encrypted vault backup snapshots |
124
+ | `zenith-passkeys recover` | Reset master password using 32-character Emergency Recovery Key |
125
+ | `zenith-passkeys change-password` | Update master password and re-wrap vault master key |
126
+ | `zenith-passkeys lock` | Clear active memory session and lock the vault |
127
+ | `zenith-passkeys config` | Configure auto-lock timeout (inactivity minutes) |
128
+ | `zenith-passkeys delete` | Permanently delete a credential by domain |
129
+ | `zenith-passkeys logs` | View internal diagnostic action audit trail |
130
+
131
+ ---
132
+
133
+ ## Pairing with the Browser Extension (Optional)
134
+
135
+ Zenith Passkeys works as a standalone terminal manager, but also pairs with the **Zenith Passkeys Browser Extension** for passwordless web logins.
136
+
137
+ When you run:
138
+
139
+ ```bash
140
+ zenith-passkeys init
141
+ ```
142
+
143
+ it automatically writes the Native Messaging manifest for Google Chrome, Microsoft Edge, and Mozilla Firefox. The browser extension can then securely communicate with the local `zenith-passkeys-host` daemon to sign WebAuthn ceremonies on sites like GitHub, Google, and Amazon.
144
+
145
+ ---
146
+
147
+ ## Security Architecture
148
+
149
+ 1. **Argon2id Key Derivation:**
150
+ - Memory: 64 MB (`m=65536`)
151
+ - Iterations: 3 (`t=3`)
152
+ - Parallelism: 4 lanes (`p=4`)
153
+ 2. **Dual-Slot Key Wrapping:**
154
+ - Slot 0 (`pwWrapped`): Master Encryption Key encrypted under master password.
155
+ - Slot 1 (`recWrapped`): Master Encryption Key encrypted under the 32-character recovery key.
156
+ - Password changes re-wrap Slot 0 while keeping the recovery key permanently functional.
157
+ 3. **Atomic Operations & Integrity:**
158
+ - Atomic file replace using tempfile swaps to eliminate corruption risks.
159
+ - File locks via `filelock` prevent concurrent write race conditions.
160
+ - Public Suffix List (PSL) validation prevents cross-subdomain credential spoofing.
161
+
162
+ ---
163
+
164
+ ## License & Support
165
+
166
+ - **License:** Open source under the [MIT License](https://opensource.org/licenses/MIT).
167
+ - **Repository:** [GitHub](https://github.com/roshhellwett/projectwarden)
168
+ - **Support & Security Disclosures:** `zenithprojects@icloud.com`
169
+ - **Maintained by:** **Zenith Open Source Projects**
@@ -0,0 +1,55 @@
1
+ [build-system]
2
+ requires = ["hatchling<1.27"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "zenithpasskeys"
7
+ version = "1.1.0"
8
+ description = "Local-first, air-gapped passkey manager with encrypted vault"
9
+ readme = "README.md"
10
+ license = { text = "MIT" }
11
+ requires-python = ">=3.10"
12
+ authors = [{name = "Zenith Open Source Projects", email = "zenithprojects@icloud.com"}]
13
+ keywords = ["passkeys", "webauthn", "fido2", "password-manager", "cryptography", "security"]
14
+ classifiers = [
15
+ "Development Status :: 4 - Beta",
16
+ "License :: OSI Approved :: MIT License",
17
+ "Programming Language :: Python :: 3",
18
+ "Programming Language :: Python :: 3.10",
19
+ "Programming Language :: Python :: 3.11",
20
+ "Programming Language :: Python :: 3.12",
21
+ "Programming Language :: Python :: 3.13",
22
+ "Programming Language :: Python :: 3.14",
23
+ "Topic :: Security",
24
+ "Environment :: Console",
25
+ "Operating System :: OS Independent",
26
+ ]
27
+ dependencies = [
28
+ "click>=8.1",
29
+ "cryptography>=43.0",
30
+ "platformdirs>=4.0",
31
+ "filelock>=3.13",
32
+ "cbor2>=5.6",
33
+ "rich>=13.0",
34
+ ]
35
+
36
+ [project.optional-dependencies]
37
+ dev = [
38
+ "pytest>=8.0",
39
+ "pytest-cov>=5.0",
40
+ "ruff>=0.5",
41
+ ]
42
+
43
+ [project.scripts]
44
+ zenith-passkeys = "zenith_passkeys.main:cli"
45
+ zenithpasskeys = "zenith_passkeys.main:cli"
46
+ zenith-passkeys-host = "zenith_passkeys.native_host.host:main"
47
+
48
+ [project.urls]
49
+ Homepage = "https://github.com/roshhellwett/projectwarden"
50
+ Repository = "https://github.com/roshhellwett/projectwarden"
51
+ Documentation = "https://github.com/roshhellwett/projectwarden/blob/main/docs/SETUP.md"
52
+ Issues = "https://github.com/roshhellwett/projectwarden/issues"
53
+
54
+ [tool.hatch.build.targets.wheel]
55
+ packages = ["src/zenith_passkeys"]
@@ -0,0 +1 @@
1
+ __version__ = "1.0.0"
@@ -0,0 +1,4 @@
1
+ from .main import cli
2
+
3
+ if __name__ == "__main__":
4
+ cli()
@@ -0,0 +1,4 @@
1
+ """Local backup module for Zenith Passkeys.
2
+
3
+ Built and managed by Zenith Open Source Projects.
4
+ """
@@ -0,0 +1,43 @@
1
+ import click
2
+ from rich.console import Console
3
+ from rich.markup import escape
4
+
5
+ from ..core.errors import SUPPORT_EMAIL
6
+ from ..core.vault import VaultManager
7
+
8
+ console = Console()
9
+
10
+ VAULT_MISSING_MSG = "No vault found. Run [green]'zenith-passkeys init'[/green] first."
11
+
12
+
13
+ def handle_vault_error(e: Exception) -> None:
14
+ """Translate vault-layer failures into clean, actionable CLI errors."""
15
+ from cryptography.exceptions import InvalidTag
16
+
17
+ if isinstance(e, InvalidTag) or "password" in str(e).lower():
18
+ console.print("[red]Wrong password.[/red]")
19
+ elif "recovery" in str(e).lower():
20
+ console.print("[red]Wrong recovery key or corrupted vault.[/red]")
21
+ elif "corrupted" in str(e).lower() or "Invalid" in str(e):
22
+ console.print(f"[red]Vault file is corrupted: {escape(str(e))}[/red]")
23
+ console.print(f"[yellow]Contact {SUPPORT_EMAIL} with error code below if this persists.[/yellow]")
24
+ else:
25
+ console.print(f"[red]{escape(str(e))}[/red]")
26
+ raise SystemExit(1)
27
+
28
+
29
+ def require_vault(vault: VaultManager) -> None:
30
+ if not vault.exists():
31
+ console.print(f"[red]{VAULT_MISSING_MSG}[/red]")
32
+ raise SystemExit(1)
33
+
34
+
35
+ def unlock_vault(vault: VaultManager, prompt: str = "Master password") -> tuple:
36
+ """Prompt, unlock, and convert any failure into a clean exit."""
37
+ from cryptography.exceptions import InvalidTag
38
+
39
+ password = click.prompt(prompt, hide_input=True)
40
+ try:
41
+ return vault.unlock(password), password
42
+ except (InvalidTag, ValueError) as e:
43
+ handle_vault_error(e)
@@ -0,0 +1,93 @@
1
+ import json
2
+ from datetime import datetime, timezone
3
+
4
+ import click
5
+ from cryptography.exceptions import InvalidTag
6
+ from rich.console import Console
7
+ from rich.markup import escape
8
+ from rich.table import Table
9
+
10
+ from ..core.vault import VaultManager
11
+ from ._shared import handle_vault_error, require_vault
12
+
13
+ console = Console()
14
+
15
+
16
+ @click.command()
17
+ @click.option("--unused-days", default=90, type=int, help="Days of inactivity threshold")
18
+ @click.option("--dns", is_flag=True, help="Also check domain DNS resolution (makes network requests)")
19
+ @click.option("--json-output", "json_flag", is_flag=True, help="Output as JSON")
20
+ def audit(unused_days: int, dns: bool, json_flag: bool):
21
+ """Audit vault for security issues (offline by default)."""
22
+ vault = VaultManager()
23
+ require_vault(vault)
24
+
25
+ from ._shared import unlock_vault
26
+ try:
27
+ (payload, _), _pw = unlock_vault(vault)
28
+ except (InvalidTag, ValueError) as e:
29
+ handle_vault_error(e)
30
+
31
+ issues = []
32
+ now = datetime.now(timezone.utc)
33
+
34
+ for c in payload.credentials:
35
+ if c.last_used_at:
36
+ try:
37
+ last = datetime.fromisoformat(c.last_used_at)
38
+ except ValueError:
39
+ continue
40
+ if (now - last).days >= unused_days:
41
+ issues.append({
42
+ "type": "stale",
43
+ "severity": "low",
44
+ "rp_id": c.rp_id,
45
+ "username": c.username,
46
+ "detail": f"Not used in {(now - last).days} days",
47
+ })
48
+
49
+ seen = {}
50
+ for c in payload.credentials:
51
+ key = (c.rp_id, c.username)
52
+ if key in seen:
53
+ issues.append({
54
+ "type": "duplicate",
55
+ "severity": "medium",
56
+ "rp_id": c.rp_id,
57
+ "username": c.username,
58
+ "detail": "Multiple credentials for same rpId+username",
59
+ })
60
+ seen[key] = True
61
+
62
+ # DNS resolution is OPT-IN: it sends every stored domain to the system
63
+ # resolver, which leaks the user's site list. Default is offline.
64
+ if dns:
65
+ import socket
66
+ for domain in {c.rp_id for c in payload.credentials}:
67
+ try:
68
+ socket.getaddrinfo(domain, 443, socket.AF_INET, socket.SOCK_STREAM)
69
+ except socket.gaierror:
70
+ issues.append({
71
+ "type": "unresolvable",
72
+ "severity": "high",
73
+ "rp_id": domain,
74
+ "username": "",
75
+ "detail": f"Cannot resolve {domain}",
76
+ })
77
+
78
+ if json_flag:
79
+ console.print_json(json.dumps(issues, indent=2))
80
+ else:
81
+ if not issues:
82
+ console.print("[green]No issues found. Vault looks good.[/green]")
83
+ raise SystemExit(0)
84
+
85
+ table = Table(title=f"Audit Issues ({len(issues)})")
86
+ table.add_column("Severity", style="yellow")
87
+ table.add_column("Type", style="cyan")
88
+ table.add_column("Domain", style="white")
89
+ table.add_column("Detail", style="white")
90
+ for issue in issues:
91
+ table.add_row(escape(issue["severity"]), escape(issue["type"]), escape(issue["rp_id"]), escape(issue["detail"]))
92
+ console.print(table)
93
+ raise SystemExit(1)