Claude Code narrows the plugin trust boundary
What changed. Version 2.1.207 now rejects ${user_config.*} inside shell-form plugin hooks, monitors, and MCP headersHelper commands. Hook authors must use exec form—an args array—or read $CLAUDE_PLUGIN_OPTION_… inside the invoked script. The same release stops accepting pluginConfigs values from project-level .claude/settings.json; only user, explicit --settings, and managed settings remain valid sources.1
Why it matters. A checked-in repository is an untrusted input surface. String interpolation into a shell command turns a convenience option into a command-injection path; allowing project settings to supply privileged plugin values also lets cloned code reshape host behavior. The release converts both concerns into enforced product policy rather than prose.
Action. Search plugin packages for shell-form command values containing ${user_config.. Convert each hook to exec form, or pass only the option’s environment-variable name and parse its value inside a script without evaluation. Remove pluginConfigs from checked-in project settings and relocate intended values to user or managed settings.
REJECT: command: "tool --token '${user_config.token}'"
ACCEPT: command: "./hooks/run-check"
args: ["--mode", "strict"]
SCRIPT: read CLAUDE_PLUGIN_OPTION_TOKEN as data; never eval it
Acceptance check. On 2.1.207, start once with a deliberately retained shell-form interpolation and confirm it is rejected. Then migrate, restart, and verify the hook receives an option containing spaces and shell metacharacters as one literal value. Also confirm deleting project-level pluginConfigs does not change the resolved user/managed value.
Caveat. This is vendor implementation evidence, not an independent exploit reproduction. Exec form removes shell parsing at this boundary; the called script can still reintroduce injection if it evaluates or concatenates the value unsafely.