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
2 changes: 1 addition & 1 deletion application/single_app/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@
EXECUTOR_TYPE = 'thread'
EXECUTOR_MAX_WORKERS = 30
SESSION_TYPE = 'filesystem'
VERSION = "0.261.328"
VERSION = "0.261.329"
IS_DEVELOPMENT = is_development_env_enabled()

# Opt-out for deployments where App Service Easy Auth is active but the platform
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
# functions_orchestration_external_sources.py
"""Server admission and current access for retained external content.

Version: 0.261.291
Version: 0.261.329
Session-trusted agent and action sources in: 0.261.270
Hand-selected agent honoured while the agent preference is off in: 0.261.289
Stored agents accepted as any dictionary, and refusals logged by check, in: 0.261.291 (#1699)
Authenticated action roles forwarded to discovery and exact resolution in: 0.261.329

Check warning on line 8 in application/single_app/functions_orchestration_external_sources.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.

No fetch, recall, plugin invocation, settings discovery, or credential persistence
occurs here. Content digests attest the exact retained payload, not a remote page
Expand Down Expand Up @@ -310,7 +311,9 @@
callback = self.agent_catalog_reader if source_type == "agent" else self.action_catalog_reader
if callback is None:
raise ResultUnavailableError("result_external_catalog_required")
if seeded_agent is None:
if source_type == "action":
catalog = callback(identity.user_id, settings=settings, user_roles=list(identity.roles))

Check warning on line 315 in application/single_app/functions_orchestration_external_sources.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.
elif seeded_agent is None:

Check warning on line 316 in application/single_app/functions_orchestration_external_sources.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.
catalog = callback(identity.user_id, settings=settings)
else:
# Narrowed to the run's selection exactly as planning and execution narrowed it.
Expand Down Expand Up @@ -413,7 +416,7 @@
if self.action_resolver is None:
raise ResultUnavailableError("result_external_resolver_required")
resolved = self.action_resolver(
identity.user_id, selected["action_ref"], settings=settings,
identity.user_id, selected["action_ref"], settings=settings, user_roles=list(identity.roles),

Check warning on line 419 in application/single_app/functions_orchestration_external_sources.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.
)
origin = get_action_origin(resolved)
if not isinstance(resolved, dict):
Expand Down
14 changes: 13 additions & 1 deletion docs/explanation/features/CONTROL_CENTER_ACTION.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@

**Version implemented:** 0.261.301

**Updated in version: 0.261.329.** Orchestration forwards authenticated roles

Check warning on line 9 in docs/explanation/features/CONTROL_CENTER_ACTION.md

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.
through worker preflight and retained-result authorization. See the

Check warning on line 10 in docs/explanation/features/CONTROL_CENTER_ACTION.md

View workflow job for this annotation

GitHub Actions / malicious-pr-security-review

Important - Changed line contains secret or sensitive data source marker. Recommendation%3A Pair this source with any nearby network, logging, serialization, or process execution sink before approving.
[role propagation fix](../fixes/CONTROL_CENTER_ORCHESTRATION_ROLE_PROPAGATION_FIX.md).

Check warning on line 11 in docs/explanation/features/CONTROL_CENTER_ACTION.md

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.

**Dependencies:** Semantic Kernel actions (`enable_semantic_kernel`), Chat Orchestration with Action Access for chat use, the V2 Control Center Dashboard, and the existing Control Center role settings.

## Technical specifications
Expand All @@ -29,7 +33,14 @@
2. Resolving the action's manifest for a plan step refuses anyone else.
3. Every function re-reads the capabilities from the signed-in session before reading data.

Orchestration captures the caller's roles with their identity and passes them explicitly to the catalog and manifest resolution, including on its worker threads, where the run re-enters a request context carrying the authenticated session. A scheduled workflow run has no signed-in session, so the action refuses it. Any failure to determine access refuses the call.
Orchestration captures the caller's roles with their authenticated identity and

Check warning on line 36 in docs/explanation/features/CONTROL_CENTER_ACTION.md

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.
passes them explicitly to catalog discovery and exact manifest resolution.

Check warning on line 37 in docs/explanation/features/CONTROL_CENTER_ACTION.md

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.
Worker preflight, result admission, and retained-result reads do not need a Flask
request context: their external-source identity reader supplies those roles.

Check warning on line 39 in docs/explanation/features/CONTROL_CENTER_ACTION.md

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.
Actual plugin calls use the existing execution bridge to re-enter a request
context carrying the authenticated session. A scheduled workflow run has no
signed-in session, so the action refuses it. Any failure to determine access
refuses the call.

The type is global-only. Personal and group workspaces cannot list, create or save it; an administrator creates it in **Admin Settings › Agents & Actions › Global Actions**.

Expand Down Expand Up @@ -105,6 +116,7 @@

- `functional_tests/test_v2_control_center_action.py`: refusal without dashboard access and outside a signed-in request, role rules through the real capability function, bounded chartable rows, validation before reads, generic storage errors, no writes, kernel function parameters, schema registration, readiness rules and remedies, admin-only links that match real Admin Settings sections and the browser allowlist, and the readiness route.
- `functional_tests/test_orchestration_action_catalog.py`: the action is offered and resolved only to dashboard viewers, never outside global scope, and the type is refused for personal and group governance.
- `functional_tests/test_control_center_orchestration_roles.py`: real worker preflight, acquisition checks, result admission, and fresh-provider retained reads with authenticated roles; denial after access changes and refusal of stale catalogs or saved/ambient roles.
- `functional_tests/test_v2_control_center_dashboard.py`: the shared aggregation and reports.
- `ui_tests/test_v2_control_center_dashboard.py`: the checklist, **Check again**, and the prompt hand-off into a new orchestrated chat.

Expand Down
12 changes: 12 additions & 0 deletions docs/explanation/features/ORCHESTRATION_EXTERNAL_SOURCE_ACCESS.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,12 @@ refused just before it ran. Each refusal is now logged with the check that faile
See the [agent document type fix](../fixes/ORCHESTRATION_AGENT_DOCUMENT_TYPE_FIX.md)
([#1699](https://github.com/microsoft/simplechat/issues/1699)).

**Updated in version: 0.261.329.** Action discovery and exact manifest resolution
receive the authenticated external-source identity's roles explicitly. Control
Center actions therefore remain available to authorized callers during worker
preflight and retained-result reads without a Flask request context. See the
[Control Center role propagation fix](../fixes/CONTROL_CENTER_ORCHESTRATION_ROLE_PROPAGATION_FIX.md).

## Purpose and scope

An external Gather result is the content an authorized adapter actually returned,
Expand Down Expand Up @@ -68,6 +74,12 @@ is refused as `result_external_source_unavailable`, and a narrowed catalog with
more than one agent is refused. The pick only selects a record from the current
catalog; its own name, scope and ID fields are never proof of access. Action steps
never receive the pick.
For action steps, the provider passes `user_roles=list(identity.roles)` to both
the action catalog reader and exact manifest resolver. Injected action callbacks
must accept this keyword. These roles come from the current authenticated
external-source identity, never a saved run or plan; the absence of a Flask
request on a worker is not a reason to drop them. Agent callbacks do not receive
this action-specific keyword.
Membership in a catalog alone is insufficient: the provider also calls
`resolve_delegation_agent` or `resolve_action_manifest` for the exact stored
integration. These existing APIs recheck scope membership, governance, enablement,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
# Control Center Orchestration Role Propagation Fix

**Version: 0.261.329**

Fixed in version: **0.261.329**, recorded in
`application/single_app/config.py`.

## Issue

A signed-in Control Center viewer could ask orchestration to compare dashboard
activity and chart daily trends. Planning selected the Control Center action, but
execution reported that the action was unavailable. The dependent summary and
chart were not delivered.

Application Insights and App Service console logs identified an
`external_source_preflight` refusal for `action_invoke`, with
`authority_reason=result_external_source_unavailable` and
`reason=selection_not_in_catalog`. The executor reported
`failure_code=integration_unavailable`. This happened before a Control Center
tool call, not while querying dashboard data.

## Root cause

Orchestration executes on a worker without the original Flask request context.
Its external-source identity reader captures the signed-in session's roles before
the worker starts and rechecks the actor's current conversation and user access.
Planning already supplied those roles to action discovery.

`OrchestrationExternalSourceProvider` did not forward that identity's roles to
either the action catalog reader or the exact action manifest resolver. Those
boundaries correctly refuse Control Center access without an authenticated role.
Their fallback to the current request session found no session on the worker, so
the catalog hid an action the planner had just selected.

Correcting discovery alone was insufficient: exact manifest resolution then
refused the same caller with `integration_access_denied`.

## Changes

The provider now supplies `user_roles=list(identity.roles)` when discovering
actions and resolving the selected action's exact manifest. Both operations use
the existing authenticated identity reader, not roles from a saved plan, run,
action record, or browser input.

These shared boundaries apply before invocation, during session-trusted
acquisition checks, when admitting results, and when reopening retained results.
The agent catalog callback and hand-selected-agent narrowing are unchanged.
Injected action catalog readers and resolvers accept the existing `user_roles`
keyword used by their production counterparts.

Permission rules have not changed:

- Only a caller whose current roles and Control Center settings grant dashboard
access can use the global Control Center action.
- Current action enablement, governance, exact scope, and conversation access are
still checked.
- Saved or ambient roles cannot override the authenticated external-source
identity.
- Runs without a signed-in session remain refused. Plugin functions still check
the signed-in session through the existing execution bridge before reading data.

No route, dashboard query, frontend control, deployment setting, or storage schema
changed.

## Files modified

| File | Change |
| --- | --- |
| `application/single_app/functions_orchestration_external_sources.py` | Forward authenticated roles to both action authorization boundaries. |
| `application/single_app/config.py` | Application patch version `0.261.329`. |
| `functional_tests/test_control_center_orchestration_roles.py` | Worker-context, exact-resolution, retained-result, and permission-revocation regressions. |
| Existing external-source, pre-effect, bootstrap, and seeded-agent tests | Accept and verify the action callback role parameter without changing the agent protocol. |
| `docs/explanation/features/CONTROL_CENTER_ACTION.md` | Explain worker preflight versus the plugin's signed-in execution bridge. |
| `docs/explanation/features/ORCHESTRATION_EXTERNAL_SOURCE_ACCESS.md` | Document the authenticated action callback contract. |

## Validation

The new regressions exercise the real external-source provider, action catalog,
and exact manifest resolver over existing isolated storage fixtures. Networking
is blocked. The role rule is the shipped Control Center capability function,
loaded through the existing bootstrap-free test helper.

The three allowed-role worker cases and the independent exact-resolution case
failed against the provider before this fix. The worker cases reproduced
`selection_not_in_catalog`; the exact-resolution case reproduced
`integration_access_denied`.

| Scenario | Before | After |
| --- | --- | --- |
| Admin or permitted dedicated Control Center viewer on a worker | Selected action refused before a tool call. | Preflight, acquisition checks, admission, persistence, and fresh-provider reads succeed. |
| Discovery succeeds but exact resolution has no Flask session | Manifest resolution refused the authorized caller. | Exact resolution receives the authenticated roles. |
| Dashboard access, action enablement, or governance revoked | Access must be refused. | Preflight and retained reads still refuse access. |
| Stale catalog or saved/ambient Admin roles | Must not grant access. | Exact resolution and current identity checks refuse access. |

Related suites cover existing action governance, session identity capture,
session-trusted result access, bootstrap and preflight wiring, selected-agent
behavior, and the read-only Control Center plugin. The combined run passed
**533 tests and 276 subtests** with the application's pinned Flask, Werkzeug,
and Semantic Kernel versions in an isolated test environment. Documentation
coverage passed all seven checks; documentation site quality passed all six.

Live deployment and chat retry are separate from this code fix and were not
performed.

## Related documentation

- [Control Center action](../features/CONTROL_CENTER_ACTION.md)
- [Retained orchestration external source access](../features/ORCHESTRATION_EXTERNAL_SOURCE_ACCESS.md)
Loading
Loading