Add landing install copy UX and document remaining security/test TODOs

This commit is contained in:
thePR0M3TH3AN committed 2026-02-23 16:33:57 -05:00
1 parent 0789dfcb55
commit 3ea37865cb
7 files changed
+181 -4

No files matched your search

+48
View File
@@ -0,0 +1,48 @@
# SeedPass TODO
This file tracks the remaining work from the latest bug search and security evaluation.
## Security
- [ ] Upgrade vulnerable runtime dependencies in lockfiles and packaging inputs:
- `aiohttp` -> `>=3.13.3`
- `cryptography` -> `>=46.0.5`
- `starlette` -> `>=0.49.1`
- `python-multipart` -> `>=0.0.22`
- `urllib3` -> `>=2.6.3`
- `pynacl` -> `>=1.6.2`
- `pillow` -> `>=12.1.1`
- `orjson`, `pyasn1`, `cbor2` and other `pip-audit` findings
- [ ] Regenerate `requirements.lock` after upgrades and re-run the full test suite.
- [ ] Re-run `pip-audit -r requirements.lock` and ensure zero actionable runtime vulnerabilities.
- [ ] Evaluate the `Crypto.*` imports in `src/seedpass/core/password_generation.py` and either:
- keep `pycryptodome` intentionally with explicit documentation, or
- migrate RSA generation to `cryptography` APIs.
## Robustness
- [ ] Replace broad `except Exception: pass` blocks with explicit exceptions + logging:
- `src/main.py`
- `src/seedpass/core/manager.py`
- `src/seedpass/core/menu_handler.py`
- [ ] Add telemetry-safe logs for swallowed error paths without exposing secrets.
## Tests To Add
- [ ] Add deterministic tests for `derive_pgp_key(..., key_type="rsa")` covering:
- stable output across repeated runs
- key fingerprint format and validity
- [ ] Add API rate-limit tests for:
- per-client isolation
- window expiry behavior
- restart/reset behavior after `start_server()`
- [ ] Add API import tests for boundary conditions:
- exactly-at-limit upload size
- malformed multipart payload
- invalid JSON body for path-based import
- [ ] Add regression tests for manager notification/logging fallback paths currently guarded by broad exception handling.
## Documentation
- [ ] Document API import upload limit (`SEEDPASS_MAX_IMPORT_BYTES`) in user docs.
- [ ] Add a security maintenance section describing dependency-audit cadence and update policy.
+11 -1
View File
@@ -76,6 +76,16 @@ flowchart TB
classDef default fill:#ffffff,stroke:#e94a39,stroke-width:2px,color:#283c4f;
</pre>
<a href="https://github.com/PR0M3TH3AN/SeedPass" class="btn-primary cta-button"><i class="fas fa-download" aria-hidden="true"></i> Get Started</a>
<div class="install-quickstart">
<p class="install-label">Install SeedPass (stable)</p>
<div class="install-command-shell">
<pre class="code-block install-code" id="install-command">bash -c "$(curl -sSL https://raw.githubusercontent.com/PR0M3TH3AN/SeedPass/main/scripts/install.sh)"</pre>
<button class="install-copy-button" type="button" data-copy-target="install-command" aria-label="Copy install command">
<i class="fas fa-copy" aria-hidden="true"></i>
Copy
</button>
</div>
</div>
</div>
</section>
<!-- Architecture Section -->
@@ -257,4 +267,4 @@ Enter your choice (1-7) or press Enter to exit:
<script src="./script.js"></script>
<!-- Relative path to script.js -->
</body>
</html>
</html>
+29
View File
@@ -65,5 +65,34 @@ document.querySelectorAll('a[href^="#"]').forEach(anchor => {
// Log when the page is loaded
document.addEventListener('DOMContentLoaded', function() {
document.querySelectorAll('[data-copy-target]').forEach((button) => {
button.addEventListener('click', async () => {
const targetId = button.getAttribute('data-copy-target');
const target = document.getElementById(targetId);
if (!target) return;
const text = target.textContent.trim();
try {
await navigator.clipboard.writeText(text);
} catch (_err) {
const temp = document.createElement('textarea');
temp.value = text;
document.body.appendChild(temp);
temp.select();
document.execCommand('copy');
document.body.removeChild(temp);
}
const original = button.innerHTML;
button.classList.add('copied');
button.innerHTML = '<i class="fas fa-check" aria-hidden="true"></i> Copied';
setTimeout(() => {
button.classList.remove('copied');
button.innerHTML = original;
}, 1800);
});
});
console.log('SeedPass landing page loaded');
});
+70 -1
View File
@@ -231,6 +231,14 @@ footer .social-media a:focus {
.nav-links.active {
display: flex;
}
.install-command-shell {
flex-direction: column;
}
.install-copy-button {
min-height: 40px;
}
}
/* Section Titles */
@@ -337,6 +345,53 @@ footer .social-media a:focus {
transform: translateY(-3px);
}
.install-quickstart {
margin: 28px auto 0;
max-width: 900px;
text-align: left;
}
.install-label {
margin: 0 0 8px;
font-size: 0.95rem;
color: rgba(255, 255, 255, 0.9);
}
.install-command-shell {
display: flex;
align-items: stretch;
gap: 10px;
}
.install-code {
flex: 1;
margin: 0;
}
.install-copy-button {
border: 1px solid rgba(255, 255, 255, 0.25);
background: rgba(255, 255, 255, 0.08);
color: #ffffff;
border-radius: 8px;
padding: 0 14px;
font-weight: 600;
cursor: pointer;
white-space: nowrap;
transition: background-color 0.2s ease, border-color 0.2s ease;
}
.install-copy-button:hover,
.install-copy-button:focus {
background: rgba(255, 255, 255, 0.18);
border-color: rgba(255, 255, 255, 0.45);
outline: none;
}
.install-copy-button.copied {
background: #2ea043;
border-color: #2ea043;
}
/* Mini flow chart in hero */
.mini-chart {
max-width: 600px;
@@ -985,6 +1040,20 @@ body.dark-mode .code-block {
box-shadow: 0 2px 8px var(--shadow-dark);
}
body.dark-mode .install-label {
color: #c9d1d9;
}
body.dark-mode .install-copy-button {
border-color: rgba(255, 255, 255, 0.3);
background: rgba(255, 255, 255, 0.08);
}
body.dark-mode .install-copy-button:hover,
body.dark-mode .install-copy-button:focus {
background: rgba(255, 255, 255, 0.18);
}
body.dark-mode .disclaimer .container {
background-color: #2c2f33;
color: var(--footer-text);
@@ -1002,4 +1071,4 @@ body.dark-mode .disclaimer ul li:focus-within {
body.dark-mode footer {
background-color: var(--background-dark);
}
}
+5 -2
View File
@@ -10,6 +10,7 @@ import queue
import time
from collections import defaultdict, deque
from typing import Any, List, Optional
import hashlib
import logging
@@ -48,7 +49,8 @@ async def dynamic_cors_headers(request: Request, call_next):
def _get_pm(request: Request) -> PasswordManager:
pm = getattr(request.app.state, "pm", None)
assert pm is not None
if pm is None:
raise HTTPException(status_code=503, detail="Server not initialized")
return pm
@@ -66,7 +68,8 @@ def _check_token(request: Request, auth: str | None) -> None:
def _enforce_rate_limit(request: Request, raw_token: str) -> None:
now = time.monotonic()
client = request.client.host if request.client else "unknown"
key = f"{client}:{raw_token}"
token_tag = hashlib.blake2s(raw_token.encode("utf-8"), digest_size=8).hexdigest()
key = f"{client}:{token_tag}"
buckets = getattr(request.app.state, "rate_limit_buckets", None)
if buckets is None:
buckets = defaultdict(deque)
+15
View File
@@ -9,6 +9,21 @@ sys.path.append(str(Path(__file__).resolve().parents[1]))
from seedpass import api
def test_get_pm_requires_initialized_state():
class DummyState:
pass
class DummyApp:
state = DummyState()
class DummyRequest:
app = DummyApp()
with pytest.raises(api.HTTPException) as exc:
api._get_pm(DummyRequest())
assert exc.value.status_code == 503
@pytest.mark.anyio
async def test_token_hashed(client):
_, token = client
+3
View File
@@ -45,3 +45,6 @@ async def test_rate_limit_exceeded(monkeypatch):
res = await client.get("/api/v1/entry", params={"query": "s"}, headers=headers)
assert res.status_code == 429
bucket_keys = list(api.app.state.rate_limit_buckets.keys())
assert bucket_keys
assert all(token not in key for key in bucket_keys)