loki-mode 6.71.1 → 6.72.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +9 -1
- package/SKILL.md +2 -2
- package/VERSION +1 -1
- package/autonomy/hooks/migration-hooks.sh +26 -0
- package/autonomy/loki +429 -92
- package/autonomy/run.sh +219 -38
- package/dashboard/__init__.py +1 -1
- package/dashboard/server.py +101 -19
- package/docs/INSTALLATION.md +20 -11
- package/docs/bug-fixes/agent-01-cli-fixes.md +101 -0
- package/docs/bug-fixes/agent-02-purplelab-fixes.md +88 -0
- package/docs/bug-fixes/agent-03-dashboard-fixes.md +119 -0
- package/docs/bug-fixes/agent-04-memory-fixes.md +105 -0
- package/docs/bug-fixes/agent-05-provider-fixes.md +86 -0
- package/docs/bug-fixes/agent-06-integration-fixes.md +101 -0
- package/docs/bug-fixes/agent-07-dash-run-fixes.md +101 -0
- package/docs/bug-fixes/agent-08-docker-fixes.md +164 -0
- package/docs/bug-fixes/agent-09-e2e-build-fixes.md +69 -0
- package/docs/bug-fixes/agent-10-e2e-fullstack-fixes.md +102 -0
- package/docs/bug-fixes/agent-11-e2e-session-fixes.md +70 -0
- package/docs/bug-fixes/agent-12-scenario-fixes.md +120 -0
- package/docs/bug-fixes/agent-13-enterprise-fixes.md +143 -0
- package/docs/bug-fixes/agent-14-uat-newuser-fixes.md +88 -0
- package/docs/bug-fixes/agent-15-uat-poweruser-fixes.md +132 -0
- package/docs/bug-fixes/agent-19-code-review.md +316 -0
- package/docs/bug-fixes/agent-20-architecture-review.md +331 -0
- package/docs/competitive/bolt-new-analysis.md +579 -0
- package/docs/competitive/emergence-others-analysis.md +605 -0
- package/docs/competitive/replit-lovable-analysis.md +622 -0
- package/docs/test-scenarios/edge-cases.md +813 -0
- package/docs/test-scenarios/enterprise-scenarios.md +732 -0
- package/mcp/__init__.py +1 -1
- package/mcp/server.py +49 -5
- package/memory/consolidation.py +33 -0
- package/memory/embeddings.py +10 -1
- package/memory/engine.py +83 -38
- package/memory/retrieval.py +36 -0
- package/memory/storage.py +56 -4
- package/memory/token_economics.py +14 -2
- package/memory/vector_index.py +36 -7
- package/package.json +1 -1
- package/providers/gemini.sh +89 -2
- package/templates/README.md +1 -1
- package/templates/cli-tool.md +30 -0
- package/templates/dashboard.md +4 -0
- package/templates/data-pipeline.md +4 -0
- package/templates/discord-bot.md +47 -0
- package/templates/game.md +4 -0
- package/templates/microservice.md +4 -0
- package/templates/npm-library.md +4 -0
- package/templates/rest-api-auth.md +50 -20
- package/templates/rest-api.md +15 -0
- package/templates/saas-starter.md +1 -1
- package/templates/slack-bot.md +36 -0
- package/templates/static-landing-page.md +9 -1
- package/templates/web-scraper.md +4 -0
- package/web-app/dist/assets/Badge-CeBkFjo6.js +1 -0
- package/web-app/dist/assets/Button-yuhqo8Fq.js +1 -0
- package/web-app/dist/assets/{Card-B1bV4syB.js → Card-BG17vsX0.js} +1 -1
- package/web-app/dist/assets/{HomePage-CZTV6Nea.js → HomePage-BMSQ7Apj.js} +3 -3
- package/web-app/dist/assets/{LoginPage-D4UdURJc.js → LoginPage-aH_6iolg.js} +1 -1
- package/web-app/dist/assets/{NotFoundPage-CCLSeL6j.js → NotFoundPage-Di8cNtB1.js} +1 -1
- package/web-app/dist/assets/ProjectPage-BtRssmw9.js +285 -0
- package/web-app/dist/assets/ProjectsPage-B-FTFagc.js +6 -0
- package/web-app/dist/assets/{SettingsPage-Xuv8EfAg.js → SettingsPage-DIJPBla4.js} +1 -1
- package/web-app/dist/assets/TeamsPage--19fNX7w.js +36 -0
- package/web-app/dist/assets/TemplatesPage-ChUQNOOv.js +11 -0
- package/web-app/dist/assets/TerminalOutput-Dwrzecyl.js +31 -0
- package/web-app/dist/assets/activity-BNRWeu9N.js +6 -0
- package/web-app/dist/assets/{arrow-left-CaGtolHc.js → arrow-left-Ce6g1_YE.js} +1 -1
- package/web-app/dist/assets/circle-alert-LIndawHL.js +11 -0
- package/web-app/dist/assets/clock-Bpj4VPlP.js +6 -0
- package/web-app/dist/assets/{external-link-CazyUyav.js → external-link-BhhdF0iQ.js} +1 -1
- package/web-app/dist/assets/folder-open-CM2LgfxI.js +11 -0
- package/web-app/dist/assets/index-8-KpWWq7.css +1 -0
- package/web-app/dist/assets/index-kPDW4e_b.js +236 -0
- package/web-app/dist/assets/lock-sAk3Xe54.js +16 -0
- package/web-app/dist/assets/search-CR-2i9by.js +6 -0
- package/web-app/dist/assets/server-DuFh4ymA.js +26 -0
- package/web-app/dist/assets/trash-2-BmkkT8V_.js +11 -0
- package/web-app/dist/index.html +2 -2
- package/web-app/server.py +1321 -53
- package/web-app/dist/assets/Badge-CBUx2PjL.js +0 -6
- package/web-app/dist/assets/Button-DsRiznlh.js +0 -21
- package/web-app/dist/assets/ProjectPage-D0w_X9tG.js +0 -237
- package/web-app/dist/assets/ProjectsPage-ByYxDlKC.js +0 -16
- package/web-app/dist/assets/TemplatesPage-BKWN07mc.js +0 -1
- package/web-app/dist/assets/TerminalOutput-Dj98V8Z-.js +0 -51
- package/web-app/dist/assets/clock-C_CDmobx.js +0 -11
- package/web-app/dist/assets/index-D452pFGl.css +0 -1
- package/web-app/dist/assets/index-Df4_kgLY.js +0 -196
package/dashboard/server.py
CHANGED
|
@@ -93,8 +93,14 @@ def _safe_json_read(path: _Path, default: Any = None) -> Any:
|
|
|
93
93
|
|
|
94
94
|
|
|
95
95
|
def _safe_read_text(path: _Path) -> str:
|
|
96
|
-
"""Read a text file with UTF-8 encoding, replacing non-UTF-8 bytes.
|
|
97
|
-
|
|
96
|
+
"""Read a text file with UTF-8 encoding, replacing non-UTF-8 bytes.
|
|
97
|
+
|
|
98
|
+
Returns empty string on any I/O or encoding error (truly safe).
|
|
99
|
+
"""
|
|
100
|
+
try:
|
|
101
|
+
return _Path(path).read_text(encoding="utf-8", errors="replace")
|
|
102
|
+
except (OSError, IOError, ValueError):
|
|
103
|
+
return ""
|
|
98
104
|
|
|
99
105
|
|
|
100
106
|
# ---------------------------------------------------------------------------
|
|
@@ -362,13 +368,39 @@ async def _push_loki_state_loop() -> None:
|
|
|
362
368
|
try:
|
|
363
369
|
raw = _safe_json_read(state_file, {})
|
|
364
370
|
# Transform to StatusResponse-compatible format
|
|
371
|
+
# BUG-NEW-001: Validate agent PIDs (match get_status behavior)
|
|
365
372
|
agents_list = raw.get("agents", [])
|
|
366
|
-
running_agents =
|
|
373
|
+
running_agents = 0
|
|
374
|
+
if isinstance(agents_list, list):
|
|
375
|
+
for _agent in agents_list:
|
|
376
|
+
_apid = _agent.get("pid") if isinstance(_agent, dict) else None
|
|
377
|
+
if _apid:
|
|
378
|
+
try:
|
|
379
|
+
os.kill(int(_apid), 0)
|
|
380
|
+
running_agents += 1
|
|
381
|
+
except (OSError, ValueError, TypeError):
|
|
382
|
+
pass
|
|
383
|
+
else:
|
|
384
|
+
running_agents += 1 # No PID field -- count as running (legacy)
|
|
367
385
|
tasks = raw.get("tasks", {})
|
|
368
386
|
pending = tasks.get("pending", [])
|
|
369
387
|
in_prog = tasks.get("inProgress", [])
|
|
388
|
+
# BUG-NEW-006: Cross-check PID to avoid broadcasting
|
|
389
|
+
# stale "running" status when process has crashed
|
|
390
|
+
_pid_alive = False
|
|
391
|
+
_ws_pid_file = loki_dir / "loki.pid"
|
|
392
|
+
if _ws_pid_file.exists():
|
|
393
|
+
try:
|
|
394
|
+
_ws_pid = int(_ws_pid_file.read_text().strip())
|
|
395
|
+
os.kill(_ws_pid, 0)
|
|
396
|
+
_pid_alive = True
|
|
397
|
+
except (ValueError, OSError, ProcessLookupError):
|
|
398
|
+
pass
|
|
399
|
+
|
|
370
400
|
status_str = raw.get("mode", "autonomous")
|
|
371
|
-
if
|
|
401
|
+
if not _pid_alive:
|
|
402
|
+
status_str = "stopped"
|
|
403
|
+
elif status_str == "paused":
|
|
372
404
|
status_str = "paused"
|
|
373
405
|
elif status_str in ("stopped", ""):
|
|
374
406
|
status_str = "stopped"
|
|
@@ -513,7 +545,7 @@ async def agent_card() -> dict:
|
|
|
513
545
|
"multi_tenant": True,
|
|
514
546
|
"rbac": True,
|
|
515
547
|
"audit_log": True,
|
|
516
|
-
"sso":
|
|
548
|
+
"sso": auth.is_oidc_mode(),
|
|
517
549
|
},
|
|
518
550
|
"authentication": {
|
|
519
551
|
"schemes": ["bearer", "api-key"],
|
|
@@ -1077,7 +1109,15 @@ async def list_tasks(
|
|
|
1077
1109
|
fpath = queue_dir / queue_file
|
|
1078
1110
|
if fpath.exists():
|
|
1079
1111
|
try:
|
|
1080
|
-
|
|
1112
|
+
raw_items = json.loads(fpath.read_text())
|
|
1113
|
+
# BUG-NEW-002: Support both array [...] and object {"tasks": [...]} formats
|
|
1114
|
+
# (matches run.sh load_queue_tasks which supports both)
|
|
1115
|
+
if isinstance(raw_items, dict):
|
|
1116
|
+
items = raw_items.get("tasks", [])
|
|
1117
|
+
elif isinstance(raw_items, list):
|
|
1118
|
+
items = raw_items
|
|
1119
|
+
else:
|
|
1120
|
+
items = []
|
|
1081
1121
|
if isinstance(items, list):
|
|
1082
1122
|
for i, item in enumerate(items):
|
|
1083
1123
|
if isinstance(item, dict):
|
|
@@ -1592,8 +1632,9 @@ async def sync_registry():
|
|
|
1592
1632
|
raise HTTPException(status_code=429, detail="Rate limit exceeded")
|
|
1593
1633
|
|
|
1594
1634
|
try:
|
|
1635
|
+
loop = asyncio.get_running_loop()
|
|
1595
1636
|
result = await asyncio.wait_for(
|
|
1596
|
-
|
|
1637
|
+
loop.run_in_executor(None, registry.sync_registry_with_discovery),
|
|
1597
1638
|
timeout=30.0,
|
|
1598
1639
|
)
|
|
1599
1640
|
except asyncio.TimeoutError:
|
|
@@ -1630,7 +1671,7 @@ class FocusRequest(BaseModel):
|
|
|
1630
1671
|
project_dir: str
|
|
1631
1672
|
|
|
1632
1673
|
|
|
1633
|
-
@app.post("/api/focus")
|
|
1674
|
+
@app.post("/api/focus", dependencies=[Depends(auth.require_scope("control"))])
|
|
1634
1675
|
async def set_focus(request: FocusRequest):
|
|
1635
1676
|
"""Set the active project directory for .loki/ resolution.
|
|
1636
1677
|
|
|
@@ -1642,10 +1683,17 @@ async def set_focus(request: FocusRequest):
|
|
|
1642
1683
|
project_dir = request.project_dir.strip()
|
|
1643
1684
|
if not project_dir:
|
|
1644
1685
|
raise HTTPException(status_code=400, detail="project_dir must not be empty")
|
|
1645
|
-
p = _Path(project_dir)
|
|
1686
|
+
p = _Path(project_dir).resolve()
|
|
1646
1687
|
if not p.is_dir():
|
|
1647
1688
|
raise HTTPException(status_code=400, detail=f"Directory does not exist: {project_dir}")
|
|
1648
|
-
|
|
1689
|
+
# Require the target directory to contain a .loki/ subdirectory to prevent
|
|
1690
|
+
# pointing the dashboard at arbitrary filesystem locations.
|
|
1691
|
+
if not (p / ".loki").is_dir():
|
|
1692
|
+
raise HTTPException(
|
|
1693
|
+
status_code=400,
|
|
1694
|
+
detail=f"Directory does not contain a .loki/ subdirectory: {project_dir}"
|
|
1695
|
+
)
|
|
1696
|
+
_active_project_dir = str(p)
|
|
1649
1697
|
return {"project_dir": _active_project_dir, "loki_dir": str(_get_loki_dir())}
|
|
1650
1698
|
|
|
1651
1699
|
|
|
@@ -1658,7 +1706,7 @@ async def get_focus():
|
|
|
1658
1706
|
}
|
|
1659
1707
|
|
|
1660
1708
|
|
|
1661
|
-
@app.delete("/api/focus")
|
|
1709
|
+
@app.delete("/api/focus", dependencies=[Depends(auth.require_scope("control"))])
|
|
1662
1710
|
async def clear_focus():
|
|
1663
1711
|
"""Clear the active project directory override (revert to CWD-based resolution)."""
|
|
1664
1712
|
global _active_project_dir
|
|
@@ -1752,7 +1800,7 @@ async def create_token(request: TokenCreateRequest):
|
|
|
1752
1800
|
raise HTTPException(status_code=400, detail=str(e))
|
|
1753
1801
|
|
|
1754
1802
|
|
|
1755
|
-
@app.get("/api/enterprise/tokens", response_model=list[TokenResponse])
|
|
1803
|
+
@app.get("/api/enterprise/tokens", response_model=list[TokenResponse], dependencies=[Depends(auth.require_scope("admin"))])
|
|
1756
1804
|
async def list_tokens(include_revoked: bool = False):
|
|
1757
1805
|
"""List all API tokens (enterprise only)."""
|
|
1758
1806
|
if not auth.is_enterprise_mode():
|
|
@@ -2347,7 +2395,7 @@ def _read_learning_signals(signal_type: Optional[str] = None, limit: int = 50) -
|
|
|
2347
2395
|
|
|
2348
2396
|
@app.get("/api/learning/metrics")
|
|
2349
2397
|
async def get_learning_metrics(
|
|
2350
|
-
timeRange: str = "7d",
|
|
2398
|
+
timeRange: str = Query("7d", pattern=r"^\d{1,4}[hdm]$"),
|
|
2351
2399
|
signalType: Optional[str] = None,
|
|
2352
2400
|
source: Optional[str] = None,
|
|
2353
2401
|
):
|
|
@@ -2416,7 +2464,7 @@ async def get_learning_metrics(
|
|
|
2416
2464
|
|
|
2417
2465
|
@app.get("/api/learning/trends")
|
|
2418
2466
|
async def get_learning_trends(
|
|
2419
|
-
timeRange: str = "7d",
|
|
2467
|
+
timeRange: str = Query("7d", pattern=r"^\d{1,4}[hdm]$"),
|
|
2420
2468
|
signalType: Optional[str] = None,
|
|
2421
2469
|
source: Optional[str] = None,
|
|
2422
2470
|
):
|
|
@@ -2436,7 +2484,7 @@ async def get_learning_trends(
|
|
|
2436
2484
|
|
|
2437
2485
|
@app.get("/api/learning/signals")
|
|
2438
2486
|
async def get_learning_signals(
|
|
2439
|
-
timeRange: str = "7d",
|
|
2487
|
+
timeRange: str = Query("7d", pattern=r"^\d{1,4}[hdm]$"),
|
|
2440
2488
|
signalType: Optional[str] = None,
|
|
2441
2489
|
source: Optional[str] = None,
|
|
2442
2490
|
limit: int = Query(default=50, ge=1, le=1000),
|
|
@@ -2876,14 +2924,30 @@ async def stop_session(request: Request):
|
|
|
2876
2924
|
stop_file.parent.mkdir(parents=True, exist_ok=True)
|
|
2877
2925
|
stop_file.write_text(datetime.now(timezone.utc).isoformat())
|
|
2878
2926
|
|
|
2879
|
-
#
|
|
2927
|
+
# BUG-ST-004: Send SIGTERM and wait for process to actually exit
|
|
2880
2928
|
pid_file = _get_loki_dir() / "loki.pid"
|
|
2929
|
+
process_stopped = False
|
|
2881
2930
|
if pid_file.exists():
|
|
2882
2931
|
try:
|
|
2883
2932
|
pid = int(pid_file.read_text().strip())
|
|
2884
2933
|
os.kill(pid, 15) # SIGTERM
|
|
2934
|
+
# Wait up to 5s for graceful shutdown
|
|
2935
|
+
for _ in range(10):
|
|
2936
|
+
await asyncio.sleep(0.5)
|
|
2937
|
+
try:
|
|
2938
|
+
os.kill(pid, 0) # Check if still alive
|
|
2939
|
+
except OSError:
|
|
2940
|
+
process_stopped = True
|
|
2941
|
+
break
|
|
2942
|
+
if not process_stopped:
|
|
2943
|
+
# Process didn't exit gracefully, send SIGKILL
|
|
2944
|
+
try:
|
|
2945
|
+
os.kill(pid, 9)
|
|
2946
|
+
process_stopped = True
|
|
2947
|
+
except (OSError, ProcessLookupError):
|
|
2948
|
+
process_stopped = True
|
|
2885
2949
|
except (ValueError, OSError, ProcessLookupError):
|
|
2886
|
-
|
|
2950
|
+
process_stopped = True
|
|
2887
2951
|
|
|
2888
2952
|
# Mark session.json as stopped
|
|
2889
2953
|
session_file = _get_loki_dir() / "session.json"
|
|
@@ -2895,7 +2959,21 @@ async def stop_session(request: Request):
|
|
|
2895
2959
|
except Exception:
|
|
2896
2960
|
pass
|
|
2897
2961
|
|
|
2898
|
-
|
|
2962
|
+
# BUG-NEW-005: Clean up orphaned per-iteration temp files left by killed process
|
|
2963
|
+
logs_dir = _get_loki_dir() / "logs"
|
|
2964
|
+
if logs_dir.exists():
|
|
2965
|
+
import glob as _glob_mod
|
|
2966
|
+
for orphan in _glob_mod.glob(str(logs_dir / "iter-output-*")):
|
|
2967
|
+
try:
|
|
2968
|
+
os.unlink(orphan)
|
|
2969
|
+
except OSError:
|
|
2970
|
+
pass
|
|
2971
|
+
|
|
2972
|
+
return {
|
|
2973
|
+
"success": True,
|
|
2974
|
+
"message": "Session stopped" if process_stopped else "Stop signal sent",
|
|
2975
|
+
"process_stopped": process_stopped,
|
|
2976
|
+
}
|
|
2899
2977
|
|
|
2900
2978
|
|
|
2901
2979
|
# =============================================================================
|
|
@@ -4956,7 +5034,7 @@ async def run_quality_scan(preset: str = Query("default")):
|
|
|
4956
5034
|
|
|
4957
5035
|
|
|
4958
5036
|
@app.get("/api/quality-report")
|
|
4959
|
-
def get_quality_report(fmt: str = Query("json", alias="format")):
|
|
5037
|
+
def get_quality_report(fmt: str = Query("json", alias="format", pattern="^(json|markdown|html)$")):
|
|
4960
5038
|
"""Get an exportable quality audit report."""
|
|
4961
5039
|
if not _read_limiter.check("quality-report"):
|
|
4962
5040
|
raise HTTPException(status_code=429, detail="Rate limit exceeded")
|
|
@@ -5034,6 +5112,10 @@ def start_migration(request_body: dict):
|
|
|
5034
5112
|
options = request_body.get("options", {})
|
|
5035
5113
|
if not codebase_path or not target:
|
|
5036
5114
|
raise HTTPException(status_code=400, detail="codebase_path and target are required")
|
|
5115
|
+
if not isinstance(codebase_path, str) or not isinstance(target, str):
|
|
5116
|
+
raise HTTPException(status_code=400, detail="codebase_path and target must be strings")
|
|
5117
|
+
if len(target) > 255:
|
|
5118
|
+
raise HTTPException(status_code=400, detail="target must be 255 characters or fewer")
|
|
5037
5119
|
# Check raw input for traversal BEFORE resolving
|
|
5038
5120
|
if '..' in codebase_path:
|
|
5039
5121
|
raise HTTPException(status_code=400, detail="Path traversal not allowed")
|
package/docs/INSTALLATION.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
The flagship product of [Autonomi](https://www.autonomi.dev/). Complete installation instructions for all platforms and use cases.
|
|
4
4
|
|
|
5
|
-
**Version:** v6.
|
|
5
|
+
**Version:** v6.72.0
|
|
6
6
|
|
|
7
7
|
---
|
|
8
8
|
|
|
@@ -476,7 +476,7 @@ loki-mode/
|
|
|
476
476
|
│ └── INSTALLATION.md # This file
|
|
477
477
|
├── CHANGELOG.md # Version history
|
|
478
478
|
├── VERSION # Current version number
|
|
479
|
-
├── LICENSE #
|
|
479
|
+
├── LICENSE # Business Source License 1.1
|
|
480
480
|
├── references/ # Agent and deployment references
|
|
481
481
|
│ ├── agents.md
|
|
482
482
|
│ ├── deployment.md
|
|
@@ -484,11 +484,11 @@ loki-mode/
|
|
|
484
484
|
├── autonomy/ # Autonomous runner (CLI only)
|
|
485
485
|
│ ├── run.sh
|
|
486
486
|
│ └── README.md
|
|
487
|
-
├──
|
|
487
|
+
├── templates/ # 22 PRD templates for project scaffolding
|
|
488
488
|
│ ├── simple-todo-app.md
|
|
489
489
|
│ ├── api-only.md
|
|
490
490
|
│ ├── static-landing-page.md
|
|
491
|
-
│ └──
|
|
491
|
+
│ └── ... (22 templates total)
|
|
492
492
|
├── tests/ # Test suite (CLI only)
|
|
493
493
|
│ ├── run-all-tests.sh
|
|
494
494
|
│ ├── test-bootstrap.sh
|
|
@@ -497,7 +497,7 @@ loki-mode/
|
|
|
497
497
|
└── vibe-kanban.md
|
|
498
498
|
```
|
|
499
499
|
|
|
500
|
-
**Note:** Some files/directories (autonomy, tests,
|
|
500
|
+
**Note:** Some files/directories (autonomy, tests, templates) are only available with full installation (Options A or B).
|
|
501
501
|
|
|
502
502
|
---
|
|
503
503
|
|
|
@@ -795,16 +795,25 @@ rm -rf ~/.claude/skills/loki-mode
|
|
|
795
795
|
|
|
796
796
|
After installation:
|
|
797
797
|
|
|
798
|
-
1. **
|
|
798
|
+
1. **Verify Setup:** Check your environment is ready
|
|
799
799
|
```bash
|
|
800
|
-
|
|
800
|
+
loki doctor
|
|
801
801
|
```
|
|
802
802
|
|
|
803
|
-
2. **
|
|
803
|
+
2. **Scaffold a Project:** Create a project from a template
|
|
804
|
+
```bash
|
|
805
|
+
loki init my-app --template simple-todo-app
|
|
806
|
+
cd my-app
|
|
807
|
+
```
|
|
808
|
+
|
|
809
|
+
3. **Start Building:** Launch autonomous development
|
|
810
|
+
```bash
|
|
811
|
+
loki start prd.md
|
|
812
|
+
```
|
|
804
813
|
|
|
805
|
-
|
|
814
|
+
4. **Read Documentation:** Check out [README.md](../README.md) for usage guides
|
|
806
815
|
|
|
807
|
-
|
|
816
|
+
5. **Join the Community:** Report issues or contribute at [GitHub](https://github.com/asklokesh/loki-mode)
|
|
808
817
|
|
|
809
818
|
---
|
|
810
819
|
|
|
@@ -812,7 +821,7 @@ After installation:
|
|
|
812
821
|
|
|
813
822
|
- **Issues/Bugs:** [GitHub Issues](https://github.com/asklokesh/loki-mode/issues)
|
|
814
823
|
- **Discussions:** [GitHub Discussions](https://github.com/asklokesh/loki-mode/discussions)
|
|
815
|
-
- **Documentation:** [README.md](README.md)
|
|
824
|
+
- **Documentation:** [README.md](../README.md)
|
|
816
825
|
|
|
817
826
|
---
|
|
818
827
|
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
# Agent 01 - CLI Functional Testing Bug Fixes
|
|
2
|
+
|
|
3
|
+
File modified: `autonomy/loki`
|
|
4
|
+
Total changes: 166 insertions, 39 deletions
|
|
5
|
+
|
|
6
|
+
## Known Bugs Fixed
|
|
7
|
+
|
|
8
|
+
### BUG-CLI-003 | PID recycling guard for `cmd_web_stop`
|
|
9
|
+
- **Location**: `cmd_web_stop()` PID file kill block
|
|
10
|
+
- **Fix**: Before killing a PID from the PID file, verify the process command matches `python`, `uvicorn`, or `Purple` via `ps -p PID -o comm=`. If the PID has been recycled by the OS to a non-Purple-Lab process, skip the kill and warn the user.
|
|
11
|
+
|
|
12
|
+
### BUG-CLI-012 | Shell injection via unquoted Python file paths
|
|
13
|
+
- **Location**: Multiple `python3 -c` calls throughout the CLI
|
|
14
|
+
- **Fix**: Replaced all `open('$variable')` patterns in `python3 -c` calls with environment-variable-based parameter passing (`_VAR="$value" python3 -c "import os; open(os.environ['_VAR'])"`). Fixed 14 instances across:
|
|
15
|
+
- `cmd_status` context window (lines ~1741-1742)
|
|
16
|
+
- Healing report (friction map, failure modes)
|
|
17
|
+
- Trigger schedule count
|
|
18
|
+
- Failover chain providers
|
|
19
|
+
- Vector index stats (numpy)
|
|
20
|
+
- Context show (5 token metrics)
|
|
21
|
+
- Budget cost display (2 metrics)
|
|
22
|
+
- OTEL config endpoint
|
|
23
|
+
- Project description JSON output
|
|
24
|
+
|
|
25
|
+
### BUG-CLI-005 | `cmd_web_status` wrong port and log paths
|
|
26
|
+
- **Location**: `cmd_web_status()`
|
|
27
|
+
- **Fix**: Port lookup now checks `$PURPLE_LAB_STATE_DIR/port` (home-based) first before CWD-based fallback. Log path resolves against home-based state dir first, falling back to CWD-based path.
|
|
28
|
+
|
|
29
|
+
### BUG-CLI-006 | `loki logs` truncates to 50 lines silently
|
|
30
|
+
- **Location**: `cmd_logs()`
|
|
31
|
+
- **Fix**: Added full argument parsing with `--tail/-n`, `--follow/-f`, `--all/-a`, and `--help`. The 50-line default is now documented in a hint line shown with output. Added `--follow` for live streaming.
|
|
32
|
+
|
|
33
|
+
### BUG-CLI-007 | `loki init` skips directory creation on failure
|
|
34
|
+
- **Location**: `cmd_init()` project directory creation
|
|
35
|
+
- **Fix**: Added `if ! mkdir -p` guards for both the project directory and `.loki` directory creation. Script now exits with an error message if directory creation fails.
|
|
36
|
+
|
|
37
|
+
### BUG-CLI-008 | `loki export` overwrites without confirmation
|
|
38
|
+
- **Location**: `_export_json()`, `_export_markdown()`, `_export_csv()`, `_export_timeline()`
|
|
39
|
+
- **Fix**: Added `_export_check_overwrite()` helper that checks if the output file exists and prompts `Overwrite? [y/N]` before writing. Called in all four export format functions.
|
|
40
|
+
|
|
41
|
+
### BUG-CLI-009 | `loki share` generates non-unique IDs
|
|
42
|
+
- **Location**: `cmd_share()` gist description
|
|
43
|
+
- **Fix**: Changed gist description timestamp from `%Y-%m-%d` (day granularity) to `%Y-%m-%dT%H:%M:%S` (second granularity) to avoid collisions when sharing multiple times per day.
|
|
44
|
+
|
|
45
|
+
### BUG-CLI-010 | `loki config set` accepts any key without validation
|
|
46
|
+
- **Location**: `cmd_config_set()` wildcard case
|
|
47
|
+
- **Fix**: Changed from a warning that stores the value anyway to a hard error that lists valid keys and returns 1. Unknown keys are now rejected.
|
|
48
|
+
|
|
49
|
+
### BUG-CLI-011 | `config_get` error handling
|
|
50
|
+
- **Location**: `cmd_config_get()`
|
|
51
|
+
- **Fix**: Wrapped the Python heredoc in a subshell capture with `|| { error; return 1; }` pattern to gracefully handle python3 failures without leaking state. Removed unnecessary `unset` of inline env vars.
|
|
52
|
+
|
|
53
|
+
### BUG-CLI-008 (Medium) | `LOKI_DIR` not exported for Python heredocs
|
|
54
|
+
- **Location**: Line 129
|
|
55
|
+
- **Fix**: Added `export LOKI_DIR` immediately after assignment. This ensures all Python heredocs using `os.environ.get("LOKI_DIR", ".loki")` receive the correct value.
|
|
56
|
+
|
|
57
|
+
### BUG-PAR-002 | `worktree list` branch pattern never matches
|
|
58
|
+
- **Location**: `cmd_worktree()` list subcommand
|
|
59
|
+
- **Fix**: Branch matching pattern changed from `loki-parallel-*` to match both `parallel-*` and `loki-parallel-*`. The actual branches created by `run.sh` use `parallel-<stream>` prefix (line 2130 of run.sh), not `loki-parallel-`.
|
|
60
|
+
|
|
61
|
+
### BUG-PAR-010 | `worktree clean` doesn't kill running sessions
|
|
62
|
+
- **Location**: `cmd_worktree()` clean subcommand
|
|
63
|
+
- **Fix**: Before removing a worktree via `git worktree remove`, check for `.loki/loki.pid` in the worktree directory. If a session is running, send SIGTERM, wait 1 second, then SIGKILL if needed.
|
|
64
|
+
|
|
65
|
+
## New Bugs Found and Fixed
|
|
66
|
+
|
|
67
|
+
### NEW-CLI-001 | Division by zero in context percentage calculation
|
|
68
|
+
- **Location**: `cmd_status()` and `_context_show()`
|
|
69
|
+
- **Fix**: Added `if [ "$ctx_total" -gt 0 ]` / `if [ "$total_tokens" -gt 0 ]` guards before the `$((used * 100 / total))` arithmetic. If total is zero, percentage defaults to 0.
|
|
70
|
+
|
|
71
|
+
### NEW-CLI-002 | Missing `loki web logs` subcommand
|
|
72
|
+
- **Location**: `cmd_web()` case dispatch
|
|
73
|
+
- **Fix**: Added `logs)` case to `cmd_web()` that displays Purple Lab server logs using `tail -n`. Defaults to 100 lines. Also checks home-based state dir and CWD-based fallback for log file location. Updated `cmd_web_help()` to list the `logs` subcommand.
|
|
74
|
+
|
|
75
|
+
## Bugs Investigated But Not Fixed (Already Correct)
|
|
76
|
+
|
|
77
|
+
### BUG-CLI-001 / BUG-CLI-002 | `--port` / `--prd` unbound variable
|
|
78
|
+
- **Status**: Already fixed in current code. `cmd_web_start()` initializes `port="${PURPLE_LAB_DEFAULT_PORT}"` and `prd_file=""` before the argument parsing loop. The `--port` and `--prd` handlers properly check `${2:-}` before accessing `$2`.
|
|
79
|
+
|
|
80
|
+
### BUG-CLI-004 | `--no-open` flag ignored
|
|
81
|
+
- **Status**: Already working correctly. The `open_browser` variable is set to `false` by `--no-open` and checked with `if [ "$open_browser" = true ]` before any browser-open calls.
|
|
82
|
+
|
|
83
|
+
### BUG-CLI-009 (Medium) | Empty array with `set -u` in `list_running_sessions`
|
|
84
|
+
- **Status**: Already fixed. Uses `${sessions[@]+"${sessions[@]}"}` pattern throughout, which is the correct `set -u`-safe array expansion.
|
|
85
|
+
|
|
86
|
+
### BUG-CMD-001 | `cmd_web` wildcard duplicates arguments
|
|
87
|
+
- **Status**: Analyzed and found to be working correctly. The `*)` case properly shifts the subcommand-as-flag, then passes it along with remaining args to `cmd_web_start`.
|
|
88
|
+
|
|
89
|
+
## Bugs Found But Not Fixed (Out of Scope or Extensive)
|
|
90
|
+
|
|
91
|
+
### Missing `$2` guards in `shift 2` patterns
|
|
92
|
+
- **Location**: Multiple argument parsers (e.g., `cmd_state query`, trigger config parsing)
|
|
93
|
+
- **Issue**: Under `set -u`, if a flag like `--agent` is the last argument without a value, `$2` is unset, causing a crash. The pattern `shift 2 ;;` should be guarded with `${2:-}` checks.
|
|
94
|
+
- **Impact**: Low -- only triggers on malformed user input.
|
|
95
|
+
- **Scope**: 20+ occurrences across the file; would require systematic refactoring.
|
|
96
|
+
|
|
97
|
+
### Remaining shell-expanded heredocs with `$variable` in `open()` calls
|
|
98
|
+
- **Location**: Lines 3745, 8002, 9332, 10228, 10262, 12604, 16844, etc.
|
|
99
|
+
- **Issue**: Double-quoted heredocs (`<< HEREDOC`) interpolate shell variables into Python `open()` calls. While the variables are internally-constructed paths (not user input), specially-crafted directory names with quotes could theoretically cause injection.
|
|
100
|
+
- **Impact**: Very low -- would require the user to be working in a directory with Python metacharacters in its path.
|
|
101
|
+
- **Scope**: 15+ occurrences; fixing all requires converting to single-quoted heredocs with env var passing.
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# Agent 02: Purple Lab Functional Testing - Bug Fix Report
|
|
2
|
+
|
|
3
|
+
## Summary
|
|
4
|
+
|
|
5
|
+
Audited `web-app/server.py` (5,679 lines) and all frontend components in `web-app/src/` (50 files).
|
|
6
|
+
Fixed 8 bugs (3 security, 2 resource leaks, 2 race conditions, 1 missing validation).
|
|
7
|
+
Verified all 13 bugs from BUG-AUDIT-v6.61.0 against the current codebase.
|
|
8
|
+
|
|
9
|
+
## Bugs Fixed
|
|
10
|
+
|
|
11
|
+
### FIX-1: `_save_secrets()` crashes when crypto module is missing (BUG-PL-013 related)
|
|
12
|
+
- **File**: `web-app/server.py:2431`
|
|
13
|
+
- **Severity**: Medium
|
|
14
|
+
- **Problem**: `_save_secrets()` calls `from crypto import encrypt_value, encryption_available` without try/except ImportError. When the crypto module is not installed, saving any secret (POST /api/secrets) crashes with an unhandled ImportError. The counterpart `_load_secrets()` already handles this correctly.
|
|
15
|
+
- **Fix**: Wrapped the crypto import in try/except ImportError with plaintext fallback, matching the pattern in `_load_secrets()`.
|
|
16
|
+
|
|
17
|
+
### FIX-2: `fix_session` orphans child processes (BUG-PL-007 related)
|
|
18
|
+
- **File**: `web-app/server.py:4203`
|
|
19
|
+
- **Severity**: High
|
|
20
|
+
- **Problem**: `run_fix()` in the `/api/sessions/{id}/fix` endpoint spawns a subprocess but never calls `_track_child_pid()` or `_untrack_child_pid()`. When `loki web stop` runs, these fix processes are not found in the tracking list and remain as orphans, consuming resources. The chat endpoint (`run_chat()`) correctly tracks and untracks -- this was a missed pattern.
|
|
21
|
+
- **Fix**: Added `_track_child_pid(proc.pid)` after process creation and `_untrack_child_pid(proc.pid)` in the finally block.
|
|
22
|
+
|
|
23
|
+
### FIX-3: Session history leaks full filesystem paths (BUG-PL-003 from task description)
|
|
24
|
+
- **File**: `web-app/server.py:3411`
|
|
25
|
+
- **Severity**: Medium (security/information disclosure)
|
|
26
|
+
- **Problem**: `get_sessions_history()` returned `"path": str(entry)` which exposes full paths like `/Users/lokesh/purple-lab-projects/project-123`. In deployed scenarios this leaks the server's filesystem structure, username, and directory layout.
|
|
27
|
+
- **Fix**: Replaced with sanitized relative paths using `~/` prefix (e.g., `~/purple-lab-projects/project-123`). This is safe because the session history path is display-only on the frontend.
|
|
28
|
+
|
|
29
|
+
### FIX-4: `delete_session` leaks filesystem path in response
|
|
30
|
+
- **File**: `web-app/server.py:3495`
|
|
31
|
+
- **Severity**: Low (security/information disclosure)
|
|
32
|
+
- **Problem**: `delete_session()` returned `"path": str(target)` in its response, exposing the full filesystem path to the client.
|
|
33
|
+
- **Fix**: Changed to return `"session_id": session_id` instead.
|
|
34
|
+
|
|
35
|
+
### FIX-5: `start_session` accepts arbitrary projectDir without validation
|
|
36
|
+
- **File**: `web-app/server.py:2510`
|
|
37
|
+
- **Severity**: High (security)
|
|
38
|
+
- **Problem**: The `projectDir` field from StartRequest was used directly without any path validation. A malicious client could pass `/etc`, `/root`, or any system path, and the server would create directories and write PRD files there. The `onboard_session` endpoint already validates paths correctly.
|
|
39
|
+
- **Fix**: Added validation ensuring user-supplied `projectDir` resolves to within the home directory, matching the pattern in `onboard_session`.
|
|
40
|
+
|
|
41
|
+
### FIX-6: `create_session_file` missing content size limit (BUG-PL-005 from task description)
|
|
42
|
+
- **File**: `web-app/server.py:3691`
|
|
43
|
+
- **Severity**: Medium
|
|
44
|
+
- **Problem**: `create_session_file()` (POST) accepts file content without any size validation. The sibling `save_session_file()` (PUT) correctly enforces a 1MB limit. A client could create arbitrarily large files via the POST endpoint.
|
|
45
|
+
- **Fix**: Added `len(req.content.encode(...)) > 1_048_576` check matching the PUT endpoint.
|
|
46
|
+
|
|
47
|
+
### FIX-7: `pause_session` and `resume_session` mutate state without lock
|
|
48
|
+
- **File**: `web-app/server.py:2972, 2989`
|
|
49
|
+
- **Severity**: Medium (race condition)
|
|
50
|
+
- **Problem**: Both `pause_session()` and `resume_session()` read `session.running`, `session.process`, and write `session.paused` without holding `session._lock`. Concurrent pause/resume and stop operations could produce torn state. The `stop_session` and `start_session` endpoints correctly use `async with session._lock`.
|
|
51
|
+
- **Fix**: Wrapped both endpoints' state access in `async with session._lock`.
|
|
52
|
+
|
|
53
|
+
### FIX-8: `delete_secret` endpoint missing key format validation
|
|
54
|
+
- **File**: `web-app/server.py:4340`
|
|
55
|
+
- **Severity**: Low (defense in depth)
|
|
56
|
+
- **Problem**: The `delete_secret()` endpoint accepts arbitrary strings as the `key` parameter without validation. While not exploitable (used only as dict key lookup), the `set_secret()` endpoint validates key format -- the inconsistency could mask bugs.
|
|
57
|
+
- **Fix**: Added `re.match(r'^[A-Za-z_][A-Za-z0-9_]*$', key)` validation matching `set_secret()`.
|
|
58
|
+
|
|
59
|
+
## Bugs from Audit Already Fixed in Current Codebase
|
|
60
|
+
|
|
61
|
+
The following bugs from BUG-AUDIT-v6.61.0 were already fixed before this audit:
|
|
62
|
+
|
|
63
|
+
| Bug ID | Description | Status |
|
|
64
|
+
|--------|-------------|--------|
|
|
65
|
+
| BUG-PL-001 | Dead code after `stop_session` return | Fixed: no early return, full cleanup runs |
|
|
66
|
+
| BUG-PL-002 | `session.reset()` never called on stop | Fixed: `session.reset()` called at line 2742 |
|
|
67
|
+
| BUG-PL-003 (audit) | Reader task sets `running=False` without lock | Fixed: uses `async with session._lock` at line 2361 |
|
|
68
|
+
| BUG-PL-004 | Chat/fix missing secrets injection | Fixed: both endpoints call `_load_secrets()` |
|
|
69
|
+
| BUG-PL-005 (audit) | Pause state never tracked | Fixed: `session.paused` set in both pause/resume |
|
|
70
|
+
| BUG-PL-006 | `delete_session` can delete active session | Fixed: explicit active session check at line 3458 |
|
|
71
|
+
| BUG-PL-009 | Project dir name collision (second-granularity) | Fixed: uses milliseconds (`time.time() * 1000`) |
|
|
72
|
+
| BUG-PL-010 | `get_status` mutates `running` without lock | Fixed: uses local `is_running` variable, not session state |
|
|
73
|
+
| BUG-PL-011 | Session history breaks after first non-empty dir | Fixed: no early `break` in history loop |
|
|
74
|
+
| BUG-PL-012 | `cancel_chat` unhandled `TimeoutExpired` | Fixed: caught at line 4133 |
|
|
75
|
+
|
|
76
|
+
## New Bugs Discovered (Beyond Audit)
|
|
77
|
+
|
|
78
|
+
All bugs listed in the "Bugs Fixed" section above (FIX-1 through FIX-8) are new discoveries not covered by the original BUG-AUDIT-v6.61.0 for the Purple Lab category. FIX-1, FIX-2, FIX-5, FIX-6, FIX-7, and FIX-8 are entirely new findings.
|
|
79
|
+
|
|
80
|
+
## Verification
|
|
81
|
+
|
|
82
|
+
- Python syntax: `ast.parse()` passes on modified server.py
|
|
83
|
+
- Frontend build: `npm run build` succeeds with no new errors
|
|
84
|
+
- All changes are backward-compatible (no API contract changes except delete_session response field rename from `path` to `session_id`)
|
|
85
|
+
|
|
86
|
+
## Files Modified
|
|
87
|
+
|
|
88
|
+
- `web-app/server.py` -- 8 bug fixes (50 insertions, 26 deletions)
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
# Agent 03: Dashboard API Functional Testing - Bug Fix Report
|
|
2
|
+
|
|
3
|
+
## Scope
|
|
4
|
+
File: `dashboard/server.py` (~5,259 lines, FastAPI)
|
|
5
|
+
Focus: All API routes, WebSocket handlers, task board features, security audit
|
|
6
|
+
|
|
7
|
+
## Bug Audit Status from BUG-AUDIT-v6.61.0.md
|
|
8
|
+
|
|
9
|
+
### Already Fixed (verified in current codebase)
|
|
10
|
+
|
|
11
|
+
| Bug ID | Description | Status |
|
|
12
|
+
|--------|-------------|--------|
|
|
13
|
+
| BUG-DASH-001 | Token creation endpoint has no authentication | FIXED - `require_scope("admin")` dependency at line 1719 |
|
|
14
|
+
| BUG-DASH-002 | WebSocket rate-limit calls close() on unaccepted connection | FIXED - `accept()` then `close()` at lines 1396-1397 |
|
|
15
|
+
| BUG-DASH-003 | WebSocket connection limit rejection enters receive loop | FIXED - `connect()` returns False, caller returns at line 1421-1422 |
|
|
16
|
+
| BUG-DASH-004 | `create_project` doesn't validate tenant_id | FIXED - Tenant existence check + `Field(..., gt=0)` validation |
|
|
17
|
+
| BUG-DASH-005 | `update_task` doesn't clear completed_at on reopen | FIXED - `completed_at = None` in else branch at line 1254 |
|
|
18
|
+
| BUG-DASH-006 | Task state machine missing DONE transitions | FIXED - `DONE: {IN_PROGRESS, REVIEW}` at line 1316 |
|
|
19
|
+
| BUG-DASH-009 | ProjectUpdate.status allows arbitrary strings | FIXED - `Literal["active", "archived", "completed", "paused"]` at line 179 |
|
|
20
|
+
| BUG-DASH-010 | Audit log offset allows negative values | FIXED - `ge=0` constraint at line 1824 |
|
|
21
|
+
| BUG-DASH-011 | Learning signals offset allows negative values | FIXED - `ge=0` constraint at line 2443 |
|
|
22
|
+
| BUG-DASH-012 | WebSocket idle timeout doesn't call disconnect | FIXED - `finally: manager.disconnect(websocket)` at line 1469 |
|
|
23
|
+
| BUG-DASH-013 | GET /api/tasks ignores project_id parameter | FIXED - Filter applied at lines 1132-1134 |
|
|
24
|
+
|
|
25
|
+
### Not Applicable (code removed)
|
|
26
|
+
|
|
27
|
+
| Bug ID | Description | Status |
|
|
28
|
+
|--------|-------------|--------|
|
|
29
|
+
| BUG-PL-001 | Dead code after stop_session return | N/A - Purple Lab code removed from server.py |
|
|
30
|
+
| BUG-PL-002 | session.reset() never called on stop | N/A - Purple Lab code removed from server.py |
|
|
31
|
+
| BUG-PL-003 | Reader task sets running=False without lock | N/A - Purple Lab code removed |
|
|
32
|
+
| BUG-PL-004 | Chat/fix/auto-fix missing secrets injection | N/A - Purple Lab code removed |
|
|
33
|
+
| BUG-PL-005 | Pause state never tracked | N/A - Purple Lab code removed |
|
|
34
|
+
| BUG-PL-006 | delete_session can delete active directory | N/A - Purple Lab code removed |
|
|
35
|
+
| BUG-PL-007 | Chat PIDs tracked but never untracked | N/A - Purple Lab code removed |
|
|
36
|
+
| BUG-DS-002 | Auto-fix restart double-wraps command | N/A - Dev server code removed |
|
|
37
|
+
| BUG-DS-003 | Overly broad port regex | N/A - Dev server code removed |
|
|
38
|
+
| BUG-DS-004 | pip install into server's own environment | N/A - Dev server code removed |
|
|
39
|
+
| BUG-DS-005 | Docker Compose port parsing crash | N/A - Dev server code removed |
|
|
40
|
+
| BUG-DASH-007 | pause_session polling loop is dead code | N/A - pause_session rewritten (no polling loop) |
|
|
41
|
+
|
|
42
|
+
## Fixes Applied in This Session
|
|
43
|
+
|
|
44
|
+
### 1. BUG-DASH-008: `_safe_read_text` not truly safe (lines 95-101)
|
|
45
|
+
|
|
46
|
+
**Problem**: `_safe_read_text` used `Path.read_text()` directly without exception handling. While `Path.read_text()` properly manages file handles (no leak), the function could raise `OSError` on permission errors or missing files. The "safe" name was misleading -- callers expected it to never raise.
|
|
47
|
+
|
|
48
|
+
**Fix**: Wrapped in try/except to return empty string on any I/O error, making the function truly safe as its name implies.
|
|
49
|
+
|
|
50
|
+
### 2. SECURITY: `/api/focus` POST/DELETE missing authentication (lines 1637, 1672)
|
|
51
|
+
|
|
52
|
+
**Problem**: The `/api/focus` POST and DELETE endpoints had no auth dependency. Any network-reachable client could redirect the dashboard to read from an arbitrary project directory, potentially exposing data from other projects or causing the dashboard to process attacker-controlled state files.
|
|
53
|
+
|
|
54
|
+
**Fix**:
|
|
55
|
+
- Added `dependencies=[Depends(auth.require_scope("control"))]` to both POST and DELETE
|
|
56
|
+
- Added validation requiring the target directory to contain a `.loki/` subdirectory to prevent pointing the dashboard at arbitrary filesystem locations
|
|
57
|
+
- Changed to resolve path before checking, preventing TOCTOU issues
|
|
58
|
+
|
|
59
|
+
### 3. SECURITY: `/api/enterprise/tokens` GET missing authentication (line 1766)
|
|
60
|
+
|
|
61
|
+
**Problem**: The token listing endpoint had no auth dependency. When enterprise mode was enabled, any client could enumerate all API tokens (names, scopes, creation dates, expiration). While raw token values are not returned, the metadata exposure is still a security risk for token enumeration and scope discovery.
|
|
62
|
+
|
|
63
|
+
**Fix**: Added `dependencies=[Depends(auth.require_scope("admin"))]` to require admin authentication.
|
|
64
|
+
|
|
65
|
+
### 4. Deprecated `asyncio.get_event_loop()` in sync_registry (line 1600)
|
|
66
|
+
|
|
67
|
+
**Problem**: Used `asyncio.get_event_loop()` which is deprecated in Python 3.10+ for getting the running loop from within an async function. This could cause `DeprecationWarning` or incorrect behavior in future Python versions.
|
|
68
|
+
|
|
69
|
+
**Fix**: Changed to `asyncio.get_running_loop()` which is the correct API for async contexts.
|
|
70
|
+
|
|
71
|
+
### 5. Input validation: timeRange parameters on learning endpoints (lines 2361, 2430, 2450)
|
|
72
|
+
|
|
73
|
+
**Problem**: The `timeRange` parameters on `/api/learning/metrics`, `/api/learning/trends`, and `/api/learning/signals` accepted arbitrary strings with no validation. While `_parse_time_range()` handles invalid input gracefully (returns None), passing malformed strings wastes processing and could be used for fuzzing.
|
|
74
|
+
|
|
75
|
+
**Fix**: Added `Query(..., pattern=r"^\d{1,4}[hdm]$")` constraint to validate format (1-4 digit number followed by h/d/m).
|
|
76
|
+
|
|
77
|
+
### 6. Input validation: quality report format parameter (line 4970)
|
|
78
|
+
|
|
79
|
+
**Problem**: The `format` parameter on `/api/quality-report` accepted any string, passing it to `rigour.export_report()`. Arbitrary format strings could cause unexpected behavior in the export function.
|
|
80
|
+
|
|
81
|
+
**Fix**: Added `pattern="^(json|markdown|html)$"` constraint to limit to known formats.
|
|
82
|
+
|
|
83
|
+
### 7. Input validation: migration start target and codebase_path (line 5048)
|
|
84
|
+
|
|
85
|
+
**Problem**: The `start_migration` endpoint accepted `codebase_path` and `target` without type checking. Non-string values (lists, dicts) from JSON body would cause cryptic errors downstream.
|
|
86
|
+
|
|
87
|
+
**Fix**: Added type validation (`isinstance` check for str) and length limit (255 chars) for `target`.
|
|
88
|
+
|
|
89
|
+
## Security Audit Summary (OWASP Top 10)
|
|
90
|
+
|
|
91
|
+
| OWASP Category | Status | Notes |
|
|
92
|
+
|----------------|--------|-------|
|
|
93
|
+
| A01: Broken Access Control | FIXED | Added auth to /api/focus, /api/enterprise/tokens |
|
|
94
|
+
| A02: Cryptographic Failures | OK | Token generation uses `secrets` module |
|
|
95
|
+
| A03: Injection | OK | SQLAlchemy ORM prevents SQL injection; list-form subprocess calls prevent shell injection; realpath + regex prevent path traversal |
|
|
96
|
+
| A04: Insecure Design | FIXED | Focus endpoint now requires .loki/ subdirectory |
|
|
97
|
+
| A05: Security Misconfiguration | OK | CORS restricted to localhost; default bind to 127.0.0.1; TLS optional |
|
|
98
|
+
| A06: Vulnerable Components | N/A | Dependency audit out of scope |
|
|
99
|
+
| A07: Auth Failures | OK | Rate limiting on sensitive endpoints; token management requires admin |
|
|
100
|
+
| A08: Data Integrity | OK | Atomic file writes for state mutations (tmp + rename) |
|
|
101
|
+
| A09: Logging Failures | OK | Audit logging on destructive operations (delete, stop, kill) |
|
|
102
|
+
| A10: SSRF | MITIGATED | Focus endpoint restricted to dirs with .loki/ subdirectory |
|
|
103
|
+
|
|
104
|
+
## Route Coverage Audit
|
|
105
|
+
|
|
106
|
+
Verified all 100+ routes in server.py for:
|
|
107
|
+
- Authentication requirements (auth scope dependencies)
|
|
108
|
+
- Input validation (Pydantic models, Query constraints, regex patterns)
|
|
109
|
+
- Error handling (try/except with proper HTTP status codes)
|
|
110
|
+
- Rate limiting (control and read limiters)
|
|
111
|
+
- Path traversal protection (realpath checks, SAFE_ID_RE regex)
|
|
112
|
+
- Resource cleanup (WebSocket disconnect in finally block)
|
|
113
|
+
|
|
114
|
+
## Verification
|
|
115
|
+
|
|
116
|
+
```
|
|
117
|
+
python3 -c "import ast; ast.parse(open('dashboard/server.py').read()); print('Syntax OK')"
|
|
118
|
+
# Output: Syntax OK
|
|
119
|
+
```
|