Remove insecure parent seed endpoint

This commit is contained in:
thePR0M3TH3AN
2025-08-02 22:01:38 -04:00
parent 087b3bd657
commit 7aeba78245
5 changed files with 40 additions and 65 deletions

View File

@@ -706,7 +706,7 @@ You can also launch the GUI directly with `seedpass gui` or `seedpass-gui`.
- **Backup Your Data:** Regularly back up your encrypted data and checksum files to prevent data loss.
- **Backup the Settings PIN:** Your settings PIN is stored in the encrypted configuration file. Keep a copy of this file or remember the PIN, as losing it will require deleting the file and reconfiguring your relays.
- **Protect Your Passwords:** Do not share your master password or seed phrases with anyone and ensure they are strong and unique.
- **Revealing the Parent Seed:** The `vault reveal-parent-seed` command and `/api/v1/parent-seed` endpoint print your seed in plain text. Run them only in a secure environment.
- **Backing Up the Parent Seed:** Use the CLI `vault reveal-parent-seed` command or the `/api/v1/vault/backup-parent-seed` endpoint with explicit confirmation to create an encrypted backup. The API does not return the seed directly.
- **No PBKDF2 Salt Needed:** SeedPass deliberately omits an explicit PBKDF2 salt. Every password is derived from a unique 512-bit BIP-85 child seed, which already provides stronger per-password uniqueness than a conventional 128-bit salt.
- **Checksum Verification:** Always verify the script's checksum to ensure its integrity and protect against unauthorized modifications.
- **Potential Bugs and Limitations:** Be aware that the software may contain bugs and lacks certain features. Snapshot chunks are capped at 50 KB and the client rotates snapshots after enough delta events accumulate. The security of memory management and logs has not been thoroughly evaluated and may pose risks of leaking sensitive information.

View File

@@ -35,14 +35,13 @@ Keep this token secret and avoid logging it. Tokens expire after a few minutes a
- `GET /api/v1/totp` Return current TOTP codes and remaining time.
- `GET /api/v1/stats` Return statistics about the active seed profile.
- `GET /api/v1/notifications` Retrieve and clear queued notifications. Messages appear in the persistent notification box but remain queued until fetched.
- `GET /api/v1/parent-seed` Reveal the parent seed or save it with `?file=`. Requires an additional `X-SeedPass-Password` header.
- `GET /api/v1/nostr/pubkey` Fetch the Nostr public key for the active seed.
- `POST /api/v1/checksum/verify` Verify the checksum of the running script.
- `POST /api/v1/checksum/update` Update the stored script checksum.
- `POST /api/v1/change-password` Change the master password for the active profile.
- `POST /api/v1/vault/import` Import a vault backup from a file or path.
- `POST /api/v1/vault/export` Export the vault and download the encrypted file. Requires an additional `X-SeedPass-Password` header.
- `POST /api/v1/vault/backup-parent-seed` Save an encrypted backup of the parent seed.
- `POST /api/v1/vault/backup-parent-seed` Save an encrypted backup of the parent seed. Requires a `confirm` flag in the request body and an `X-SeedPass-Password` header.
- `POST /api/v1/vault/lock` Lock the vault and clear sensitive data from memory.
- `GET /api/v1/relays` List configured Nostr relays.
- `POST /api/v1/relays` Add a relay URL.
@@ -50,7 +49,6 @@ Keep this token secret and avoid logging it. Tokens expire after a few minutes a
- `POST /api/v1/relays/reset` Reset the relay list to defaults.
- `POST /api/v1/shutdown` Stop the server gracefully.
**Security Warning:** Accessing `/api/v1/parent-seed` exposes your master seed in plain text. Use it only from a trusted environment.
## Secure Deployment
@@ -205,8 +203,9 @@ Trigger an encrypted seed backup with `/api/v1/vault/backup-parent-seed`:
```bash
curl -X POST http://127.0.0.1:8000/api/v1/vault/backup-parent-seed \
-H "Authorization: Bearer <token>" \
-H "X-SeedPass-Password: <master password>" \
-H "Content-Type: application/json" \
-d '{"path": "seed_backup.enc"}'
-d '{"path": "seed_backup.enc", "confirm": true}'
```
### Retrieving Vault Statistics

View File

@@ -531,7 +531,7 @@ Mutation testing is disabled in the GitHub workflow due to reliability issues an
- **Backup Your Data:** Regularly back up your encrypted data and checksum files to prevent data loss.
- **Backup the Settings PIN:** Your settings PIN is stored in the encrypted configuration file. Keep a copy of this file or remember the PIN, as losing it will require deleting the file and reconfiguring your relays.
- **Protect Your Passwords:** Do not share your master password or seed phrases with anyone and ensure they are strong and unique.
- **Revealing the Parent Seed:** The `vault reveal-parent-seed` command and `/api/v1/parent-seed` endpoint print your seed in plain text. Run them only in a secure environment.
- **Backing Up the Parent Seed:** Use the CLI `vault reveal-parent-seed` command or the `/api/v1/vault/backup-parent-seed` endpoint with explicit confirmation to create an encrypted backup. The API does not return the seed directly.
- **No PBKDF2 Salt Needed:** SeedPass deliberately omits an explicit PBKDF2 salt. Every password is derived from a unique 512-bit BIP-85 child seed, which already provides stronger per-password uniqueness than a conventional 128-bit salt.
- **Checksum Verification:** Always verify the script's checksum to ensure its integrity and protect against unauthorized modifications.
- **Potential Bugs and Limitations:** Be aware that the software may contain bugs and lacks certain features. Snapshot chunks are capped at 50KB and the client rotates snapshots after enough delta events accumulate. The security of memory management and logs has not been thoroughly evaluated and may pose risks of leaking sensitive information.

View File

@@ -430,25 +430,6 @@ def get_notifications(authorization: str | None = Header(None)) -> List[dict]:
return notes
@app.get("/api/v1/parent-seed")
def get_parent_seed(
authorization: str | None = Header(None),
file: str | None = None,
password: str | None = Header(None, alias="X-SeedPass-Password"),
) -> dict:
"""Return the parent seed or save it as an encrypted backup."""
_check_token(authorization)
_require_password(password)
assert _pm is not None
if file:
path = Path(file)
_pm.encryption_manager.encrypt_and_save_file(
_pm.parent_seed.encode("utf-8"), path
)
return {"status": "saved", "path": str(path)}
return {"seed": _pm.parent_seed}
@app.get("/api/v1/nostr/pubkey")
def get_nostr_pubkey(authorization: str | None = Header(None)) -> Any:
_check_token(authorization)
@@ -581,18 +562,24 @@ async def import_vault(
@app.post("/api/v1/vault/backup-parent-seed")
def backup_parent_seed(
data: dict | None = None, authorization: str | None = Header(None)
data: dict,
authorization: str | None = Header(None),
password: str | None = Header(None, alias="X-SeedPass-Password"),
) -> dict[str, str]:
"""Backup and reveal the parent seed."""
"""Create an encrypted backup of the parent seed after confirmation."""
_check_token(authorization)
_require_password(password)
assert _pm is not None
path = None
if data is not None:
p = data.get("path")
if p:
path = Path(p)
_pm.handle_backup_reveal_parent_seed(path)
return {"status": "ok"}
if not data.get("confirm"):
raise HTTPException(status_code=400, detail="Confirmation required")
path_str = data.get("path")
if not path_str:
raise HTTPException(status_code=400, detail="Missing path")
path = Path(path_str)
_pm.encryption_manager.encrypt_and_save_file(_pm.parent_seed.encode("utf-8"), path)
return {"status": "saved", "path": str(path)}
@app.post("/api/v1/change-password")

View File

@@ -155,30 +155,10 @@ def test_totp_codes_endpoint(client):
}
def test_parent_seed_endpoint(client, tmp_path):
def test_parent_seed_endpoint_removed(client):
cl, token = client
api._pm.parent_seed = "seed"
called = {}
api._pm.encryption_manager = SimpleNamespace(
encrypt_and_save_file=lambda data, path: called.setdefault("path", path)
)
headers = {
"Authorization": f"Bearer {token}",
"X-SeedPass-Password": "pw",
}
res = cl.get("/api/v1/parent-seed", headers=headers)
assert res.status_code == 200
assert res.json() == {"seed": "seed"}
out = tmp_path / "bk.enc"
res = cl.get("/api/v1/parent-seed", params={"file": str(out)}, headers=headers)
assert res.status_code == 200
assert res.json() == {"status": "saved", "path": str(out)}
assert called["path"] == out
res = cl.get("/api/v1/parent-seed", headers={"Authorization": f"Bearer {token}"})
assert res.status_code == 401
assert res.status_code == 404
def test_fingerprint_endpoints(client):
@@ -350,22 +330,31 @@ def test_vault_export_endpoint(client, tmp_path):
def test_backup_parent_seed_endpoint(client, tmp_path):
cl, token = client
api._pm.parent_seed = "seed"
called = {}
def backup(path=None):
called["path"] = path
api._pm.handle_backup_reveal_parent_seed = backup
api._pm.encryption_manager = SimpleNamespace(
encrypt_and_save_file=lambda data, path: called.setdefault("path", path)
)
path = tmp_path / "seed.enc"
headers = {"Authorization": f"Bearer {token}"}
headers = {
"Authorization": f"Bearer {token}",
"X-SeedPass-Password": "pw",
}
res = cl.post(
"/api/v1/vault/backup-parent-seed",
json={"path": str(path), "confirm": True},
headers=headers,
)
assert res.status_code == 200
assert res.json() == {"status": "saved", "path": str(path)}
assert called["path"] == path
res = cl.post(
"/api/v1/vault/backup-parent-seed",
json={"path": str(path)},
headers=headers,
)
assert res.status_code == 200
assert res.json() == {"status": "ok"}
assert called["path"] == path
assert res.status_code == 400
def test_relay_management_endpoints(client, dummy_nostr_client, monkeypatch):