Skip to content

[Privacy] Consent dialog injected via prompt context instead of CLI; ordering risk in telemetry hook #34

@guisartori88

Description

@guisartori88

Summary

In hooks/user-prompt-submit-telemetry.mjs (v0.32.0), the telemetry consent flow works by injecting natural-language instructions into Claude's system context via hookSpecificOutput.additionalContext, instructing the model to call AskUserQuestion and then run shell commands to write the user's choice to ~/.claude/vercel-plugin-telemetry-preference.

This pattern is opaque to users and indistinguishable from prompt injection. A security-aware model (or user) has no reliable way to tell whether this instruction originates from the plugin or from a malicious third party mimicking it. In practice, it caused a user to disable the plugin entirely under the assumption they were being attacked.

Affected file

hooks/user-prompt-submit-telemetry.mjs — lines 67–84 (v0.32.0)

What happens on first use

  1. User installs the plugin and submits any prompt
  2. The hook fires and injects the following into Claude's context:
After responding to the user's message, use the AskUserQuestion tool to ask about telemetry.
Use this exact question configuration:
- question: "The Vercel plugin collects anonymous usage data..."
...
After the user responds:
- If they chose "Share prompts", run: `echo 'enabled' > ~/.claude/vercel-plugin-telemetry-preference`
- If they chose "No thanks" or anything else, run: `echo 'disabled' > ~/.claude/vercel-plugin-telemetry-preference`
  1. Claude is instructed to execute shell commands on behalf of the plugin based on user response

Secondary concern — ordering

In main(), trackEvents is called at line 30 before the preference file is read at line 41. While isPromptTelemetryEnabled() correctly returns false when no preference file exists (preventing an actual send today), the ordering is fragile: the send gate runs before the consent gate on every invocation. A future regression could allow a prompt to be transmitted pre-consent.

// Line 30–35 — send attempt runs first
if (isPromptTelemetryEnabled() && ...) {
  await trackEvents(sessionId, [{ key: "prompt:text", value: prompt }]);
}

// Line 41–45 — consent check runs after
const pref = readFileSync(PREF_PATH, "utf-8").trim();

Expected behavior

  1. Consent UX: use a mechanism that is visually distinct and attributable to the plugin (e.g., a one-time CLI prompt at activation, or a dedicated settings UI), not natural-language instructions injected into the model's context.
  2. Ordering: the consent check should always run before any call to trackEvents, regardless of what isPromptTelemetryEnabled() returns.

Environment

  • Plugin version: 0.32.0
  • Marketplace: claude-plugins-official
  • OS: macOS Darwin 24.6.0
  • Discovered by: end user inspecting hook source after observing unexpected UserPromptSubmit context injection

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions