docs(auth): preserve login runtime evidence

This commit is contained in:
2026-10-03 00:17:44 +00:00
parent e7242a89f7
commit 755c26865c
@@ -0,0 +1,73 @@
{
"schema": "jarca-ledger.review-evidence/v1",
"issue": 6,
"pull_request": 7,
"source_review": 51,
"finding": "D6-23",
"contract_sha256": "4f734169ec896747fe39fd7f3ab75d1fc0d8cdd4903045e665beba40d80eb1fe",
"verified_at_utc": "2026-10-03T00:10:43Z",
"source_revision": "e7242a89f7f1faacbe984713a5ad211bd7bcac2b",
"source_tree_clean_before_evidence_write": true,
"environment": {
"os": "Linux 6.18.34+rpt-rpi-v8",
"architecture": "aarch64",
"python": "3.13.5",
"starlette": "1.3.1",
"httpx": "0.28.1",
"streamlit": "1.58.0",
"uvicorn": "0.49.0",
"itsdangerous": "2.2.0",
"credentials": "synthetic test-only values; not recorded",
"signing_secret": "synthetic test-only value; not recorded"
},
"commands": [
{
"purpose": "Focused route, hardened cookie, event-loop offload, and failed-delay helper verification",
"command": "uv run --extra dashboard --extra dev pytest -q tests/test_dashboard_auth_server.py tests/test_dashboard_authentication.py",
"exit_code": 0,
"result": "29 passed; one Starlette/httpx deprecation warning",
"relevant_output": "............................. [100%]\n=============================== warnings summary ===============================\ntests/test_dashboard_auth_server.py:11\n /opt/data/gitea-agent/repositories/jsfamily/jarca-ledger-mcp/worktrees/issue-6/checkout/tests/test_dashboard_auth_server.py:11: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.\n from starlette.testclient import TestClient\n\n-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html"
},
{
"purpose": "Real-delay concurrent failed login plus successful login and hardened cookie runtime probe",
"command": "uv run --extra dashboard python - <<'PY'\nimport asyncio\nimport json\nimport os\nimport secrets\nimport time\n\nfrom starlette.requests import Request\n\nfrom dashboard import authentication\nfrom dashboard.auth_server import login\n\nadmin_id = \"synthetic-\" + secrets.token_hex(8)\nadmin_password = secrets.token_hex(16)\nos.environ[authentication.ADMIN_ID_ENV] = admin_id\nos.environ[authentication.ADMIN_PASSWORD_ENV] = admin_password\nos.environ[authentication.SIGNING_SECRET_ENV] = secrets.token_hex(32)\n\n\ndef request(password: str) -> Request:\n body = f\"admin_id={admin_id}&admin_password={password}\".encode()\n sent = False\n\n async def receive():\n nonlocal sent\n if sent:\n return {\"type\": \"http.disconnect\"}\n sent = True\n return {\"type\": \"http.request\", \"body\": body, \"more_body\": False}\n\n scope = {\n \"type\": \"http\",\n \"method\": \"POST\",\n \"scheme\": \"https\",\n \"path\": \"/api/auth/login\",\n \"raw_path\": b\"/api/auth/login\",\n \"query_string\": b\"\",\n \"headers\": [\n (b\"host\", b\"testserver\"),\n (b\"origin\", b\"https://testserver\"),\n (b\"content-type\", b\"application/x-www-form-urlencoded\"),\n ],\n \"client\": (\"testclient\", 50000),\n \"server\": (\"testserver\", 443),\n }\n return Request(scope, receive)\n\n\nasync def main() -> None:\n wrong_password = secrets.token_hex(16)\n started = time.monotonic()\n failures = [asyncio.create_task(login(request(wrong_password))) for _ in range(2)]\n await asyncio.sleep(0.1)\n probe_elapsed = time.monotonic() - started\n success_started = time.monotonic()\n success = await login(request(admin_password))\n success_elapsed = time.monotonic() - success_started\n failed = await asyncio.gather(*failures)\n total_elapsed = time.monotonic() - started\n cookie = success.headers.get(\"set-cookie\", \"\")\n assert success.status_code == 303\n assert success.headers[\"location\"] == \"/\"\n assert all(marker in cookie for marker in [\"HttpOnly\", \"Secure\", \"SameSite=lax\", \"Path=/\", \"Max-Age=86400\"])\n assert all(response.status_code == 303 for response in failed)\n assert all(\"set-cookie\" not in response.headers for response in failed)\n assert probe_elapsed < 0.5\n assert success_elapsed < 0.5\n assert 0.95 <= total_elapsed < 1.75\n print(json.dumps({\n \"success\": True,\n \"source_revision\": \"e7242a89f7f1faacbe984713a5ad211bd7bcac2b\",\n \"successful_login_status\": success.status_code,\n \"successful_login_cookie_hardened\": True,\n \"failed_login_statuses\": [response.status_code for response in failed],\n \"failed_login_cookie_absent\": True,\n \"event_loop_probe_elapsed_seconds\": round(probe_elapsed, 6),\n \"successful_login_elapsed_seconds\": round(success_elapsed, 6),\n \"concurrent_failed_logins_total_elapsed_seconds\": round(total_elapsed, 6),\n \"minimum_failed_login_delay_seconds\": 1.0,\n \"credentials_and_cookie_values_recorded\": False,\n }, indent=2))\n\n\nasyncio.run(main())\nPY",
"exit_code": 0,
"result": {
"success": true,
"source_revision": "e7242a89f7f1faacbe984713a5ad211bd7bcac2b",
"successful_login_status": 303,
"successful_login_cookie_hardened": true,
"failed_login_statuses": [
303,
303
],
"failed_login_cookie_absent": true,
"event_loop_probe_elapsed_seconds": 0.10056,
"successful_login_elapsed_seconds": 0.001267,
"concurrent_failed_logins_total_elapsed_seconds": 1.00614,
"minimum_failed_login_delay_seconds": 1.0,
"credentials_and_cookie_values_recorded": false
},
"relevant_output": "{\n \"success\": true,\n \"source_revision\": \"e7242a89f7f1faacbe984713a5ad211bd7bcac2b\",\n \"successful_login_status\": 303,\n \"successful_login_cookie_hardened\": true,\n \"failed_login_statuses\": [\n 303,\n 303\n ],\n \"failed_login_cookie_absent\": true,\n \"event_loop_probe_elapsed_seconds\": 0.10056,\n \"successful_login_elapsed_seconds\": 0.001267,\n \"concurrent_failed_logins_total_elapsed_seconds\": 1.00614,\n \"minimum_failed_login_delay_seconds\": 1.0,\n \"credentials_and_cookie_values_recorded\": false\n}"
}
],
"hashes_sha256": {
"dashboard/auth_server.py": "4b991fbe942b573f8be270538b7e9df81bca9ffed3a8982f09db0d95d26e66d1",
"dashboard/authentication.py": "63df2e33ef10859039c9b8140e92cbf0c7c8c9fe2d19cfcedbf16637c8cc2596",
"tests/test_dashboard_auth_server.py": "8b96421af776b613867a8a5ab5dd962033b39d1cc616ba4e91cf3c4de63a1f0d",
"tests/test_dashboard_authentication.py": "1ca7ecc4d4db55c674aa2934d665b121476ff5fa2e1f74071090a67e815af676",
"pyproject.toml": "64100d3aa1c2272df2e2139ed435724901f7215ea72e48bee9d73f95e7e53a8d"
},
"redaction": {
"credentials_recorded": false,
"signing_secret_recorded": false,
"token_or_cookie_value_recorded": false,
"request_body_recorded": false,
"raw_set_cookie_recorded": false
},
"notes": [
"This artifact is an evidence-only descendant of source_revision; the application and test paths hashed above were not modified before these commands ran.",
"The runtime probe exercised the changed streaming login path with synthetic credentials, confirmed hardened 24-hour cookie issuance, retained the real one-second failed-login delay, and showed the event loop and a successful login remained responsive while two failed requests waited.",
"This D6-23 evidence does not replace or claim a fresh browser run; D6-22 remains supported by docs/evidence/issue-6-persistent-auth-https.json and the Review #51 change-impact analysis."
]
}