Imported from kunchenguid/no-mistakes (
.agents/skills/pr-publication-safety/SKILL.md). Install upstream withnpx skills add kunchenguid/no-mistakes --skill pr-publication-safety. Copyright stays with the author.
Home-Path Redaction in Published PR Content (security)
internal/safepathis the one owner of home-directory redaction, the path analogue ofinternal/safeurl.RedactTextrewrites the process's own home plus/home/<user>,/Users/<user>, andC:\Users\<user>to~, unconditionally and for every occurrence. Add new shapes there rather than scrubbing paths at a call site. Candidate resolution must stay free offilepath.IsAbs/VolumeNameand of any reliance onfilepath.Clean's separator normalisation: those answer for the build platform, and on WindowsIsAbsdiscards the POSIX-rootedHOMEthat Git Bash, MSYS2, and Cygwin set - silently disabling redaction instead of failing. Regression:TestUsableHomeCandidate_AcceptsBothPlatformSpellings,TestHomeCandidates_AreSeparatorSpellingIndependent.redactPRContentinpr.goowns the publication redaction boundary:safepath.RedactWorktrees(the run worktreesctx.WorkDir, then any run worktree undernmWorktreesRoot()) beforesafepath.RedactText, the same order as evidence publication, since home redaction would stop the worktree roots matching.PRStep.buildPRContentuses it for ordinary drafts; template creation and live author-preserving updates use it throughcomposeOwnedPRContentbefore stamping their byte-integrity guard. Every source that can reach a PR body - agent prose, extracted intent, findings, fix summaries, step errors, artifactpath, artifact captions, and captured output embedded from evidence files - is covered there, so a new rendering path cannot reintroduce the leak. Ordinary drafts redact after length caps (the placeholder never grows a path); template composition redacts before its integrity guard and fail-closed size check.pr_ownership.goowns that marked-appendix contract: never feed live author text through heading-based stripping/clamping, and keeprebindOwnedPRAttestationin the pre-push restamp path so its integrity guard remains valid. Regressions:pr_template_test.go,pr_ownership_test.go.- The
artifacts[].pathdescription intestFindingsSchema(common.go) must not solicit absolute paths, and must not forbid them either. The renderer's allowlist is the worktree or the run's evidence directory and a path under neither is dropped, while the evidence directory defaults under the operator's home - so soliciting more just re-supplies what the boundary has to strip, and a blanket "never report a home directory path" clause makes an obedient agent drop its own evidence. Publication safety is thepr.goboundary's job; the schema only stops soliciting paths from elsewhere on the machine. Regressions:TestTestFindingsSchema_DoesNotSolicitAbsolutePaths,TestTestFindingsSchema_KeepsEvidenceDirectoryPathsReportable. - The opt-in evidence branch (
test.evidence.store_in_repo) is the second boundary:evidence.Publishruns its text files through the same worktree and home redaction (hashEvidenceFile, owned by thetest-evidence-storageskill). One other public surface deliberately does NOT share this rendering and is not covered: agent-authored commit subjects (commitAgentFixes->Commit.RenderFixMessage) and their trailers (withCommitTrailers->Commit.RenderTrailers, whose agent and model values are reduced to one safe token), which reach the remote through Push. Keep theinternal/safepathpackage doc honest about that scope. - The PR body must contain exactly ONE live pipeline-attestation marker, the run's own.
require-no-mistakes(.github/actions/require-no-mistakes/verify.py) binds the FIRST marker in the RAW body to the PR head, so a foreign copy placed earlier fails a PR the pipeline did produce - and a code fence is no defense, because that scan is raw text. Step agents embed foreign markers routinely, by capturing a generated PR body as evidence. - A CI repair that publishes a new head rewrites only that live marker's
head_shain the current PR body (restampPublishedAttestation) and does not send a title. It never inserts a marker that was not already there. Hosts without a PR content reader skip the restamp instead of failing the push. Regressions:TestCIStep_PublishRepairRebindsAttestationAcrossRepairPushes,TestCIStep_PublishRepairDoesNotMintAttestation,TestCIStep_PublishRepairSkipsRestampWithoutReader,TestRestampPRAttestation_PreservesContentEditedWhilePreparingRewrite,TestUpdatePROmitsTitleWhenEmpty. - Neutralize at the assembly choke point (
appendGeneratedSectionsToCleanBodyWithinLimitplus the two intent paths), never per render path.pipelineMDalone carries the real marker and is left intact;BuildPipelineSummaryForneutralizes its own step-detail blocks, which quote agent text. A first attempt put this inescapePipelineFoldMarkers- per-render-path - and shipped three live foreign markers to #831 anyway. Regressions:TestPRStep_ForeignAttestationsInEveryComponentDoNotShadowTheRealOne(all components at once), plus the per-component guards inpr_test.go. redactPRContentalso runsneutralizePRControls(pr_controls.go): published bytes must be a fixed point ofgh pr view --json, whose sanitizer rewrites C0/C1 controls and the literal text\u00XXof them to caret notation, or the post-create body check and the appendix digest fail with a false "edited" refusal (#1435, #1454).composeOwnedPRContentneutralizes the appendix BEFOREescapePRAppendixMarkersso removing a byte cannot splice a marker together after the escape ran.parsePROwnedBodystill accepts an appendix whose digest matches only afterrestoreCaretControls(PRs stamped by older versions).internal/pipeline/fakecli/ghsanitizeis the shared port of that sanitizer; do not hand-write another. Regressions:internal/pipeline/steps/pr_controls_test.go.- Regressions:
internal/safepath/redact_test.go,internal/pipeline/steps/pr_homepath_test.go.
Pre-Push Pipeline Attestation
publishRunHeadmust write an existing PR attestation for the proposed head before pushing it on every supported provider with raw content reads.docs/src/content/docs/reference/repo-config.md(pr.template) owns provider-specific ownership/budget and visible Bitbucket metadata caveats. The protocol has single-publisher scope because the daemon enforces one active run per repository branch; do not add cross-publisher coordination without a separate requirement. Detailed behavior is owned bydocs/src/content/docs/reference/pipeline-steps.md; local rationale lives inattestHeadBeforePushandpublishRunHead. Regressions:TestPushStep_AttestsHeadBeforePush,TestPushStep_AttestationWriteFailureAbortsBeforePush,TestPushStep_UnavailableSCMLeavesStaleAttestationFailingClosed,TestPushStep_PushFailureAfterAttestationLeavesBodyAhead, andTestCIStep_PublishRepairRebindsAttestationAcrossRepairPushes.
