From 755c26865c8e111aede8bcbe150a03e305a0ec60 Mon Sep 17 00:00:00 2001 From: gitea-impl-agent Date: Sat, 3 Oct 2026 00:17:44 +0000 Subject: [PATCH] docs(auth): preserve login runtime evidence --- .../evidence/issue-6-d6-23-login-runtime.json | 73 +++++++++++++++++++ 1 file changed, 73 insertions(+) create mode 100644 docs/evidence/issue-6-d6-23-login-runtime.json diff --git a/docs/evidence/issue-6-d6-23-login-runtime.json b/docs/evidence/issue-6-d6-23-login-runtime.json new file mode 100644 index 0000000..e58e142 --- /dev/null +++ b/docs/evidence/issue-6-d6-23-login-runtime.json @@ -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." + ] +}