{"id":4252,"date":"2026-11-11T10:00:00","date_gmt":"2026-11-11T07:00:00","guid":{"rendered":"https:\/\/inferne.com\/blog\/?p=4252"},"modified":"2026-11-11T10:00:00","modified_gmt":"2026-11-11T07:00:00","slug":"pull-request-target-security","status":"publish","type":"post","link":"https:\/\/inferne.com\/blog\/pull-request-target-security\/","title":{"rendered":"pull_request_target Security: 5 Urgent Checks"},"content":{"rendered":"<p><strong>pull_request_target security<\/strong> needs attention before GitHub starts enforcing a new default on December 2. Public repositories that use this GitHub Actions event may see workflows blocked unless an applicable Actions event policy explicitly allows them. The first temporary enforcement window runs on November 9 from 16:00 to 18:00 UTC, followed by another on November 16.<\/p>\n<nav aria-label=\"Table of contents\">\n<ul>\n<li><a href=\"#change\">What GitHub is changing<\/a><\/li>\n<li><a href=\"#risk\">Why the event is risky<\/a><\/li>\n<li><a href=\"#audit\">Five checks before the next brownout<\/a><\/li>\n<li><a href=\"#exceptions\">When an exception is justified<\/a><\/li>\n<li><a href=\"#plan\">A practical response plan<\/a><\/li>\n<\/ul>\n<\/nav>\n<h2 id=\"change\">What GitHub is changing<\/h2>\n<p>GitHub will block <code>pull_request_target<\/code> by default in public repositories from December 2, 2026. The rule is already running in evaluate mode, which means teams can see warnings for runs that would be stopped. During the two brownouts, affected runs will fail instead of merely warning. Private and internal repositories are outside this change, as are GitHub Enterprise Server installations.<\/p>\n<p>The scope matters. This is not a general ban on Actions or on pull request automation. It is a secure default for one event that often carries more privilege than teams realize. A workflow can still use the event when a targeted Actions policy permits it. GitHub has published the schedule and scope in its <a href=\"https:\/\/github.blog\/changelog\/2026-11-02-pull_request_target-blocked-by-default-december-2\/\" target=\"_blank\" rel=\"nofollow noopener\">change announcement<\/a>.<\/p>\n<h2 id=\"risk\">Why pull_request_target security deserves a separate review<\/h2>\n<p>The event runs in the context of the base repository. That can make repository secrets, write permissions, or trusted caches reachable by a workflow started from a pull request. If that workflow checks out code from an untrusted fork and executes it, the trust boundary has already been crossed. Renaming the trigger does not fix the workflow design.<\/p>\n<p>That does not mean every use is unsafe. A workflow might use the event only to label a pull request, leave a comment, or inspect metadata. The review needs to follow the data and code paths. What triggers the workflow? What code runs? Which inputs can a contributor influence? Which credentials and permissions are available at each step?<\/p>\n<figure><img decoding=\"async\" src=\"https:\/\/inferne.com\/blog\/wp-content\/uploads\/2026\/11\/pull-request-target-security-workflow.webp\" alt=\"pull_request_target security workflow review diagram\" \/><figcaption>An abstract view of the review boundary between pull request inputs and privileged automation.<\/figcaption><\/figure>\n<h2 id=\"audit\">Five pull_request_target security checks before the next brownout<\/h2>\n<h3>1. Find every workflow that uses the event<\/h3>\n<p>Search workflow files across public repositories, including templates and reusable workflows. Do not stop at the event declaration. Record the owner, the repository visibility, the purpose, and whether the workflow has run recently. Policy Insights can show runs that the new default would block, which gives the inventory a useful starting point.<\/p>\n<h3>2. Trace what runs after the trigger<\/h3>\n<p>Look for checkout steps, shell commands, scripts, package installation, generated configuration, and artifacts. A pull request title, branch name, changed file, artifact, or external API response can all become an input. The dangerous pattern is simple: privileged workflow context plus code or data that an untrusted contributor controls.<\/p>\n<h3>3. Reduce permissions before discussing an allow rule<\/h3>\n<p>Check the workflow and job-level <code>permissions<\/code> settings. Remove write access that the job does not need. Avoid passing secrets into jobs that only report status. Split a mixed workflow if the public-facing task and the privileged task have different trust needs. Small permission changes are easier to test than a broad exception that lives for years.<\/p>\n<h3>4. Test the real fork path<\/h3>\n<p>A workflow can look harmless when tested from a branch in the same repository and behave differently for a fork. Use a controlled fork scenario and watch the run during a brownout or evaluate period. Confirm who can trigger it, what is checked out, and whether any privileged action occurs. Keep a screenshot or run URL with the decision record so a later maintainer can understand the exception.<\/p>\n<h3>5. Name an owner and a fallback<\/h3>\n<p>Every workflow that will be affected needs an accountable owner before December 2. That owner should know whether the safe answer is to keep the block, change the workflow, or allow a tightly scoped exception. For build, release, or contributor flows, document the manual fallback. A failed Action is manageable. A release process that nobody can explain is not.<\/p>\n<table>\n<thead>\n<tr>\n<th>Workflow purpose<\/th>\n<th>Preferred direction<\/th>\n<th>What to verify<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td>Commenting or labeling<\/td>\n<td>Use the least privileged event and token<\/td>\n<td>No untrusted code runs<\/td>\n<\/tr>\n<tr>\n<td>Testing fork code<\/td>\n<td>Use a pull request workflow with minimal permissions<\/td>\n<td>Secrets stay unavailable<\/td>\n<\/tr>\n<tr>\n<td>Privileged maintenance task<\/td>\n<td>Allow only a named workflow when truly required<\/td>\n<td>Inputs, permissions, and owner are documented<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<h2 id=\"exceptions\">When an exception is justified<\/h2>\n<p>An explicit allow policy is an operational choice, not proof that a workflow is safe. GitHub supports policies at enterprise, organization, and repository levels, with targeting by workflow path and event. That makes a narrow exception possible. It also means a broad policy can quietly undo the protection across many projects.<\/p>\n<p>Start with the narrowest target. Allow the specific workflow that has a documented need, not every workflow in an organization. Keep the default block for workflows that do not need the event. GitHub explains how policies layer across account levels in its <a href=\"https:\/\/docs.github.com\/en\/actions\/how-tos\/administer\/control-workflow-execution\" target=\"_blank\" rel=\"nofollow noopener\">workflow execution protections documentation<\/a>.<\/p>\n<h2 id=\"plan\">A practical response plan for product teams<\/h2>\n<p>Today, create the inventory and check Policy Insights. Before the next brownout, review the workflows that handle public contributions or deployments. During the window, capture failures and decide whether each one needs a redesign or a limited exception. Then retest the flows that protect releases, issue triage, and contributor experience.<\/p>\n<p>Teams building products with public repositories should treat this as a useful forcing function. It separates automation that is merely convenient from automation that has earned privileged access. The same review can expose stale secrets, unnecessary write tokens, and workflow paths nobody owns anymore. For a broader look at secure product delivery, see <a href=\"https:\/\/inferne.com\/blog\/web-application-development\/\">web application development<\/a>. If a workflow review uncovers a deeper platform risk, <a href=\"https:\/\/inferne.com\/#contact\">talk to Inferne about the product and delivery constraints around it<\/a>.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>pull_request_target security needs attention before GitHub starts enforcing a new default on December 2. Public repositories that use this GitHub Actions event may see workflows blocked unless an&hellip;<\/p>\n","protected":false},"author":4,"featured_media":4249,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[312],"tags":[475,484],"class_list":["post-4252","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-web","tag-github","tag-github-actions"],"_links":{"self":[{"href":"https:\/\/inferne.com\/blog\/wp-json\/wp\/v2\/posts\/4252","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/inferne.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/inferne.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/inferne.com\/blog\/wp-json\/wp\/v2\/users\/4"}],"replies":[{"embeddable":true,"href":"https:\/\/inferne.com\/blog\/wp-json\/wp\/v2\/comments?post=4252"}],"version-history":[{"count":1,"href":"https:\/\/inferne.com\/blog\/wp-json\/wp\/v2\/posts\/4252\/revisions"}],"predecessor-version":[{"id":4253,"href":"https:\/\/inferne.com\/blog\/wp-json\/wp\/v2\/posts\/4252\/revisions\/4253"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/inferne.com\/blog\/wp-json\/wp\/v2\/media\/4249"}],"wp:attachment":[{"href":"https:\/\/inferne.com\/blog\/wp-json\/wp\/v2\/media?parent=4252"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/inferne.com\/blog\/wp-json\/wp\/v2\/categories?post=4252"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/inferne.com\/blog\/wp-json\/wp\/v2\/tags?post=4252"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}