loki-mode 5.40.0 → 5.41.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/SKILL.md CHANGED
@@ -3,7 +3,7 @@ name: loki-mode
3
3
  description: Multi-agent autonomous startup system. Triggers on "Loki Mode". Takes PRD to deployed product with zero human intervention. Requires --dangerously-skip-permissions flag.
4
4
  ---
5
5
 
6
- # Loki Mode v5.40.0
6
+ # Loki Mode v5.41.0
7
7
 
8
8
  **You are an autonomous agent. You make decisions. You do not ask questions. You do not stop.**
9
9
 
@@ -258,8 +258,8 @@ The following features are documented in skill modules but not yet fully automat
258
258
  |---------|--------|-------|
259
259
  | PRE-ACT goal drift detection | Planned | Agent-level attention check before each action; no automated enforcement yet |
260
260
  | CONTINUITY.md working memory | Implemented (v5.35.0) | Auto-managed by run.sh, updated each iteration |
261
- | GitHub issue import | Planned | Config flags exist (`LOKI_GITHUB_IMPORT`); `gh` CLI integration partial |
261
+ | GitHub integration | Implemented (v5.41.0) | Import, sync-back, PR creation, export. CLI: `loki github`, API: `/api/github/*` |
262
262
  | Quality gates 3-reviewer system | Implemented (v5.35.0) | 5 specialist reviewers in `skills/quality-gates.md`; execution in run.sh |
263
263
  | Benchmarks (HumanEval, SWE-bench) | Infrastructure only | Runner scripts and datasets exist in `benchmarks/`; no published results |
264
264
 
265
- **v5.40.0 | fix: 46-bug audit across all distributions, JSON injection, Docker mounts, auth hardening | ~260 lines core**
265
+ **v5.41.0 | feat: GitHub sync-back, PR creation, export (fully wired) | ~260 lines core**
package/VERSION CHANGED
@@ -1 +1 @@
1
- 5.40.0
1
+ 5.41.0
@@ -10,6 +10,9 @@ CWD=$(echo "$INPUT" | python3 -c "import sys,json; print(json.load(sys.stdin).ge
10
10
 
11
11
  # Dangerous command patterns (matched anywhere in the command string)
12
12
  # Safe paths like /tmp/ and relative paths (./) are excluded below
13
+ # NOTE: This is defense-in-depth, not a security boundary. Motivated attackers
14
+ # can bypass with advanced techniques (heredocs, printf, arbitrary string building).
15
+ # This hook catches common mistakes and simple bypass attempts.
13
16
  BLOCKED_PATTERNS=(
14
17
  "rm -rf /"
15
18
  "rm -rf ~"
@@ -18,6 +21,15 @@ BLOCKED_PATTERNS=(
18
21
  "mkfs[. ]"
19
22
  "dd if=/dev/zero"
20
23
  "chmod -R 777 /"
24
+ "eval.*base64"
25
+ "base64.*\|.*sh"
26
+ "base64.*\|.*bash"
27
+ "\$\(base64"
28
+ "eval.*\\\$\("
29
+ "curl.*\|.*sh"
30
+ "wget.*\|.*sh"
31
+ "curl.*\|.*bash"
32
+ "wget.*\|.*bash"
21
33
  )
22
34
 
23
35
  # Safe path patterns that override rm -rf / matches
package/autonomy/loki CHANGED
@@ -14,6 +14,7 @@
14
14
  # loki status - Show current status
15
15
  # loki dashboard - Open dashboard in browser
16
16
  # loki import - Import GitHub issues
17
+ # loki github [cmd] - GitHub integration (sync|export|pr|status)
17
18
  # loki help - Show this help
18
19
  #===============================================================================
19
20
 
@@ -323,6 +324,7 @@ show_help() {
323
324
  echo " notify [cmd] Send notifications (test|slack|discord|webhook|status)"
324
325
  echo " voice [cmd] Voice input for PRD creation (status|listen|dictate|speak|start)"
325
326
  echo " import Import GitHub issues as tasks"
327
+ echo " github [cmd] GitHub integration (sync|export|pr|status)"
326
328
  echo " config [cmd] Manage configuration (show|init|edit|path)"
327
329
  echo " completions [bash|zsh] Output shell completion scripts"
328
330
  echo " memory [cmd] Cross-project learnings (list|show|search|stats)"
@@ -515,7 +517,7 @@ cmd_start() {
515
517
  if [ -n "$prd_file" ]; then
516
518
  args+=("$prd_file")
517
519
  else
518
- # No PRD file specified -- warn and confirm before consuming API credits
520
+ # No PRD file specified -- warn and confirm before starting
519
521
  # Auto-confirm in CI environments or when LOKI_AUTO_CONFIRM is set
520
522
  # LOKI_AUTO_CONFIRM takes precedence when explicitly set;
521
523
  # fall back to CI env var only when LOKI_AUTO_CONFIRM is unset
@@ -524,10 +526,10 @@ cmd_start() {
524
526
  echo -e "${YELLOW}Warning: No PRD file specified. Auto-confirming (CI mode).${NC}"
525
527
  else
526
528
  echo -e "${YELLOW}Warning: No PRD file specified.${NC}"
527
- echo "Loki Mode will start autonomous execution in the current directory"
528
- echo "without a requirements document."
529
+ echo "Loki Mode will analyze the existing codebase and generate"
530
+ echo "a PRD automatically. No requirements document needed."
529
531
  echo ""
530
- echo -e "This will consume API credits. Continue? [y/N] \c"
532
+ echo -e "Continue? [y/N] \c"
531
533
  read -r confirm
532
534
  if [[ ! "$confirm" =~ ^[Yy] ]]; then
533
535
  echo "Aborted. Usage: loki start <path-to-prd.md>"
@@ -1832,6 +1834,183 @@ cmd_import() {
1832
1834
  fi
1833
1835
  }
1834
1836
 
1837
+ # GitHub integration management (v5.41.0)
1838
+ cmd_github() {
1839
+ local subcmd="${1:-help}"
1840
+ shift 2>/dev/null || true
1841
+
1842
+ case "$subcmd" in
1843
+ sync)
1844
+ # Sync completed tasks back to GitHub issues
1845
+ if [ ! -d "$LOKI_DIR" ]; then
1846
+ echo -e "${RED}No active Loki session found${NC}"
1847
+ exit 1
1848
+ fi
1849
+
1850
+ if ! command -v gh &>/dev/null; then
1851
+ echo -e "${RED}Error: gh CLI not found. Install with: brew install gh${NC}"
1852
+ exit 1
1853
+ fi
1854
+
1855
+ if ! gh auth status &>/dev/null; then
1856
+ echo -e "${RED}Error: gh CLI not authenticated. Run: gh auth login${NC}"
1857
+ exit 1
1858
+ fi
1859
+
1860
+ export LOKI_GITHUB_SYNC=true
1861
+ source "$RUN_SH" 2>/dev/null || true
1862
+
1863
+ echo -e "${GREEN}Syncing completed tasks to GitHub...${NC}"
1864
+ if type sync_github_completed_tasks &>/dev/null; then
1865
+ sync_github_completed_tasks
1866
+ echo -e "${GREEN}Sync complete.${NC}"
1867
+
1868
+ # Show what was synced
1869
+ if [ -f "$LOKI_DIR/github/synced.log" ]; then
1870
+ local count
1871
+ count=$(wc -l < "$LOKI_DIR/github/synced.log" | tr -d ' ')
1872
+ echo -e "${DIM}Total synced status updates: $count${NC}"
1873
+ fi
1874
+ else
1875
+ echo -e "${YELLOW}Sync function not available.${NC}"
1876
+ fi
1877
+ ;;
1878
+
1879
+ export)
1880
+ # Export local tasks as GitHub issues
1881
+ if [ ! -d "$LOKI_DIR" ]; then
1882
+ echo -e "${RED}No active Loki session found${NC}"
1883
+ exit 1
1884
+ fi
1885
+
1886
+ if ! command -v gh &>/dev/null; then
1887
+ echo -e "${RED}Error: gh CLI not found. Install with: brew install gh${NC}"
1888
+ exit 1
1889
+ fi
1890
+
1891
+ echo -e "${GREEN}Exporting local tasks to GitHub issues...${NC}"
1892
+ source "$RUN_SH" 2>/dev/null || true
1893
+ if type export_tasks_to_github &>/dev/null; then
1894
+ export_tasks_to_github
1895
+ echo -e "${GREEN}Export complete.${NC}"
1896
+ else
1897
+ echo -e "${YELLOW}Export function not available.${NC}"
1898
+ fi
1899
+ ;;
1900
+
1901
+ pr)
1902
+ # Create PR from completed work
1903
+ if ! command -v gh &>/dev/null; then
1904
+ echo -e "${RED}Error: gh CLI not found. Install with: brew install gh${NC}"
1905
+ exit 1
1906
+ fi
1907
+
1908
+ local feature_name="${1:-Loki Mode changes}"
1909
+ export LOKI_GITHUB_PR=true
1910
+ source "$RUN_SH" 2>/dev/null || true
1911
+
1912
+ echo -e "${GREEN}Creating pull request: $feature_name${NC}"
1913
+ if type create_github_pr &>/dev/null; then
1914
+ create_github_pr "$feature_name"
1915
+ else
1916
+ echo -e "${YELLOW}PR function not available.${NC}"
1917
+ fi
1918
+ ;;
1919
+
1920
+ status)
1921
+ # Show GitHub integration status
1922
+ echo -e "${BOLD}GitHub Integration Status${NC}"
1923
+ echo ""
1924
+
1925
+ # gh CLI
1926
+ if command -v gh &>/dev/null; then
1927
+ echo -e " gh CLI: ${GREEN}installed$(gh --version 2>/dev/null | head -1 | sed 's/gh version /v/')${NC}"
1928
+ if gh auth status &>/dev/null 2>&1; then
1929
+ echo -e " Auth: ${GREEN}authenticated${NC}"
1930
+ else
1931
+ echo -e " Auth: ${RED}not authenticated${NC}"
1932
+ fi
1933
+ else
1934
+ echo -e " gh CLI: ${RED}not installed${NC}"
1935
+ fi
1936
+
1937
+ # Repo detection
1938
+ local repo=""
1939
+ repo=$(git remote get-url origin 2>/dev/null | sed 's|.*github.com[:/]||;s|\.git$||' || echo "")
1940
+ if [ -n "$repo" ]; then
1941
+ echo -e " Repository: ${GREEN}$repo${NC}"
1942
+ else
1943
+ echo -e " Repository: ${YELLOW}not detected${NC}"
1944
+ fi
1945
+
1946
+ # Config flags
1947
+ echo ""
1948
+ echo -e "${BOLD}Configuration${NC}"
1949
+ echo -e " LOKI_GITHUB_IMPORT: ${LOKI_GITHUB_IMPORT:-false}"
1950
+ echo -e " LOKI_GITHUB_SYNC: ${LOKI_GITHUB_SYNC:-false}"
1951
+ echo -e " LOKI_GITHUB_PR: ${LOKI_GITHUB_PR:-false}"
1952
+ echo -e " LOKI_GITHUB_LABELS: ${LOKI_GITHUB_LABELS:-(all)}"
1953
+ echo -e " LOKI_GITHUB_LIMIT: ${LOKI_GITHUB_LIMIT:-100}"
1954
+
1955
+ # Sync log
1956
+ if [ -f "$LOKI_DIR/github/synced.log" ]; then
1957
+ echo ""
1958
+ echo -e "${BOLD}Sync History${NC}"
1959
+ local total
1960
+ total=$(wc -l < "$LOKI_DIR/github/synced.log" | tr -d ' ')
1961
+ echo -e " Total synced updates: $total"
1962
+ echo -e " Recent:"
1963
+ tail -5 "$LOKI_DIR/github/synced.log" | sed 's/^/ /'
1964
+ fi
1965
+
1966
+ # Imported tasks
1967
+ if [ -f "$LOKI_DIR/queue/pending.json" ]; then
1968
+ local gh_tasks
1969
+ gh_tasks=$(python3 -c "
1970
+ import json
1971
+ try:
1972
+ with open('$LOKI_DIR/queue/pending.json') as f:
1973
+ data = json.load(f)
1974
+ tasks = data.get('tasks', data) if isinstance(data, dict) else data
1975
+ gh = [t for t in tasks if t.get('source') == 'github']
1976
+ print(len(gh))
1977
+ except: print(0)
1978
+ " 2>/dev/null || echo "0")
1979
+ echo ""
1980
+ echo -e "${BOLD}Imported Issues${NC}"
1981
+ echo -e " GitHub tasks in queue: $gh_tasks"
1982
+ fi
1983
+ ;;
1984
+
1985
+ help|*)
1986
+ echo -e "${BOLD}loki github${NC} - GitHub integration management"
1987
+ echo ""
1988
+ echo "Commands:"
1989
+ echo " status Show GitHub integration status"
1990
+ echo " sync Sync completed task status back to GitHub issues"
1991
+ echo " export Export local tasks as new GitHub issues"
1992
+ echo " pr [name] Create pull request from completed work"
1993
+ echo ""
1994
+ echo "Environment Variables:"
1995
+ echo " LOKI_GITHUB_IMPORT=true Import open issues as tasks on start"
1996
+ echo " LOKI_GITHUB_SYNC=true Sync status back to issues during session"
1997
+ echo " LOKI_GITHUB_PR=true Create PR when session completes successfully"
1998
+ echo " LOKI_GITHUB_LABELS=bug Filter issues by label (comma-separated)"
1999
+ echo " LOKI_GITHUB_MILESTONE=v2 Filter by milestone"
2000
+ echo " LOKI_GITHUB_ASSIGNEE=me Filter by assignee"
2001
+ echo " LOKI_GITHUB_LIMIT=50 Max issues to import (default: 100)"
2002
+ echo " LOKI_GITHUB_PR_LABEL=loki Label to add to created PRs"
2003
+ echo ""
2004
+ echo "Examples:"
2005
+ echo " loki github status"
2006
+ echo " loki github sync"
2007
+ echo " loki github export"
2008
+ echo " loki github pr \"Add user authentication\""
2009
+ echo " LOKI_GITHUB_SYNC=true loki start --github ./prd.md"
2010
+ ;;
2011
+ esac
2012
+ }
2013
+
1835
2014
  # Parse GitHub issue using issue-parser.sh
1836
2015
  cmd_issue_parse() {
1837
2016
  local issue_ref=""
@@ -4278,6 +4457,9 @@ main() {
4278
4457
  import)
4279
4458
  cmd_import
4280
4459
  ;;
4460
+ github)
4461
+ cmd_github "$@"
4462
+ ;;
4281
4463
  issue)
4282
4464
  cmd_issue "$@"
4283
4465
  ;;
package/autonomy/run.sh CHANGED
@@ -1273,10 +1273,12 @@ create_github_pr() {
1273
1273
  local pr_body=".loki/reports/pr-body.md"
1274
1274
  mkdir -p "$(dirname "$pr_body")"
1275
1275
 
1276
+ local version
1277
+ version=$(cat "${SCRIPT_DIR%/*}/VERSION" 2>/dev/null || echo "unknown")
1276
1278
  cat > "$pr_body" << EOF
1277
1279
  ## Summary
1278
1280
 
1279
- Automated implementation by Loki Mode v4.1.0
1281
+ Automated implementation by Loki Mode v$version ($ITERATION_COUNT iterations, provider: ${PROVIDER_NAME:-claude})
1280
1282
 
1281
1283
  ### Feature: $feature_name
1282
1284
 
@@ -1349,24 +1351,105 @@ sync_github_status() {
1349
1351
  return 1
1350
1352
  fi
1351
1353
 
1354
+ # Track synced issues to avoid duplicate comments
1355
+ mkdir -p .loki/github
1356
+ local sync_log=".loki/github/synced.log"
1357
+ local sync_key="${issue_number}:${status}"
1358
+ if [ -f "$sync_log" ] && grep -qF "$sync_key" "$sync_log" 2>/dev/null; then
1359
+ return 0 # Already synced this status
1360
+ fi
1361
+
1352
1362
  case "$status" in
1353
1363
  "in_progress")
1354
1364
  gh issue comment "$issue_number" --repo "$repo" \
1355
- --body "Loki Mode: Task in progress - ${message:-implementing solution...}" \
1365
+ --body "**Loki Mode** -- Working on this issue (iteration $ITERATION_COUNT)" \
1356
1366
  2>/dev/null || true
1357
1367
  ;;
1358
1368
  "completed")
1369
+ local branch
1370
+ branch=$(git rev-parse --abbrev-ref HEAD 2>/dev/null || echo "main")
1371
+ local commit
1372
+ commit=$(git rev-parse --short HEAD 2>/dev/null || echo "unknown")
1359
1373
  gh issue comment "$issue_number" --repo "$repo" \
1360
- --body "Loki Mode: Implementation complete. ${message:-}" \
1374
+ --body "**Loki Mode** -- Implementation complete on \`$branch\` ($commit). ${message:-}" \
1361
1375
  2>/dev/null || true
1362
1376
  ;;
1363
1377
  "closed")
1364
1378
  gh issue close "$issue_number" --repo "$repo" \
1365
1379
  --reason "completed" \
1366
- --comment "Loki Mode: Fixed. ${message:-}" \
1380
+ --comment "**Loki Mode** -- Resolved. ${message:-}" \
1367
1381
  2>/dev/null || true
1368
1382
  ;;
1369
1383
  esac
1384
+
1385
+ # Record sync to avoid duplicates
1386
+ echo "$sync_key" >> "$sync_log"
1387
+ }
1388
+
1389
+ # Sync all completed GitHub-sourced tasks back to their issues
1390
+ # Called after each iteration and at session end
1391
+ sync_github_completed_tasks() {
1392
+ if [ "$GITHUB_SYNC" != "true" ]; then
1393
+ return 0
1394
+ fi
1395
+
1396
+ if ! check_github_cli; then
1397
+ return 0
1398
+ fi
1399
+
1400
+ local completed_file=".loki/queue/completed.json"
1401
+ if [ ! -f "$completed_file" ]; then
1402
+ return 0
1403
+ fi
1404
+
1405
+ # Find GitHub-sourced tasks in completed queue that haven't been synced
1406
+ python3 -c "
1407
+ import json, sys
1408
+ try:
1409
+ with open('$completed_file') as f:
1410
+ tasks = json.load(f)
1411
+ for t in tasks:
1412
+ tid = t.get('id', '')
1413
+ if tid.startswith('github-'):
1414
+ print(tid)
1415
+ except Exception:
1416
+ pass
1417
+ " 2>/dev/null | while read -r task_id; do
1418
+ sync_github_status "$task_id" "completed"
1419
+ done
1420
+ }
1421
+
1422
+ # Sync GitHub-sourced tasks currently in-progress
1423
+ sync_github_in_progress_tasks() {
1424
+ if [ "$GITHUB_SYNC" != "true" ]; then
1425
+ return 0
1426
+ fi
1427
+
1428
+ if ! check_github_cli; then
1429
+ return 0
1430
+ fi
1431
+
1432
+ local pending_file=".loki/queue/pending.json"
1433
+ if [ ! -f "$pending_file" ]; then
1434
+ return 0
1435
+ fi
1436
+
1437
+ # Find GitHub-sourced tasks in pending queue (about to be worked on)
1438
+ python3 -c "
1439
+ import json
1440
+ try:
1441
+ with open('$pending_file') as f:
1442
+ data = json.load(f)
1443
+ tasks = data.get('tasks', data) if isinstance(data, dict) else data
1444
+ for t in tasks:
1445
+ tid = t.get('id', '')
1446
+ if tid.startswith('github-'):
1447
+ print(tid)
1448
+ except Exception:
1449
+ pass
1450
+ " 2>/dev/null | while read -r task_id; do
1451
+ sync_github_status "$task_id" "in_progress"
1452
+ done
1370
1453
  }
1371
1454
 
1372
1455
  # Export tasks to GitHub issues (reverse sync)
@@ -2147,13 +2230,32 @@ init_loki_dir() {
2147
2230
 
2148
2231
  # Clean up stale control files ONLY if no other session is running
2149
2232
  # Deleting these while another session is active would destroy its signals
2150
- local existing_pid=""
2151
- if [ -f ".loki/loki.pid" ]; then
2152
- existing_pid=$(cat ".loki/loki.pid" 2>/dev/null)
2233
+ # Use flock if available to avoid TOCTOU race
2234
+ local lock_file=".loki/session.lock"
2235
+ local can_cleanup=false
2236
+
2237
+ if command -v flock >/dev/null 2>&1 && [ -f "$lock_file" ]; then
2238
+ # Try non-blocking lock - if we get it, no other session is running
2239
+ {
2240
+ if flock -n 201 2>/dev/null; then
2241
+ can_cleanup=true
2242
+ fi
2243
+ } 201>"$lock_file"
2244
+ else
2245
+ # Fallback: check PID file
2246
+ local existing_pid=""
2247
+ if [ -f ".loki/loki.pid" ]; then
2248
+ existing_pid=$(cat ".loki/loki.pid" 2>/dev/null)
2249
+ fi
2250
+ if [ -z "$existing_pid" ] || ! kill -0 "$existing_pid" 2>/dev/null; then
2251
+ can_cleanup=true
2252
+ fi
2153
2253
  fi
2154
- if [ -z "$existing_pid" ] || ! kill -0 "$existing_pid" 2>/dev/null; then
2254
+
2255
+ if [ "$can_cleanup" = "true" ]; then
2155
2256
  rm -f .loki/PAUSE .loki/STOP .loki/HUMAN_INPUT.md 2>/dev/null
2156
2257
  rm -f .loki/loki.pid 2>/dev/null
2258
+ rm -f .loki/session.lock 2>/dev/null
2157
2259
  fi
2158
2260
 
2159
2261
  mkdir -p .loki/{state,queue,messages,logs,config,prompts,artifacts,scripts}
@@ -2785,6 +2887,9 @@ EFF_EOF
2785
2887
  # Check notification triggers (v5.40.0)
2786
2888
  check_notification_triggers "$iteration"
2787
2889
 
2890
+ # Sync completed GitHub tasks back to issues (v5.41.0)
2891
+ sync_github_completed_tasks
2892
+
2788
2893
  # Get task from in-progress
2789
2894
  local in_progress_file=".loki/queue/in-progress.json"
2790
2895
  local completed_file=".loki/queue/completed.json"
@@ -6945,16 +7050,50 @@ main() {
6945
7050
  update_continuity
6946
7051
 
6947
7052
  # Session lock: prevent concurrent sessions on same repo
7053
+ # Use flock for atomic locking to prevent TOCTOU race conditions
6948
7054
  local pid_file=".loki/loki.pid"
6949
- if [ -f "$pid_file" ]; then
6950
- local existing_pid
6951
- existing_pid=$(cat "$pid_file" 2>/dev/null)
6952
- # Skip if it's our own PID or parent PID (background mode writes PID before child starts)
6953
- if [ -n "$existing_pid" ] && [ "$existing_pid" != "$$" ] && [ "$existing_pid" != "$PPID" ] && kill -0 "$existing_pid" 2>/dev/null; then
6954
- log_error "Another Loki session is already running (PID: $existing_pid)"
7055
+ local lock_file=".loki/session.lock"
7056
+
7057
+ # Try to acquire exclusive lock with flock (if available)
7058
+ if command -v flock >/dev/null 2>&1; then
7059
+ # Create lock file
7060
+ touch "$lock_file"
7061
+
7062
+ # Open FD 200 at process scope so flock persists for entire session lifetime
7063
+ # (block-scoped redirection would release the lock when the block exits)
7064
+ exec 200>"$lock_file"
7065
+
7066
+ # Try to acquire exclusive lock (non-blocking)
7067
+ if ! flock -n 200 2>/dev/null; then
7068
+ log_error "Another Loki session is already running (locked)"
6955
7069
  log_error "Stop it first with: loki stop"
6956
7070
  exit 1
6957
7071
  fi
7072
+
7073
+ # Check PID file after acquiring lock
7074
+ if [ -f "$pid_file" ]; then
7075
+ local existing_pid
7076
+ existing_pid=$(cat "$pid_file" 2>/dev/null)
7077
+ # Skip if it's our own PID or parent PID (background mode writes PID before child starts)
7078
+ if [ -n "$existing_pid" ] && [ "$existing_pid" != "$$" ] && [ "$existing_pid" != "$PPID" ] && kill -0 "$existing_pid" 2>/dev/null; then
7079
+ log_error "Another Loki session is already running (PID: $existing_pid)"
7080
+ log_error "Stop it first with: loki stop"
7081
+ exit 1
7082
+ fi
7083
+ fi
7084
+ else
7085
+ # Fallback to original behavior if flock not available
7086
+ log_warn "flock not available - using non-atomic PID check (race condition possible)"
7087
+ if [ -f "$pid_file" ]; then
7088
+ local existing_pid
7089
+ existing_pid=$(cat "$pid_file" 2>/dev/null)
7090
+ # Skip if it's our own PID or parent PID (background mode writes PID before child starts)
7091
+ if [ -n "$existing_pid" ] && [ "$existing_pid" != "$$" ] && [ "$existing_pid" != "$PPID" ] && kill -0 "$existing_pid" 2>/dev/null; then
7092
+ log_error "Another Loki session is already running (PID: $existing_pid)"
7093
+ log_error "Stop it first with: loki stop"
7094
+ exit 1
7095
+ fi
7096
+ fi
6958
7097
  fi
6959
7098
 
6960
7099
  # Write PID file for ALL modes (foreground + background)
@@ -6967,6 +7106,8 @@ main() {
6967
7106
  # Import GitHub issues if enabled (v4.1.0)
6968
7107
  if [ "$GITHUB_IMPORT" = "true" ]; then
6969
7108
  import_github_issues
7109
+ # Notify GitHub that imported issues are being worked on (v5.41.0)
7110
+ sync_github_in_progress_tasks
6970
7111
  fi
6971
7112
 
6972
7113
  # Start web dashboard (if enabled)
@@ -7056,6 +7197,14 @@ main() {
7056
7197
  run_autonomous "$PRD_PATH" || result=$?
7057
7198
  fi
7058
7199
 
7200
+ # Final GitHub sync: sync all completed tasks and create PR (v5.41.0)
7201
+ sync_github_completed_tasks
7202
+ if [ "$GITHUB_PR" = "true" ] && [ "$result" = "0" ]; then
7203
+ local feature_name="${PRD_PATH:-Codebase improvements}"
7204
+ feature_name=$(basename "$feature_name" .md 2>/dev/null || echo "$feature_name")
7205
+ create_github_pr "$feature_name"
7206
+ fi
7207
+
7059
7208
  # Extract and save learnings from this session
7060
7209
  extract_learnings_from_session
7061
7210
 
@@ -7,7 +7,7 @@ Modules:
7
7
  control: Session control API (start/stop/pause/resume)
8
8
  """
9
9
 
10
- __version__ = "5.40.0"
10
+ __version__ = "5.41.0"
11
11
 
12
12
  # Expose the control app for easy import
13
13
  try:
package/dashboard/auth.py CHANGED
@@ -33,6 +33,7 @@ TOKEN_FILE = TOKEN_DIR / "tokens.json"
33
33
  OIDC_ISSUER = os.environ.get("LOKI_OIDC_ISSUER", "") # e.g., https://accounts.google.com
34
34
  OIDC_CLIENT_ID = os.environ.get("LOKI_OIDC_CLIENT_ID", "")
35
35
  OIDC_AUDIENCE = os.environ.get("LOKI_OIDC_AUDIENCE", "") # Usually same as client_id
36
+ OIDC_SKIP_SIGNATURE_VERIFY = os.environ.get("LOKI_OIDC_SKIP_SIGNATURE_VERIFY", "").lower() in ("true", "1", "yes")
36
37
  OIDC_ENABLED = bool(OIDC_ISSUER and OIDC_CLIENT_ID)
37
38
 
38
39
  # Role-to-scope mapping (predefined roles)
@@ -54,11 +55,19 @@ _SCOPE_HIERARCHY = {
54
55
 
55
56
  if OIDC_ENABLED:
56
57
  import logging as _logging
57
- _logging.getLogger("loki.auth").warning(
58
- "OIDC/SSO enabled (EXPERIMENTAL). Claims-based validation only -- "
59
- "JWT signatures are NOT cryptographically verified. Install PyJWT + "
60
- "cryptography for production signature verification."
61
- )
58
+ _logger = _logging.getLogger("loki.auth")
59
+ if OIDC_SKIP_SIGNATURE_VERIFY:
60
+ _logger.critical(
61
+ "OIDC/SSO signature verification DISABLED (LOKI_OIDC_SKIP_SIGNATURE_VERIFY=true). "
62
+ "This is INSECURE and allows forged JWTs. Only use for local testing. "
63
+ "For production, install PyJWT + cryptography and remove this env var."
64
+ )
65
+ else:
66
+ _logger.warning(
67
+ "OIDC/SSO enabled (EXPERIMENTAL). Claims-based validation only -- "
68
+ "JWT signatures are NOT cryptographically verified. Install PyJWT + "
69
+ "cryptography for production signature verification."
70
+ )
62
71
 
63
72
  # OIDC JWKS cache (issuer URL -> (keys_dict, fetch_timestamp))
64
73
  _oidc_jwks_cache = {} # type: dict[str, tuple[dict, float]]
@@ -452,15 +461,18 @@ def validate_oidc_token(token_str: str) -> Optional[dict]:
452
461
 
453
462
  This is a claims-based validation that checks:
454
463
  - Token structure (3 base64url-encoded parts)
464
+ - Signature part is not empty (basic sanity check)
455
465
  - Issuer matches OIDC_ISSUER
456
466
  - Audience matches OIDC_AUDIENCE or OIDC_CLIENT_ID
457
467
  - Token is not expired
458
468
 
459
- NOTE: Full cryptographic signature verification requires an RSA
460
- library (e.g., PyJWT + cryptography). This implementation validates
461
- claims and relies on HTTPS transport security for the token. For
462
- production deployments with untrusted networks, consider adding
463
- PyJWT for full signature verification.
469
+ SECURITY WARNING: Cryptographic signature verification is NOT performed
470
+ unless PyJWT is installed. This implementation only validates claims.
471
+ An attacker can forge JWTs unless LOKI_OIDC_SKIP_SIGNATURE_VERIFY is
472
+ explicitly set to 'true' (which is INSECURE and only for local testing).
473
+
474
+ For production: Install PyJWT + cryptography for proper RSA/HMAC
475
+ signature verification.
464
476
  """
465
477
  if not OIDC_ENABLED:
466
478
  return None
@@ -470,8 +482,29 @@ def validate_oidc_token(token_str: str) -> Optional[dict]:
470
482
  if len(parts) != 3:
471
483
  return None
472
484
 
485
+ # Basic sanity check: signature part must not be empty
486
+ header_b64, payload_b64, signature_b64 = parts
487
+ if not signature_b64 or len(signature_b64) < 10:
488
+ import logging as _logging
489
+ _logging.getLogger("loki.auth").error(
490
+ "OIDC token rejected: signature part is missing or too short"
491
+ )
492
+ return None
493
+
494
+ # CRITICAL: Check if signature verification is explicitly skipped
495
+ if not OIDC_SKIP_SIGNATURE_VERIFY:
496
+ import logging as _logging
497
+ _logging.getLogger("loki.auth").critical(
498
+ "OIDC token received but signature verification is NOT implemented. "
499
+ "Set LOKI_OIDC_SKIP_SIGNATURE_VERIFY=true to explicitly allow "
500
+ "unverified tokens (INSECURE - local testing only), or install "
501
+ "PyJWT + cryptography for production signature verification. "
502
+ "Rejecting token for security."
503
+ )
504
+ return None
505
+
473
506
  # Decode payload (claims)
474
- claims = json.loads(_base64url_decode(parts[1]))
507
+ claims = json.loads(_base64url_decode(payload_b64))
475
508
 
476
509
  # Validate issuer
477
510
  if claims.get("iss") != OIDC_ISSUER: