Improve security
This commit is contained in:
@@ -22,7 +22,9 @@ def queue_codex_event_with_pre_approval(
|
||||
action: str,
|
||||
provider_payload: dict,
|
||||
idempotency_key: str,
|
||||
provider: str = "codex_cli",
|
||||
) -> tuple[ExternalSyncEvent, CodexPermissionRequest]:
|
||||
provider = str(provider or "codex_cli").strip() or "codex_cli"
|
||||
approval_key = _deterministic_approval_key(idempotency_key)
|
||||
waiting_event, _ = ExternalSyncEvent.objects.update_or_create(
|
||||
idempotency_key=f"codex_waiting:{idempotency_key}",
|
||||
@@ -30,7 +32,7 @@ def queue_codex_event_with_pre_approval(
|
||||
"user": user,
|
||||
"task": task,
|
||||
"task_event": task_event,
|
||||
"provider": "codex_cli",
|
||||
"provider": provider,
|
||||
"status": "waiting_approval",
|
||||
"payload": {
|
||||
"action": str(action or "append_update"),
|
||||
@@ -43,16 +45,18 @@ def queue_codex_event_with_pre_approval(
|
||||
run.error = ""
|
||||
run.save(update_fields=["status", "error", "updated_at"])
|
||||
|
||||
provider_label = "Claude" if provider == "claude_cli" else "Codex"
|
||||
xmpp_cmd = ".claude" if provider == "claude_cli" else ".codex"
|
||||
request, _ = CodexPermissionRequest.objects.update_or_create(
|
||||
approval_key=approval_key,
|
||||
defaults={
|
||||
"user": user,
|
||||
"codex_run": run,
|
||||
"external_sync_event": waiting_event,
|
||||
"summary": "Pre-submit approval required before sending to Codex",
|
||||
"summary": f"Pre-submit approval required before sending to {provider_label}",
|
||||
"requested_permissions": {
|
||||
"type": "pre_submit",
|
||||
"provider": "codex_cli",
|
||||
"provider": provider,
|
||||
"action": str(action or "append_update"),
|
||||
},
|
||||
"resume_payload": {
|
||||
@@ -68,7 +72,7 @@ def queue_codex_event_with_pre_approval(
|
||||
},
|
||||
)
|
||||
|
||||
cfg = TaskProviderConfig.objects.filter(user=user, provider="codex_cli", enabled=True).first()
|
||||
cfg = TaskProviderConfig.objects.filter(user=user, provider=provider, enabled=True).first()
|
||||
settings_payload = dict(getattr(cfg, "settings", {}) or {})
|
||||
approver_service = str(settings_payload.get("approver_service") or "").strip().lower()
|
||||
approver_identifier = str(settings_payload.get("approver_identifier") or "").strip()
|
||||
@@ -78,10 +82,10 @@ def queue_codex_event_with_pre_approval(
|
||||
approver_service,
|
||||
approver_identifier,
|
||||
text=(
|
||||
f"[codex approval] key={approval_key}\n"
|
||||
"summary=Pre-submit approval required before sending to Codex\n"
|
||||
f"[{provider} approval] key={approval_key}\n"
|
||||
f"summary=Pre-submit approval required before sending to {provider_label}\n"
|
||||
"requested=pre_submit\n"
|
||||
f"use: .codex approve {approval_key} or .codex deny {approval_key}"
|
||||
f"use: {xmpp_cmd} approve {approval_key} or {xmpp_cmd} deny {approval_key}"
|
||||
),
|
||||
attachments=[],
|
||||
metadata={"origin_tag": f"codex-pre-approval:{approval_key}"},
|
||||
|
||||
@@ -26,6 +26,7 @@ from core.tasks.chat_defaults import ensure_default_source_for_chat, resolve_mes
|
||||
from core.tasks.codex_approval import queue_codex_event_with_pre_approval
|
||||
from core.tasks.providers import get_provider
|
||||
from core.tasks.codex_support import resolve_external_chat_id
|
||||
from core.security.command_policy import CommandSecurityContext, evaluate_command_policy
|
||||
|
||||
_TASK_HINT_RE = re.compile(r"\b(todo|task|action|need to|please)\b", re.IGNORECASE)
|
||||
_COMPLETION_RE = re.compile(r"\b(done|completed|fixed)\s*#([A-Za-z0-9_-]+)\b", re.IGNORECASE)
|
||||
@@ -699,6 +700,20 @@ def _is_task_command_candidate(text: str) -> bool:
|
||||
return _has_task_prefix(body.lower(), ["task:", "todo:"])
|
||||
|
||||
|
||||
def _is_explicit_task_command(text: str) -> bool:
|
||||
body = str(text or "").strip()
|
||||
if not body:
|
||||
return False
|
||||
return bool(
|
||||
_LIST_TASKS_RE.match(body)
|
||||
or _LIST_TASKS_CMD_RE.match(body)
|
||||
or _TASK_SHOW_RE.match(body)
|
||||
or _TASK_COMPLETE_CMD_RE.match(body)
|
||||
or _UNDO_TASK_RE.match(body)
|
||||
or _EPIC_CREATE_RE.match(body)
|
||||
)
|
||||
|
||||
|
||||
async def process_inbound_task_intelligence(message: Message) -> None:
|
||||
if message is None:
|
||||
return
|
||||
@@ -707,6 +722,20 @@ async def process_inbound_task_intelligence(message: Message) -> None:
|
||||
text = str(message.text or "").strip()
|
||||
if not text:
|
||||
return
|
||||
security_context = CommandSecurityContext(
|
||||
service=str(message.source_service or "").strip().lower(),
|
||||
channel_identifier=str(message.source_chat_id or "").strip(),
|
||||
message_meta=dict(message.message_meta or {}),
|
||||
payload={},
|
||||
)
|
||||
if _is_explicit_task_command(text):
|
||||
command_decision = await sync_to_async(evaluate_command_policy)(
|
||||
user=message.user,
|
||||
scope_key="tasks.commands",
|
||||
context=security_context,
|
||||
)
|
||||
if not command_decision.allowed:
|
||||
return
|
||||
|
||||
sources = await _resolve_source_mappings(message)
|
||||
if not sources:
|
||||
@@ -729,6 +758,14 @@ async def process_inbound_task_intelligence(message: Message) -> None:
|
||||
if await _handle_epic_create_command(message, sources, text):
|
||||
return
|
||||
|
||||
submit_decision = await sync_to_async(evaluate_command_policy)(
|
||||
user=message.user,
|
||||
scope_key="tasks.submit",
|
||||
context=security_context,
|
||||
)
|
||||
if not submit_decision.allowed:
|
||||
return
|
||||
|
||||
completion_allowed = any(bool(_effective_flags(source).get("completion_enabled")) for source in sources)
|
||||
completion_rx = await _completion_regex(message) if completion_allowed else None
|
||||
marker_match = (completion_rx.search(text) if completion_rx else None) or (_COMPLETION_RE.search(text) if completion_allowed else None)
|
||||
|
||||
Reference in New Issue
Block a user