T09 · Insecure Skill Coding Practices
- Location
SKILL.md:420- Finding
Temporary Schema-Free HTTP Trigger May Persist After Test Failure
- Content
View full analysis
Vulnerability Details
File Location:
SKILL.md, lines 420-455
Vulnerability Type: Incomplete rollback of a temporary externally accessible trigger
Risk Level: HighVulnerable Code
python production_trigger = definition["triggers"] definition["triggers"] = { "manual": {"type": "Request", "kind": "Http", "inputs": {"schema": {}}} } result = mcp("update_live_flow", environmentName=ENV, flowName=FLOW_ID, # omit if creating new definition=definition, connectionReferences=connection_references, displayName="Overdue Invoice Notifications") FLOW_ID = FLOW_ID or result["created"] test = mcp("trigger_live_flow", environmentName=ENV, flowName=FLOW_ID, body={"sample": "payload"}) runs = mcp("get_live_flow_runs", environmentName=ENV, flowName=FLOW_ID, top=1) if runs[0]["status"] == "Failed": err = mcp("get_live_flow_run_error", environmentName=ENV, flowName=FLOW_ID, runName=runs[0]["name"]) raise Exception(err["failedActions"][-1]) definition["triggers"] = production_trigger mcp("update_live_flow", environmentName=ENV, flowName=FLOW_ID, definition=definition, connectionReferences=connection_references)Technical Analysis
The testing procedure replaces the production trigger with an HTTP request trigger whose schema is empty, allowing arbitrary valid JSON. Restoration of the production trigger occurs only after the test and run-status inspection complete successfully.
If the test run fails, the explicit
raise Exception(...)executes before restoration. An MCP error, timeout, interruption, cancellation, malformed response, or an emptyrunsresult can similarly prevent the cleanup code from running. The deployed flow may consequently retain the temporary HTTP trigger indefinitely.The temporary trigger is not inherently malicious and supports the Skill's declared testing functionality. However, deploying it directly over the target flow without guaranteed rollb ...[truncated 1586 chars]
- Remediation
View remediation
Remediation Suggestions
- Wrap temporary-trigger deployment and testing in
try/finally, restoring the production trigger in thefinallyblock regardless of test outcome. - After restoration, call
get_live_flowand verify that the deployed trigger exactly matches the saved production trigger. - If rollback fails, immediately stop the flow with
set_live_flow_stateand report the failure prominently to the user. - Prefer creating an isolated disposable test flow rather than replacing the trigger on the production flow.
- Use an explicit, restrictive test schema instead of
"schema": {}and validate all test payload fields. - Add deployment-result and response-shape checks before accessing
result["created"],runs[0], or nested error fields. - Record the original flow state and restore it after testing.
- Require explicit user confirmation not only before running the test, but also before temporarily changing the deployed trigger.
- Ensure signed callback URLs are never logged, committed, or returned to untrusted callers.
- Add automated cleanup for stale temporary test flows or triggers.
A safer control structure is:
python production_trigger = definition["triggers"] try: definition["triggers"] = { "manual": { "type": "Request", "kind": "Http", "inputs": { "schema": { "type": "object", "properties": { "sample": {"type": "string"} }, "required": ["sample"] } } } } result = mcp( "update_live_flow", environmentName=ENV, flowName=FLOW_ID, definition=definition, connectionReferences=connection_references ) if result.get("error") is not None: raise RuntimeError(result["error"]) FLOW_ID = FLOW_ID or result["created"] # Perform the approved test and validate all respon ...[truncated 534 chars]- Wrap temporary-trigger deployment and testing in
