Security model
palm installs files from repositories you choose. Most of them are text, but hooks and stdio MCP servers run programs on your machine. palm shows you those programs before it writes them, pins your consent to their exact bytes, and refuses content that can hide instructions.
palm install trailofbits/skills plugin:gh-cli --dry-run--dry-run prints every program the install would add, with its scripts, and writes nothing.
$ palm install trailofbits/skills plugin:gh-cli --dry-runThis 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.i ref ^2.1 would be saved to palm.yaml (latest tag v2.1.0); edit ref: to track main+ would install hook gh-cli .palm/assets/trailofbits__skills/gh-cli/plugins/gh-cli/, .claude/settings.json +1 3 files +4 mergeddry run: 1 would install; nothing written.What you trust
Section titled “What you trust”A source is code you chose, the same way a dependency in package.json is.
Declaring one in palm.yaml is a decision to trust its author, and it shows up in review like any other dependency.
palm does not rank, scan for intent or sandbox what a source contains.
| Kind | What it can do |
|---|---|
| skill, agent, instruction | Shape what the model does. The harness decides which tools the model may use. |
| hook | Runs a shell command on a harness event, with your permissions. |
| mcp, stdio | Starts a local program and exposes its tools to the model. |
| mcp, remote | Connects to a URL and exposes that server’s tools. Nothing runs locally. |
Programs need a yes
Section titled “Programs need a yes”Before palm writes a hook or a stdio MCP server, it shows the source, the commit and its date. It shows every command as the harness will run it and the files it lands in. It lists every script with its mode, size and hash.
- The default answer is no. Enter declines.
--yesnever consents. It covers plan confirmations only.- Consent is a hash over the commands, events, matchers, environment keys, working directory and every script byte and mode bit. Any change asks again.
- The hash covers the scripts of an in-repo source too, read from the working tree, and every file a script reads through a literal path.
- The hash goes into the lock as
trust:. Teammates and CI install the same program without a prompt, and a changed one fails CI until someone reviews it. - Without a terminal, only an
--allow-execline with the exact hash consents. - The hash pins the command text, not what
npx -y <package>downloads at run time. Pin a version in the command to pin the code.
Consent and executable content shows the prompt and the lock record.
The lock records that someone consented, not who.
Put palm.lock.yaml, .palm/assets/**, the harness hook files, such as .claude/settings.json and .codex/hooks.json, and your in-repo sources under CODEOWNERS, so every consent passes a reviewer you chose.
Share a team baseline has the file.
Scripts are committed
Section titled “Scripts are committed”palm copies what a hook runs into .palm/assets/<source>/<entity>/, and you commit it.
An in-repo source’s scripts are not copied, because they already live in the repository.
A reviewer reads the script in the same pull request as the hook command.
A clone without palm never runs a command that points at a missing file.
palm does not mark the folder as generated for GitHub’s diff view, because reviewers should read it.
What palm refuses
Section titled “What palm refuses”| Refused | Rule |
|---|---|
| Names that escape | An entity name is one path segment of letters, digits, ., _ and -, never ... |
| Symlinks that escape | palm follows a symlink inside a source only when its real target stays inside the source. A skill cannot carry ~/.ssh/id_rsa into .claude/skills. |
| Writes outside the scope | Every write and delete checks the real path. A path that resolves outside the project, or outside your home and harness homes with -g, is refused, naming the link. |
| Project scope in the wrong place | Your home directory, ~/.palm and every harness home refuse project scope, so a forgotten -g never turns your dotfiles into a project. |
| Sources that overlap outputs | A source that contains, equals or sits inside an output folder or .palm/assets is refused on real paths, also when the output folder is a symlink into the source. |
| Deleting source files | palm never deletes or overwrites a path inside a declared source, --force included. |
| Two owners for one name | A second source cannot install an entity whose kind and name another source already installed, --force included. |
| Unresolvable commands | A hook or server command that names a script not in the source is refused, with the line. palm never merges a command it could not resolve. |
| Hidden Unicode | Bidirectional overrides and isolates, tag characters and rare variation selectors refuse the entity, including in a hook’s scripts. There is no override. |
| Secrets from a source | A literal secret that arrives from a source is never written. palm writes ${NAME} instead, and --force does not change that. A skill whose copied files hold a known token shape, or a high-entropy value in a configuration file, is refused. A high-entropy value in code warns. |
| Oversized skills | A skill copy leaves out harness folders, .git, node_modules, .env* and test folders, and one above 200 files or 5 MB needs --force. |
| Unsafe git transports | ext::, fd:: and every other <x>:: transport, a leading - and control characters in URLs. http:// and git:// warn. |
| Files it does not own | A file palm did not write that differs from the render stops the install until you pass --force. |
| Files you changed | palm compares each file with the lock’s render hash before it overwrites or deletes it. |
$ palm install acme/unicode-demo bidi-overridex skill bidi-overridex skill bidi-override from acme/unicode-demo: skills/bidi-override/SKILL.md: 1 hidden character, first U+202E RIGHT-TO-LEFT OVERRIDE at line 8 fix it in the source (or a fork of it), then run: palm install acme/unicode-demo skill:bidi-override1 failed.How palm runs git
Section titled “How palm runs git”git is the only program palm starts, apart from your pager.
- A clean environment from an allow list. Tokens and variables such as
GIT_DIRnever reach git, so a palm run from a git hook never touches the hook’s repository. protocol.ext.allow=never,protocol.fd.allow=never, andprotocol.file.allow=neverexcept for local sources.--before every URL and ref, so a source cannot pass itself off as an option.GIT_TERMINAL_PROMPT=0and ssh in batch mode, so git never prompts.- 120 seconds for network steps, 30 seconds for local ones.
- Inside a project, only read-only git commands.
What palm never does
Section titled “What palm never does”- It never runs what it installs.
- It never writes a secret that came from a source. A value you type becomes an environment reference unless you pass
--secrets literal. - It never writes a literal into a file inside a git worktree under
-g,--forceincluded, and warns before a project file git would commit. - It never stores a secret value in the index or the lock. Both hold redacted hashes.
- It never sends telemetry, checks for updates or queries a registry.
Deliberately not built
Section titled “Deliberately not built”| Idea | Why not |
|---|---|
| A minimum-age or new-maintainer gate | git dates and authors are forgeable, so it would give false comfort. |
| Scanning prose for prompt injection | Unbounded false positives. The review diff is the honest tool. |
| Running scripts in a sandbox to inspect them | palm’s promise is that it runs nothing it installs. |
| Signature verification | No source ships keys to verify against yet. |
Report a vulnerability
Section titled “Report a vulnerability”Report a security problem in palm privately, through a GitHub security advisory. Do not open a public issue for it. The security policy lists what is in scope and how fast we respond. The content of a source you chose is out of scope, so report a malicious skill to its repository’s host.