Improve security

This commit is contained in:
2026-03-07 15:34:23 +00:00
parent add685a326
commit 611de57bf8
31 changed files with 3617 additions and 58 deletions

View File

@@ -338,6 +338,23 @@ def _codex_settings_with_defaults(raw: dict | None) -> dict:
}
def _claude_settings_with_defaults(raw: dict | None) -> dict:
row = dict(raw or {})
timeout_raw = str(row.get("timeout_seconds") or "60").strip()
try:
timeout_seconds = max(1, int(timeout_raw))
except Exception:
timeout_seconds = 60
return {
"command": str(row.get("command") or "claude").strip() or "claude",
"workspace_root": str(row.get("workspace_root") or "").strip(),
"default_profile": str(row.get("default_profile") or "").strip(),
"timeout_seconds": timeout_seconds,
"approver_service": str(row.get("approver_service") or "").strip().lower(),
"approver_identifier": str(row.get("approver_identifier") or "").strip(),
}
def _enqueue_codex_task_submission(
*,
user,
@@ -347,10 +364,12 @@ def _enqueue_codex_task_submission(
mode: str = "default",
command_text: str = "",
source_message=None,
provider: str = "codex_cli",
) -> CodexRun:
provider = str(provider or "codex_cli").strip() or "codex_cli"
external_chat_id = resolve_external_chat_id(
user=user,
provider="codex_cli",
provider=provider,
service=source_service,
channel=source_channel,
)
@@ -398,6 +417,7 @@ def _enqueue_codex_task_submission(
action="append_update",
provider_payload=dict(provider_payload),
idempotency_key=idempotency_key,
provider=provider,
)
return run
@@ -703,6 +723,12 @@ class TasksHub(LoginRequiredMixin, View):
"mapped": mapped,
}
)
enabled_providers = list(
TaskProviderConfig.objects.filter(user=request.user, enabled=True)
.exclude(provider="mock")
.values_list("provider", flat=True)
.order_by("provider")
)
return {
"projects": projects,
"project_choices": all_projects,
@@ -711,6 +737,7 @@ class TasksHub(LoginRequiredMixin, View):
"person_identifier_rows": person_identifier_rows,
"selected_project": selected_project,
"show_empty_projects": show_empty,
"enabled_providers": enabled_providers,
}
def get(self, request):
@@ -1152,9 +1179,13 @@ class TaskSettings(LoginRequiredMixin, View):
provider_map = _provider_row_map(request.user)
codex_cfg = provider_map.get("codex_cli")
codex_settings = _codex_settings_with_defaults(dict(getattr(codex_cfg, "settings", {}) or {}))
claude_cfg = provider_map.get("claude_cli")
claude_settings = _claude_settings_with_defaults(dict(getattr(claude_cfg, "settings", {}) or {}))
mock_cfg = provider_map.get("mock")
codex_provider = get_provider("codex_cli")
claude_provider = get_provider("claude_cli")
codex_healthcheck = codex_provider.healthcheck(codex_settings) if codex_cfg else None
claude_healthcheck = claude_provider.healthcheck(claude_settings) if claude_cfg else None
codex_queue_counts = {
"pending": ExternalSyncEvent.objects.filter(
user=request.user, provider="codex_cli", status="pending"
@@ -1169,11 +1200,25 @@ class TaskSettings(LoginRequiredMixin, View):
user=request.user, provider="codex_cli", status="ok"
).count(),
}
claude_queue_counts = {
"pending": ExternalSyncEvent.objects.filter(
user=request.user, provider="claude_cli", status="pending"
).count(),
"waiting_approval": ExternalSyncEvent.objects.filter(
user=request.user, provider="claude_cli", status="waiting_approval"
).count(),
"failed": ExternalSyncEvent.objects.filter(
user=request.user, provider="claude_cli", status="failed"
).count(),
"ok": ExternalSyncEvent.objects.filter(
user=request.user, provider="claude_cli", status="ok"
).count(),
}
codex_recent_runs = CodexRun.objects.filter(user=request.user).order_by("-created_at")[:10]
latest_worker_event = (
ExternalSyncEvent.objects.filter(
user=request.user,
provider="codex_cli",
provider__in=["codex_cli", "claude_cli"],
)
.filter(status__in=["ok", "failed", "waiting_approval", "retrying"])
.order_by("-updated_at")
@@ -1233,6 +1278,21 @@ class TaskSettings(LoginRequiredMixin, View):
"queue_counts": codex_queue_counts,
"recent_runs": codex_recent_runs,
},
"claude_provider_config": claude_cfg,
"claude_provider_settings": {
"command": str(claude_settings.get("command") or "claude"),
"workspace_root": str(claude_settings.get("workspace_root") or ""),
"default_profile": str(claude_settings.get("default_profile") or ""),
"timeout_seconds": int(claude_settings.get("timeout_seconds") or 60),
"approver_service": str(claude_settings.get("approver_service") or ""),
"approver_identifier": str(claude_settings.get("approver_identifier") or ""),
},
"claude_compact_summary": {
"healthcheck_ok": bool(getattr(claude_healthcheck, "ok", False)),
"healthcheck_error": str(getattr(claude_healthcheck, "error", "") or ""),
"healthcheck_payload": dict(getattr(claude_healthcheck, "payload", {}) or {}),
"queue_counts": claude_queue_counts,
},
"person_identifiers": person_identifiers,
"external_link_person_identifiers": external_link_person_identifiers,
"external_link_scoped": external_link_scoped,
@@ -1376,6 +1436,17 @@ class TaskSettings(LoginRequiredMixin, View):
"approver_mode": "channel",
}
)
elif provider == "claude_cli":
settings_payload = _claude_settings_with_defaults(
{
"command": request.POST.get("command"),
"workspace_root": request.POST.get("workspace_root"),
"default_profile": request.POST.get("default_profile"),
"timeout_seconds": request.POST.get("timeout_seconds"),
"approver_service": request.POST.get("approver_service"),
"approver_identifier": request.POST.get("approver_identifier"),
}
)
row.settings = settings_payload
row.save(update_fields=["enabled", "settings", "updated_at"])
return _settings_redirect(request)
@@ -1460,10 +1531,16 @@ class TaskSettings(LoginRequiredMixin, View):
return _settings_redirect(request)
_ALLOWED_SUBMIT_PROVIDERS = {"codex_cli", "claude_cli"}
class TaskCodexSubmit(LoginRequiredMixin, View):
def post(self, request):
task_id = str(request.POST.get("task_id") or "").strip()
next_url = str(request.POST.get("next") or reverse("tasks_hub")).strip()
provider = str(request.POST.get("provider") or "codex_cli").strip().lower()
if provider not in _ALLOWED_SUBMIT_PROVIDERS:
provider = "codex_cli"
task = get_object_or_404(
DerivedTask.objects.select_related("project", "epic", "origin_message"),
id=task_id,
@@ -1471,13 +1548,14 @@ class TaskCodexSubmit(LoginRequiredMixin, View):
)
cfg = TaskProviderConfig.objects.filter(
user=request.user,
provider="codex_cli",
provider=provider,
enabled=True,
).first()
provider_label = "Claude" if provider == "claude_cli" else "Codex"
if cfg is None:
messages.error(
request,
"Codex provider is disabled. Enable it in Task Settings first.",
f"{provider_label} provider is disabled. Enable it in Task Settings first.",
)
return redirect(next_url)
run = _enqueue_codex_task_submission(
@@ -1487,10 +1565,11 @@ class TaskCodexSubmit(LoginRequiredMixin, View):
source_channel=str(task.source_channel or ""),
mode="default",
source_message=getattr(task, "origin_message", None),
provider=provider,
)
messages.success(
request,
f"Queued approval for task #{task.reference_code} before Codex run {run.id}.",
f"Queued approval for task #{task.reference_code} before {provider_label} run {run.id}.",
)
return redirect(next_url)