mirror of
https://github.com/PR0M3TH3AN/SeedPass.git
synced 2026-10-27 07:52:03 +00:00
Add landing install copy UX and document remaining security/test TODOs
This commit is contained in:
1 parent
0789dfcb55
commit
3ea37865cb
7 files changed
+181
-4
No files matched your search
@@ -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
@@ -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>
|
||||
@@ -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
@@ -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
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
Reference in new issue
Block a user