Skip to content

Consent and executable content

Executable content is anything palm writes that makes a harness, or a program it starts, run code. Before palm writes any of it, you see every command and script, and you answer yes. The default answer is no.

Terminal window
palm install trailofbits/skills plugin:gh-cli --dry-run

--dry-run prints the same review a terminal prompt shows and writes nothing.

Executable Gated Why
hook commands, per event and matcher yes the harness runs them on every matching event
stdio MCP servers: command, arguments, working directory, environment keys yes the harness starts the program
the scripts those reach, and the files a script reads, copied to .palm/assets/ yes, as part of the unit a changed script byte changes what runs
the scripts of an in-repo source, which run in place yes, hashed in the working tree an edit changes what runs
prompt hooks (type: prompt) no text sent to the model; listed, never called a command
remote MCP servers (a URL) no nothing runs on your machine; the review says which is which
skills that cite scripts, such as scripts/find-polluter.sh no the harness asks before the model runs a shell command; palm describe lists them

palm itself never runs what it installs. The only programs palm starts are git and your pager.

On a terminal, palm prints one block per program and asks once. This is the prompt as palm prints it for a plugin hook and a stdio MCP server.

This install adds 2 programs that will run on your machine.
1. hook gh-cli from trailofbits/skills (commit 82fe822, v2.1.0, 2026-07-14)
SessionStart bash "$CLAUDE_PROJECT_DIR"/.palm/assets/trailofbits__skills/gh-cli/hooks/persist-session-id.sh
PreToolUse Bash bash "$CLAUDE_PROJECT_DIR"/.palm/assets/trailofbits__skills/gh-cli/hooks/intercept-github-curl.sh
targets: claude (.claude/settings.json), cursor (.cursor/hooks.json)
scripts: 8 files, 21 KB -> .palm/assets/trailofbits__skills/gh-cli/ (committed with your repo)
hooks/persist-session-id.sh 755 612 B sha256:1b9e04c2
hooks/intercept-github-curl.sh 755 1.1 KB sha256:77d0a9f1
... 6 more (v shows every script)
2. mcp team-helper from acme-kit (commit 61ec102, v1.1.1)
stdio node ".palm/assets/acme-kit/team-helper/server.js" env: none
targets: claude (.mcp.json), cursor (.cursor/mcp.json)
Also 1 prompt hook (text sent to the model; no program runs): fp-check Stop.
palm never runs these itself. If you say yes, their hashes go into palm.lock.yaml,
so teammates and CI install them without being asked; any change asks again.
Allow these 2 programs to run? [y/N/v=view scripts]

Each block names the source, the commit, the tag and its date. Printed without a question (a dry run, or no terminal), the block ends in (--review shows every script) instead of v. A target the program is not installed for, such as OpenCode, which has no command hooks, is named on a skipped: line. An env value a server gets as written reads KEY (literal), and an npx pkg@latest command without an exact version gets an ! unpinned line. On palm update, the headline says changes for a program you trusted before and adds for a new one. It shows every command as the harness will run it, the file it lands in per target, and the scripts with mode, size and a short hash. A script that reads another file of the source by a literal relative path, such as ../skills/x/SKILL.md, gets a reads: line. palm copies that file too, and a read it cannot resolve prints a warning at install.

Key Does
y allows every program in the list
n or Enter declines; the default
v pages every script body from the pinned commit
d during palm update, pages a unified diff against the version you trusted, fetching that commit when the cache lacks it

Every yes or no question palm asks uses the same [y/N] prompt.

palm hashes each program as one unit. The hash covers the commands before per-harness rendering, with their events and matchers. It also covers the environment keys, the working directory, and a tree hash of every script the unit reaches or reads, mode bits included.

An in-repo source’s scripts run in place, so an edit is live at once. palm still hashes them from the working tree, and v shows them from there. The next palm install asks again, and palm check fails until someone consents.

Asks again Does not ask again
a new hook or stdio server the same hash at a newer commit
any changed command, script byte, mode bit, environment key or working directory a target you add
a trust entry removed from the lock a newer palm version

The hash covers the command text and the files in the source. It cannot cover what a command downloads each time it runs. npx -y <package> runs whichever version npm resolves when the harness starts it, so one trusted hash can run different code over time. uvx <package> and docker run with a tag instead of a digest behave the same way. Pin the version in the command, such as npx -y <package>@1.4.2. A new version then changes the hash and asks again.

A yes is recorded once, in palm.lock.yaml, as a trust: hash on the entry. Every later install replays it silently while the hash matches: yours, a teammate’s and CI’s.

~/project
$ palm install trailofbits/skills plugin:gh-cli
This install adds 1 program that will run on your machine.
1. hook gh-cli from trailofbits/skills (commit 924ffa8, v2.1.0, 2026-09-28)
SessionStart CLAUDE_PLUGIN_ROOT="$CLAUDE_PROJECT_DIR"/.palm/assets/trailofbits__skills/gh-cli/plugins/gh-cli bash "$CLAUDE_PROJECT_DIR"/.palm/assets/trailofbits__skills/gh-cli/plugins/gh-cli/hooks/persist-session-id.sh
cursor: CURSOR_PLUGIN_ROOT="$CURSOR_PROJECT_DIR"/.palm/assets/trailofbits__skills/gh-cli/plugins/gh-cli CLAUDE_PLUGIN_ROOT="$CURSOR_PROJECT_DIR"/.palm/assets/trailofbits__skills/gh-cli/plugins/gh-cli bash "$CURSOR_PROJECT_DIR"/.palm/assets/trailofbits__skills/gh-cli/plugins/gh-cli/hooks/persist-session-id.sh
PreToolUse Bash CLAUDE_PLUGIN_ROOT="$CLAUDE_PROJECT_DIR"/.palm/assets/trailofbits__skills/gh-cli/plugins/gh-cli bash "$CLAUDE_PROJECT_DIR"/.palm/assets/trailofbits__skills/gh-cli/plugins/gh-cli/hooks/intercept-github-curl.sh
cursor: CURSOR_PLUGIN_ROOT="$CURSOR_PROJECT_DIR"/.palm/assets/trailofbits__skills/gh-cli/plugins/gh-cli CLAUDE_PLUGIN_ROOT="$CURSOR_PROJECT_DIR"/.palm/assets/trailofbits__skills/gh-cli/plugins/gh-cli bash "$CURSOR_PROJECT_DIR"/.palm/assets/trailofbits__skills/gh-cli/plugins/gh-cli/hooks/intercept-github-curl.sh
targets: claude (.claude/settings.json), cursor (.cursor/hooks.json)
scripts: 3 files, 438 B -> .palm/assets/trailofbits__skills/gh-cli/ (committed with your repo)
plugins/gh-cli/hooks/persist-session-id.sh 755 50 B sha256:1033f868
plugins/gh-cli/hooks/intercept-github-curl.sh 755 98 B sha256:6d5d4536
... 1 more (--review shows every script)
palm never runs this itself. If you say yes, its hash goes into palm.lock.yaml,
so teammates and CI install it without being asked; any change asks again.
x 1 program needs your consent and there is no terminal
review: palm install trailofbits/skills plugin:gh-cli --dry-run --review
then: palm install trailofbits/skills plugin:gh-cli --allow-exec hook:gh-cli@trailofbits/skills=sha256:23759d85ebda016f23c6343d5a49064f98ce3f65bd1d68616ad0a2541ae18adb
$ palm install trailofbits/skills plugin:gh-cli --allow-exec hook:gh-cli@trailofbits/skills=sha256:23759d85ebda016f23c6343d5a49064f98ce3f65bd1d68616ad0a2541ae18adb
This install adds 1 program that will run on your machine.
1. hook gh-cli from trailofbits/skills (commit 924ffa8, v2.1.0, 2026-09-28)
SessionStart CLAUDE_PLUGIN_ROOT="$CLAUDE_PROJECT_DIR"/.palm/assets/trailofbits__skills/gh-cli/plugins/gh-cli bash "$CLAUDE_PROJECT_DIR"/.palm/assets/trailofbits__skills/gh-cli/plugins/gh-cli/hooks/persist-session-id.sh
cursor: CURSOR_PLUGIN_ROOT="$CURSOR_PROJECT_DIR"/.palm/assets/trailofbits__skills/gh-cli/plugins/gh-cli CLAUDE_PLUGIN_ROOT="$CURSOR_PROJECT_DIR"/.palm/assets/trailofbits__skills/gh-cli/plugins/gh-cli bash "$CURSOR_PROJECT_DIR"/.palm/assets/trailofbits__skills/gh-cli/plugins/gh-cli/hooks/persist-session-id.sh
PreToolUse Bash CLAUDE_PLUGIN_ROOT="$CLAUDE_PROJECT_DIR"/.palm/assets/trailofbits__skills/gh-cli/plugins/gh-cli bash "$CLAUDE_PROJECT_DIR"/.palm/assets/trailofbits__skills/gh-cli/plugins/gh-cli/hooks/intercept-github-curl.sh
cursor: CURSOR_PLUGIN_ROOT="$CURSOR_PROJECT_DIR"/.palm/assets/trailofbits__skills/gh-cli/plugins/gh-cli CLAUDE_PLUGIN_ROOT="$CURSOR_PROJECT_DIR"/.palm/assets/trailofbits__skills/gh-cli/plugins/gh-cli bash "$CURSOR_PROJECT_DIR"/.palm/assets/trailofbits__skills/gh-cli/plugins/gh-cli/hooks/intercept-github-curl.sh
targets: claude (.claude/settings.json), cursor (.cursor/hooks.json)
scripts: 3 files, 438 B -> .palm/assets/trailofbits__skills/gh-cli/ (committed with your repo)
plugins/gh-cli/hooks/persist-session-id.sh 755 50 B sha256:1033f868
plugins/gh-cli/hooks/intercept-github-curl.sh 755 98 B sha256:6d5d4536
... 1 more (--review shows every script)
palm never runs this itself. If you say yes, its hash goes into palm.lock.yaml,
so teammates and CI install it without being asked; any change asks again.
allowed by --allow-exec: hook:gh-cli@trailofbits/skills (sha256:23759d85)
i ref ^2.1 saved to palm.yaml (latest tag v2.1.0); edit ref: to track main
+ hook gh-cli .palm/assets/trailofbits__skills/gh-cli/plugins/gh-cli/, .claude/settings.json +1 3 files +4 merged from trailofbits/skills v2.1.0
~ trusted hook gh-cli sha256:23759d85
1 installed. Commit palm.yaml, palm.lock.yaml, .palm/, .claude/ and .cursor/ together.
$ grep -n -A 8 'exec:' palm.lock.yaml
27: exec:
28- commands:
29- - {id: PreToolUse//Bash, command: CLAUDE_PLUGIN_ROOT="$CLAUDE_PROJECT_DIR"/.palm/assets/trailofbits__skills/gh-cli/plugins/gh-cli bash "$CLAUDE_PROJECT_DIR"/.palm/assets/trailofbits__skills/gh-cli/plugins/gh-cli/hooks/intercept-github-curl.sh}
30- - {id: SessionStart//-, command: CLAUDE_PLUGIN_ROOT="$CLAUDE_PROJECT_DIR"/.palm/assets/trailofbits__skills/gh-cli/plugins/gh-cli bash "$CLAUDE_PROJECT_DIR"/.palm/assets/trailofbits__skills/gh-cli/plugins/gh-cli/hooks/persist-session-id.sh}
31- closure: {root: .palm/assets/trailofbits__skills/gh-cli, files: 3, tree: sha256:208bc9feb334e591e9ed4f6f9a86c72cb790b6d0af6abc71d8ade7e52a7d7262}
32- hash: sha256:23759d85ebda016f23c6343d5a49064f98ce3f65bd1d68616ad0a2541ae18adb
33- trust: [sha256:23759d85ebda016f23c6343d5a49064f98ce3f65bd1d68616ad0a2541ae18adb]
34- - kind: plugin
35- name: gh-cli

The lock diff is the team’s consent record. A reviewer sees the readable command: lines and one hash line beside them, and approves both in the same pull request.

palm never prompts without a terminal, and --yes never consents. A program without a trusted hash stops the command with E_UNTRUSTED_EXEC, and the error prints the exact line that consents.

x 2 programs need your consent and there is no terminal
review: palm install --dry-run --review
then: palm install --allow-exec hook:gh-cli@trailofbits/skills=sha256:a7cc7911f2bd0a61d9686cbc62fcfb17c8e8276fa2ea5aa0c69e646a0b23ad60,mcp:team-helper@acme-kit=sha256:75aafd9baefdaaee905cd992fe17dbe17b4fa390f7f487399d6a57f282682c79

--allow-exec takes <kind>:<name>@<source>=<hash>, comma-separated for several. palm prints the full hash; a prefix of at least 16 hex characters is accepted too. It pins the exact program you reviewed, so a changed script fails again instead of slipping through. --allow-exec all works only on a terminal. Values you typed with --env or --header are redacted in the review: and then: lines.

palm install <source> --all leaves every hook and stdio server out without asking. It prints a see it: and an install it: line for each one, and exits 0.

At the prompt, what you decline decides what palm writes.

You decline palm writes Exit
a program you named, such as palm install <source> hook:<name> nothing 130
a hook that arrives with a plugin the rest of the plugin, and exclude: [hook:<name>] on the plugin entry in palm.yaml 0, with a ! … not installed line
the new version of a program you trusted before nothing: the update stops 130

The exclude: line goes through review like any other change, and later bare installs keep the hook out. palm install <source> hook:<name> asks again.

Declining a changed version of a program you trusted before ends the run and writes nothing. The previous version stays active, and palm prints its trusted hash and the palm remove command that takes it out.