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
- User installs the plugin and submits any prompt
- 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`
- 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
- 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.
- 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
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 viahookSpecificOutput.additionalContext, instructing the model to callAskUserQuestionand 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
Secondary concern — ordering
In
main(),trackEventsis called at line 30 before the preference file is read at line 41. WhileisPromptTelemetryEnabled()correctly returnsfalsewhen 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.Expected behavior
trackEvents, regardless of whatisPromptTelemetryEnabled()returns.Environment
claude-plugins-officialUserPromptSubmitcontext injection