GV

GeezerVison

The World Wide Web, restored to 800 pixels of glory.

Home|About|Gaming cabinet|Archives|How it works|Starter sites

On this site

  • Pullfrog home page
  • GitHub
  • Console
  • Getting started
  • Comparisons
  • Direct prompting
  • Iterating in GitHub
  • PR reviews
  • Issue enrichment
  • Coding tasks
  • Flags
  • Learnings
  • Asking other models
  • Watch a PR

This view

  • Original page
  • New address

GeezerVison is showing docs.pullfrog.com — Security - Pullfrog

Pullfrog gives agents a real shell, git and the GitHub API, and still keeps secrets and credentials out of their reach. Independent layers block the usual prompt-injection goals: reading API keys, stealing tokens and pushing malicious code.
LayerProtection
System promptTells the agent never to reveal secrets and to refuse malicious requests
Untrusted input isolationIssue and PR bodies reach the agent through a tool, not as instructions
Secret maskingRedacts secrets in GitHub Actions logs
Secret isolationSecrets reach a run only through the workflow or an OIDC-verified request
Shell environment scrubbingOnly allowlisted environment variables reach shell commands
Lifecycle scriptsSetup, post-checkout and pre-push scripts run scrubbed and sandboxed
Filesystem sandboxHides on-disk credentials and blocks environment injection and git-config tampering
Short-lived tokensGitHub tokens are scoped to the repository and revoked after each run
Permission checksRuns from people without write access are always restricted and never push
Code pushesRepo admins decide where the agent may push
Most of these layers defend against untrusted authors: people who open issues, pull requests and comments without write access. That exposure is largest on public repositories.

​
System prompt

Every run carries a system prompt that tells the agent not to reveal secrets or commit them, and to refuse any request it suspects is malicious. The prompt marks where the system section ends and the task begins, and the task cannot override the system section.

​
Untrusted input isolation

Issue and pull request bodies are the main prompt-injection vector, so Pullfrog does not inline them as context. The agent gets the event metadata it needs, such as the title, branch and author, and fetches bodies through a Pullfrog tool, so that text reaches the model as tool output, not as instructions. The exception is the text that asks Pullfrog for something: a comment, issue, pull request or review that mentions @pullfrog becomes the agent’s task.

​
Secret masking

Pullfrog registers every secret with GitHub’s secret masking, so a secret printed by accident shows as *** in the Actions log.

​
Secret isolation

A run gets secrets from two places, and neither one exposes them by default:
  • GitHub Actions secrets reach the action only when your workflow passes them under env:.
  • Pullfrog secrets, stored in the console or with the CLI, are encrypted at rest. Pullfrog releases them only to a GitHub Actions run that proves with an OIDC token that it belongs to that repository. A workflow triggered by a pull request from a fork cannot get an OIDC token, so a contributor who edits the workflow in a fork cannot reach these secrets.
Both kinds land in the action’s own environment, where a workflow variable wins over a Pullfrog secret of the same name. Shell commands see them only as the shell environment scrubbing rules allow. See API keys for how to store and scope secrets.

​
Shell environment scrubbing

The Shell isolation toggle in the repo console’s Security card decides what environment shell commands get. It works the same way for every agent.
The Security card: Code pushes, Signed commits, Shell isolation and Environment allowlist
Shell environment scrubbing example
SettingConsoleShell commands get
restrictedShell isolation on (default)Only the default allowed variables and your environment allowlist
enabledShell isolation offThe full environment, secrets included
disabledCLI or workflow input onlyNo shell at all
Shell isolation is always on for public repositories, and a repo admin can turn it off on a private one. Runs triggered by people without write access are always held to restricted, whatever the setting.
  • The allowlist is default-deny, so env or printenv cannot reveal an API key.
  • Variables whose names end in _TOKEN, _KEY, _SECRET, _PASSWORD or _CREDENTIAL are dropped even under an allowed prefix, so GITHUB_TOKEN and GH_TOKEN do not pass. Pullfrog’s git operations authenticate separately and work without them.
  • Shell commands get a GH_TOKEN of their own for the gh CLI: a read-only token for this repository’s actions, checks, contents, issues and pull requests, revoked when the run ends. Anything the shell runs can read it, so it grants no more than anyone who can trigger a run can already see.
  • The agent process itself keeps the full environment, because model providers need it. The subprocesses that execute repository code — the agent’s shell commands, and Pullfrog’s own git operations and dependency installs — run with a filtered environment inside the sandbox.
  • In CI every shell command runs in its own Linux PID namespace, so cat /proc/$PPID/environ cannot read the parent’s environment. Pullfrog creates the namespace with unshare, with passwordless sudo, or inside an unprivileged user namespace; see Self-hosted runners.
  • With isolation on, shell commands cannot gain root: sudo fails, so a sudo apt-get install step needs shell isolation off or a runner image that already has the package.

​
Lifecycle scripts

Setup, post-checkout and pre-push scripts get the same environment and the same sandbox as shell commands. You write these scripts, but the code they call is not always yours: on a pull request from a fork, a pre-push script of pnpm test runs the contributor’s tests.
  • A script that needs a secret needs that variable in the environment allowlist, or shell isolation turned off. A missing variable is the usual reason a working script starts to fail.
  • On a runner that cannot provide namespace isolation, Pullfrog skips the script instead of running it unprotected. The run continues, and the agent is told the script did not run.

​
Default allowed variables

These variables reach shell commands automatically. Every other variable is blocked unless you add it to the environment allowlist. Prefix-matched. Every variable with one of these prefixes passes, except a name that ends in one of the sensitive suffixes above.
PrefixDescription
GITHUB_*GitHub Actions runner metadata and workflow context
RUNNER_*Runner configuration and paths
JAVA_HOME_*JDK version paths, for example JAVA_HOME_17_X64
GOROOT_*Go installation paths
Exact names.

System variables

CI
HOME
LANG
LOGNAME
PATH
SHELL
SHLVL
TERM
TMPDIR
TZ
USER
XDG_CONFIG_HOME
XDG_RUNTIME_DIR
DEBIAN_FRONTEND

Runner image toolchain variables

ACCEPT_EULA
AGENT_TOOLSDIRECTORY
ANDROID_HOME
ANDROID_NDK
ANDROID_NDK_HOME
ANDROID_NDK_LATEST_HOME
ANDROID_NDK_ROOT
ANDROID_SDK_ROOT
ANT_HOME
AZURE_EXTENSION_DIR
BOOTSTRAP_HASKELL_NONINTERACTIVE
CHROME_BIN
CHROMEWEBDRIVER
CONDA
DOTNET_MULTILEVEL_LOOKUP
DOTNET_NOLOGO
DOTNET_SKIP_FIRST_TIME_EXPERIENCE
EDGEWEBDRIVER
GECKOWEBDRIVER
GHCUP_INSTALL_BASE_PREFIX
GRADLE_HOME
HOMEBREW_CLEANUP_PERIODIC_FULL_DAYS
HOMEBREW_NO_AUTO_UPDATE
ImageOS
ImageVersion
JAVA_HOME
NVM_DIR
PIPX_BIN_DIR
PIPX_HOME
PSModulePath
SELENIUM_JAR_PATH
SGX_AESM_ADDR
SWIFT_PATH
VCPKG_INSTALLATION_ROOT

​
Environment allowlist

The Environment allowlist field in the Security card passes extra variables to shell commands, one name per line. Use it for variables your build needs, such as DATABASE_URL or NPM_TOKEN. Every shell already has a read-only GH_TOKEN; list GH_TOKEN or GITHUB_TOKEN only when a prompt needs gh to write, and the listed one replaces it.
DATABASE_URL
NPM_TOKEN
Listed values reach agent shells in plaintext, so list only variables you are willing to expose to any prompt that runs in the repository. Allowlisting GITHUB_TOKEN or GH_TOKEN gives shell commands the workflow token’s permissions; narrow them with the permissions: block in pullfrog.yml.

​
Filesystem sandbox

In CI each shell command also runs in a mount namespace that hides or freezes three kinds of path. This applies whether shell isolation is on or off.
PathInside the sandbox
/var/lib/pullfrog/An empty directory. Pullfrog keeps subscription credentials here, such as ChatGPT and Grok sign-ins, and only Pullfrog’s own process reads the real files.
$RUNNER_TEMP/_runner_file_commands/A throwaway directory, so echo X >> $GITHUB_ENV cannot inject variables into later workflow steps.
The repository’s .git directoryRead-only for shell commands, so the agent cannot plant a git hook or filter that runs in a later workflow step. Pullfrog’s own git operations and dependency installs run in the same PID-namespace isolation but keep .git writable, so commits and hook managers such as husky still work while a planted hook that fires on them cannot read the environment.
The agent’s built-in file tools have their own rules. They cannot write anywhere under .git, including .git pointer files in worktree and submodule layouts, and they cannot read .git/config or /var/lib/pullfrog/. Pullfrog’s own file-upload tool only uploads regular files from the repository or a temp directory.

​
Commands an agent starts itself

In CI on Linux, unless shell is enabled, everything Pullfrog starts outside the sandbox, the agent process included, only finds commands in directories that only root can write, plus its own node, npm and npx. This stops anything installed from the agent’s shell from running outside the sandbox. Shell commands keep your full PATH, so installs work as usual. A local MCP server or formatter configured in your repo starts from the agent process. If its command lives somewhere else, such as uvx in ~/.local/bin, give the command as an absolute path. OpenCode’s file tools refuse a path that a symbolic link leads outside the repository and the temp directories. Use the shell tool for a file reached that way.

​
Self-hosted runners

Pullfrog runs on self-hosted runners. The shell sandbox needs CAP_SYS_ADMIN, which runner kinds expose differently:
  • VM and bare-metal runners work with no setup. Pullfrog uses passwordless sudo to create the namespace, as it does on GitHub-hosted runners.
  • Containerized and Kubernetes runners, such as actions-runner-controller, usually have neither CAP_SYS_ADMIN nor sudo. Pullfrog first creates an unprivileged user namespace, which grants the capability inside that namespace only. A stock runner pod needs no configuration.
Pullfrog only uses sudo, env, unshare, setpriv and bash from a PATH directory that root owns and no other user can write, because anything a shell command can write it could replace. If unshare or setpriv lives only in a user-writable directory, such as a Homebrew prefix, shell commands stay off for that run and the log says why. /usr/bin and NixOS’s /run/wrappers/bin and /run/current-system/sw/bin qualify. Subscription credentials need passwordless sudo to set up /var/lib/pullfrog/. On a runner without it, Pullfrog skips them and uses an API key if one is available.

​
What the container fallback guarantees

A shell command still cannot read another process’s environment variables, reach Pullfrog-managed files, undo the filesystem protections or signal the Pullfrog process. The kernel blocks cross-namespace environment reads on its own. One thing differs: inside a user namespace the container’s /proc cannot be replaced, so a shell command can see which processes run and their command lines. Pullfrog never puts credentials on a command line.

​
When it does not engage

The fallback needs a pod that may create user namespaces and a node kernel that permits them. It does not engage in these cases:
  • Pod seccomp. A pod hardened to seccompProfile: RuntimeDefault, which the Pod Security Standards baseline and restricted profiles require, cannot create user namespaces. Stock runner pods are unaffected, because the ARC chart sets no securityContext by default.
  • Root pod without CAP_SETFCAP. A container that runs as root needs this capability to map user IDs. Pods that run as a non-root user, the ARC default, are unaffected.
  • Node kernel. Most distributions permit unprivileged user namespaces, including Debian, Ubuntu 22.04 and earlier, RHEL 8 and 9, Amazon Linux 2023 and GKE COS. Ubuntu 23.10 and 24.04 restrict them by default (kernel.apparmor_restrict_unprivileged_userns), and RHEL and CentOS 7, Bottlerocket and Talos disable them (user.max_user_namespaces=0).
If no method provides isolation, the run continues without a shell or the gh tool, and the log names these causes. The agent can still read and edit files, commit, push, review and comment. It is told why it has no shell, and says so when that leaves a result unverified, such as a fix whose tests it could not run. Pullfrog never runs shell commands unsandboxed in CI.

​
Short-lived tokens

Pullfrog uses GitHub OIDC to get installation tokens that are scoped to the repository and expire on their own. Pullfrog also revokes them at the end of each run. The git token is never in the agent’s environment or in .git/config, and the .git directory is read-only to shell commands. Git authenticates through ASKPASS: a localhost helper hands git a code that is valid only for one operation and revokes it when the operation ends, and it answers only for GitHub, so git configuration that points a fetch or push at another host gets no credential. If anything replays a code after that, Pullfrog revokes the GitHub token at once.

​
Permission checks

Pullfrog checks the GitHub permission of the person who triggered each run. Users with write, maintain or admin access are collaborators; everyone else is a non-collaborator. Repo admins decide which non-collaborator events start a run:
Event from a non-collaboratorDefault
MentionOff
New pull request (review)On
New issueOn
A run triggered by a non-collaborator always uses the restricted shell, with a scrubbed environment, and cannot push code, however the repository is configured: its git token is read-only. A review bot on a pull request that Pullfrog opened counts as the repository’s own automation, so Pullfrog still pushes fixes for its review.

​
Signed commits

Signed commits are free on personal accounts and included in the Organization plan. With the setting on, Pullfrog creates commits through the GitHub API, and GitHub signs them with its own key. They show as Verified and satisfy a “require signed commits” branch protection rule, which otherwise rejects every push from an agent. Turn it on per repository with Signed commits in the Security card. Pull requests from forks and content tracked by Git LFS fall back to ordinary unsigned commits.

​
Code pushes

Repo admins set Code pushes in the repo console’s Security card. The setting applies to the whole repository, and an explicit push: input in the workflow overrides it.
SettingValueThe agent can
No code pushesdisabledComment and review only. No pushes and no signed commits.
Feature branches onlyrestrictedPush feature branches and create signed commits. No pushes to the default branch, no branch deletion, no tag pushes.
Full push accessenabledPush anywhere GitHub branch protection allows. This is the default.
Whatever the setting, the agent can never delete the default branch. On cross-repo runs, it cannot push to the default branch of any repository other than the one that triggered the run. Use GitHub branch protection rules to protect other branches on the server side.
These settings control what the agent publishes, not local edits or local commits. They apply to the Pullfrog runtime, and a modified workflow can bypass them. A custom GH_TOKEN keeps the permissions its owner granted; Pullfrog does not narrow it.
You are visitor 01402503 — GeezerVison does not store the pages you look up.
Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required.