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.340"
VERSION = "0.261.341"
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
Expand Up @@ -18,9 +18,11 @@
from content_screening import access

def resolve_manifest(user_id, action_ref):
scope_type, scope_id, action_id = action_ref.split(":", 2)
catalog_ref = functions_action_catalog._action_ref(scope_type, scope_id, action_id)
user = functions_authentication.get_current_user_info() or {}
return functions_action_catalog.resolve_action_manifest(
user_id, action_ref, settings=functions_settings.get_settings(), user_roles=user.get("roles") or [],
user_id, catalog_ref, settings=functions_settings.get_settings(), user_roles=user.get("roles") or [],

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

Check warning on line 25 in application/single_app/functions_action_credential_runtime.py

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.
)

def resolve_agent(reference, *, user_id):
Expand Down
7 changes: 7 additions & 0 deletions docs/explanation/features/YAMCS_ACTION.md
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,13 @@
See [Personal action credentials](PERSONAL_ACTION_CREDENTIALS.md) for the
framework, storage behavior, continuation, and limitations.

Fixed in version **0.261.341**: runtime credential references are translated to

Check warning on line 182 in docs/explanation/features/YAMCS_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.
the action catalog's canonical format before reauthorization. This prevents a

Check warning on line 183 in docs/explanation/features/YAMCS_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.
correctly configured personal credential source from failing with a generic

Check warning on line 184 in docs/explanation/features/YAMCS_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.
validation error before its chat card appears. Existing actions, identities, and
private bindings do not need migration. See the
[personal credential prompt fix](../fixes/YAMCS_PERSONAL_CREDENTIAL_PROMPT_FIX.md).

Check warning on line 187 in docs/explanation/features/YAMCS_ACTION.md

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 187 in docs/explanation/features/YAMCS_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.

## Usage Instructions

Create a new action from a personal, group, or admin action surface and choose **Yamcs**. Enter the Yamcs server URL and the instance name, and optionally change the processor from the `realtime` default.
Expand Down
61 changes: 61 additions & 0 deletions docs/explanation/fixes/YAMCS_PERSONAL_CREDENTIAL_PROMPT_FIX.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
# YAMCS Personal Credential Prompt Fix (0.261.341)

Check warning on line 1 in docs/explanation/fixes/YAMCS_PERSONAL_CREDENTIAL_PROMPT_FIX.md

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 1 in docs/explanation/fixes/YAMCS_PERSONAL_CREDENTIAL_PROMPT_FIX.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.

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

## Issue

A correctly configured YAMCS action using each user's personal proxy identity
could fail in chat without displaying its credential card. The agent instead

Check warning on line 9 in docs/explanation/fixes/YAMCS_PERSONAL_CREDENTIAL_PROMPT_FIX.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.
reported `Yamcs configuration or response is invalid.` No pending credential
request was created. The YAMCS server and authenticating proxy could be healthy.

## Root Cause

Private credential requests and bindings identify actions as
`<scope>:<scope-id>:<action-id>`. The shared action catalog resolves a canonical
`action:v1:<scope>:<encoded-scope-id>:<encoded-action-id>` reference.

The initialized credential runtime passed its stored reference directly to the
catalog. The catalog rejected it with a `ValueError` before personal credential
resolution could create a request. The YAMCS plugin correctly kept that internal
exception out of the model response, but its generic validation result concealed
the reference mismatch from users.

## Change

`application/single_app/functions_action_credential_runtime.py` now splits the
stored reference at its first two separators and calls the existing catalog
reference builder before resolving the action. Splitting only twice preserves
action IDs that contain a colon.

The catalog still reauthorizes the exact action against the current actor,
workspace ownership or membership, governance, and enabled state. Stored private
references and fingerprints are unchanged, so existing identities, bindings, and
pending interactions do not require migration. No credential is copied into a
shared action, and no frontend or route changes are needed.

## Validation

`functional_tests/test_action_credentials_bootstrap.py` exercises the real
runtime, catalog, and credential owners in fresh processes with network access
blocked and installed-SDK CosmosDict responses. The regression failed at the
catalog parser before the fix and passed afterward.

Coverage includes global and personal proxy-only actions, creation of the
private pending credential challenge, colon-bearing action IDs, foreign personal
action rejection, and rejection of a group the actor has not joined. Existing
normal/optimized Python, import-order, web startup, and scheduler checks remain
in the same suite.

Related coverage remains in `functional_tests/test_action_personal_credentials.py`
and `functional_tests/test_action_credentials_sk_continuation.py` for identity
storage, secret redaction, credential submission, SDK transport, and continuation.

## Deployment

Deploy version **0.261.341** or later, then invoke the saved action in chat. When
the invoking user has no confirmed personal proxy binding, chat should display
the credential card instead of returning the generic validation failure. The
hosted UI must be retested after deployment; offline regression coverage is not
a claim that an older hosted build has changed.
54 changes: 53 additions & 1 deletion functional_tests/test_action_credentials_bootstrap.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
# test_action_credentials_bootstrap.py
"""Fresh-process application bootstrap and actual chat SDK mapping ownership.

Version: 0.261.337
Version: 0.261.341
Implemented in: 0.261.333
Runtime catalog reference regression added in: 0.261.341

Normal/optimized Python imports real modules in both orders with network blocked.
The initialized web app owns private state; backup/transfer inventories exclude it.
Expand Down Expand Up @@ -92,6 +93,57 @@ def check(value, message):
pass
else:
raise AssertionError("Private credentials bypassed actual conversation ownership.")
from flask import g
from unittest.mock import patch
from functions_action_manifest import bind_action_origin
from action_credential_contracts import ActionCredentialsRequired
import functions_settings
catalog_settings = {
**functions_settings.get_settings(), "enable_semantic_kernel": True,
"allow_user_plugins": True, "enable_user_workspace": True,
"allow_group_plugins": True, "enable_group_workspaces": True,
"per_user_semantic_kernel": False,
}
g.conversation_id = "credential-chat"
with patch.object(functions_settings, "get_settings", return_value=catalog_settings):
for scope_type, scope_id in (("global", "global"), ("personal", "owner")):
action_id = f"credential-{scope_type}:mission"
action = {
"id": action_id, "name": "mission", "type": "yamcs", "is_enabled": True,
"endpoint": "https://mission.example.invalid", "metadata": {},
"auth": {"type": "NoAuth"},
"additionalFields": {
"instance": "sim", "auth_method": "none", "enable_basic_auth": True,
},
"credential_requirements": {"version": 1, "slots": {
"proxy": {"source": "personal", "auth_type": "username_password"},
}},
}
if scope_type == "personal":
action["user_id"] = scope_id
action_container = getattr(config, f"cosmos_{scope_type}_actions_container")
action_container.upsert_item(action)
bound = bind_action_origin(action, scope_type, scope_id)
reference = credentials.manifest_reference(bound)
resolved = credentials.credential_services().resolve_manifest("owner", reference)
check(resolved["id"] == action_id, "Credential runtime resolved the wrong catalog action.")
with credentials.credential_continuation({"mode": "chat", "checkpoint_id": action_id}):
try:
credentials.resolve_personal_credentials(bound)
except ActionCredentialsRequired as error:
check(error.credential_request["status"] == "pending", "Credential challenge was not pending.")
requirements = error.credential_request["requirements"]
check(len(requirements) == 1 and requirements[0]["slot"] == "proxy",
"Proxy-only YAMCS requested the wrong credentials.")
else:
raise AssertionError("Missing personal proxy credentials did not create a challenge.")
for forbidden_reference in ("personal:other:mission", "group:unjoined:mission"):
try:
credentials.credential_services().resolve_manifest("owner", forbidden_reference)
except PermissionError:
pass
else:
raise AssertionError("Credential reference translation bypassed catalog authorization.")
saved = chats._persist_screened_assistant({
"id": "credential-answer", "conversation_id": "credential-chat",
"user_id": "owner", "role": "assistant", "content": "The retained answer.",
Expand Down
Loading