Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions application/single_app/background_tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,7 @@
def check_expired_approvals_once():
"""Auto-deny expired approval requests and return the affected count."""
from functions_approvals import auto_deny_expired_approvals
from functions_simplechat_operations import auto_deny_expired_generated_file_approvals

lock_document = acquire_distributed_task_lock('approval_expiry', lease_seconds=1800)
if not lock_document:
Expand All @@ -255,6 +256,18 @@
denied_count = auto_deny_expired_approvals()
if denied_count > 0:
print(f"Auto-denied {denied_count} expired approval request(s).")

try:

Check warning on line 260 in application/single_app/background_tasks.py

View workflow job for this annotation

GitHub Actions / malicious-pr-security-review

Important - Changed line contains security control, sanitization, or audit marker. Recommendation%3A Confirm the change does not weaken auth, CSRF, CSP, XSS defenses, settings sanitization, redaction, audit logging, or tests.
expired_file_count = auto_deny_expired_generated_file_approvals()
if expired_file_count > 0:
print(f"Auto-denied {expired_file_count} expired generated file approval(s).")
except Exception as exc:

Check warning on line 264 in application/single_app/background_tasks.py

View workflow job for this annotation

GitHub Actions / malicious-pr-security-review

Important - Changed line contains security control, sanitization, or audit marker. Recommendation%3A Confirm the change does not weaken auth, CSRF, CSP, XSS defenses, settings sanitization, redaction, audit logging, or tests.
# Staged file expiry must never take down the Control Center approval sweep.
print(f"Error expiring staged generated file approvals: {exc}")
log_event(

Check warning on line 267 in application/single_app/background_tasks.py

View workflow job for this annotation

GitHub Actions / malicious-pr-security-review

Important - Changed line contains security control, sanitization, or audit marker. Recommendation%3A Confirm the change does not weaken auth, CSRF, CSP, XSS defenses, settings sanitization, redaction, audit logging, or tests.
f"Error expiring staged generated file approvals: {exc}",
level=logging.ERROR,
)
finally:
release_distributed_task_lock(lock_document)

Expand Down
1 change: 1 addition & 0 deletions application/single_app/collaboration_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@


COLLABORATION_KIND = 'collaborative'
COLLABORATION_SOURCE_KIND = 'collaboration_source'

PERSONAL_MULTI_USER_CHAT_TYPE = 'personal_multi_user'
GROUP_MULTI_USER_CHAT_TYPE = 'group_multi_user'
Expand Down
2 changes: 1 addition & 1 deletion application/single_app/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@
EXECUTOR_TYPE = 'thread'
EXECUTOR_MAX_WORKERS = 30
SESSION_TYPE = 'filesystem'
VERSION = "0.260.005"
VERSION = "0.260.006"
IS_DEVELOPMENT = is_development_env_enabled()

SESSION_COOKIE_SAMESITE = os.getenv('SESSION_COOKIE_SAMESITE', 'Lax')
Expand Down
90 changes: 88 additions & 2 deletions application/single_app/functions_collaboration.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
from config import *
from collaboration_models import (
COLLABORATION_KIND,
COLLABORATION_SOURCE_KIND,
GROUP_MULTI_USER_CHAT_TYPE,
MEMBERSHIP_ROLE_ADMIN,
MEMBERSHIP_ROLE_MEMBER,
Expand Down Expand Up @@ -1568,6 +1569,91 @@
return access_context


def is_collaboration_source_conversation(conversation_item):

Check warning on line 1572 in application/single_app/functions_collaboration.py

View workflow job for this annotation

GitHub Actions / malicious-pr-security-review

Important - Changed line contains AI, plugin, agent, or workspace boundary marker. Recommendation%3A Check whether prompts, chat history, uploaded documents, embeddings, citations, settings, or identity can cross a new boundary.
"""Return True when a personal conversation is the hidden backing store of a shared one."""

Check warning on line 1573 in application/single_app/functions_collaboration.py

View workflow job for this annotation

GitHub Actions / malicious-pr-security-review

Important - Changed line contains AI, plugin, agent, or workspace boundary marker. Recommendation%3A Check whether prompts, chat history, uploaded documents, embeddings, citations, settings, or identity can cross a new boundary.

Check warning on line 1573 in application/single_app/functions_collaboration.py

View workflow job for this annotation

GitHub Actions / malicious-pr-security-review

Moderate - Changed line contains obfuscation, dynamic loading, or hidden payload marker. Recommendation%3A Confirm the changed code is not hiding behavior, decoding payloads, or bypassing normal review.
normalized_item = conversation_item or {}

Check warning on line 1574 in application/single_app/functions_collaboration.py

View workflow job for this annotation

GitHub Actions / malicious-pr-security-review

Important - Changed line contains AI, plugin, agent, or workspace boundary marker. Recommendation%3A Check whether prompts, chat history, uploaded documents, embeddings, citations, settings, or identity can cross a new boundary.
return bool(
str(normalized_item.get('conversation_kind') or '').strip() == COLLABORATION_SOURCE_KIND

Check warning on line 1576 in application/single_app/functions_collaboration.py

View workflow job for this annotation

GitHub Actions / malicious-pr-security-review

Important - Changed line contains AI, plugin, agent, or workspace boundary marker. Recommendation%3A Check whether prompts, chat history, uploaded documents, embeddings, citations, settings, or identity can cross a new boundary.
or str(normalized_item.get('collaboration_conversation_id') or '').strip()

Check warning on line 1577 in application/single_app/functions_collaboration.py

View workflow job for this annotation

GitHub Actions / malicious-pr-security-review

Important - Changed line contains AI, plugin, agent, or workspace boundary marker. Recommendation%3A Check whether prompts, chat history, uploaded documents, embeddings, citations, settings, or identity can cross a new boundary.
)


def get_collaboration_conversation_for_source(conversation_item):

Check warning on line 1581 in application/single_app/functions_collaboration.py

View workflow job for this annotation

GitHub Actions / malicious-pr-security-review

Important - Changed line contains AI, plugin, agent, or workspace boundary marker. Recommendation%3A Check whether prompts, chat history, uploaded documents, embeddings, citations, settings, or identity can cross a new boundary.
"""Load the shared conversation that owns a collaboration source conversation."""
collaboration_conversation_id = str(
(conversation_item or {}).get('collaboration_conversation_id') or ''
).strip()
if not collaboration_conversation_id:
return None

try:
return get_collaboration_conversation(collaboration_conversation_id)
except CosmosResourceNotFoundError:
return None


def build_conversation_participation_context(user_id, conversation_item):
"""Authorize one caller against a personal conversation or its linked shared conversation.

Ordinary personal conversations stay owner-only. Collaboration source conversations are
always owned by the shared conversation creator, so every other participant fails a plain
ownership comparison even though they are legitimate members. Those callers are authorized
against the linked collaboration conversation instead, mirroring the chat upload path in
``route_frontend_chats._resolve_chat_upload_context``.

Returns a context describing how access was granted so callers can distinguish an owner
acting on their own conversation from a participant acting inside a shared one.
"""
normalized_user_id = str(user_id or '').strip()
normalized_item = conversation_item or {}
owner_user_id = str(normalized_item.get('user_id') or '').strip()
conversation_id = str(normalized_item.get('id') or '').strip()

if normalized_user_id and owner_user_id == normalized_user_id:
return {
'user_id': normalized_user_id,
'conversation_id': conversation_id,
'owner_user_id': owner_user_id,
'is_owner': True,
'is_collaboration_source': is_collaboration_source_conversation(normalized_item),
'collaboration_conversation': None,
'collaboration_conversation_id': str(
normalized_item.get('collaboration_conversation_id') or ''
).strip(),
'collaboration_access': None,
'group_id': '',
'group_role': '',
}

collaboration_conversation = get_collaboration_conversation_for_source(normalized_item)
if not collaboration_conversation:
raise PermissionError('You can only access your own conversations')

collaboration_access = assert_user_can_participate_in_collaboration_conversation(
normalized_user_id,
collaboration_conversation,
)

group_id = ''
if is_group_collaboration_conversation(collaboration_conversation):
group_id = str(
(collaboration_conversation.get('scope') or {}).get('group_id') or ''
).strip()

return {
'user_id': normalized_user_id,
'conversation_id': conversation_id,
'owner_user_id': owner_user_id,
'is_owner': False,
'is_collaboration_source': True,
'collaboration_conversation': collaboration_conversation,
'collaboration_conversation_id': str(collaboration_conversation.get('id') or '').strip(),
'collaboration_access': collaboration_access,
'group_id': group_id,
'group_role': str((collaboration_access or {}).get('group_role') or '').strip(),
}


def record_personal_invite_response(conversation_id, user_id, action):
conversation_doc = get_collaboration_conversation(conversation_id)
if not is_explicit_membership_collaboration(conversation_doc):
Expand Down Expand Up @@ -1930,7 +2016,7 @@
'locked_contexts': list((conversation_doc or {}).get('locked_contexts', []) or []),
'classification': list((conversation_doc or {}).get('classification', []) or []),
'summary': (conversation_doc or {}).get('summary'),
'conversation_kind': 'collaboration_source',
'conversation_kind': COLLABORATION_SOURCE_KIND,
'collaboration_conversation_id': (conversation_doc or {}).get('id'),
'is_hidden': True,
}
Expand All @@ -1950,7 +2036,7 @@
'locked_contexts': list((conversation_doc or {}).get('locked_contexts', []) or source_conversation_doc.get('locked_contexts', []) or []),
'classification': list((conversation_doc or {}).get('classification', []) or source_conversation_doc.get('classification', []) or []),
'summary': (conversation_doc or {}).get('summary', source_conversation_doc.get('summary')),
'conversation_kind': 'collaboration_source',
'conversation_kind': COLLABORATION_SOURCE_KIND,
'collaboration_conversation_id': (conversation_doc or {}).get('id'),
'is_hidden': True,
}
Expand Down
Loading
Loading