# Pr Publication Safety

> Use when changing PR body rendering, home-path redaction, artifact path publication, or pipeline-attestation markers.

- Skill: `mvanhorn/pr-publication-safety` (Agent Skill)
- Install (CLI): `npx skillmds@latest add mvanhorn/pr-publication-safety`
- Raw SKILL.md: https://api.skillmd.com/api/skills/mvanhorn/pr-publication-safety/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: DevOps & Infra
- Author: mvanhorn (https://skillmd.com/u/mvanhorn)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/mvanhorn/pr-publication-safety

---


**Home-Path Redaction in Published PR Content (security)**

- `internal/safepath` is the one owner of home-directory redaction, the path analogue of `internal/safeurl`. `RedactText` rewrites the process's own home plus `/home/<user>`, `/Users/<user>`, and `C:\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 of `filepath.IsAbs`/`VolumeName` and of any reliance on `filepath.Clean`'s separator normalisation: those answer for the build platform, and on Windows `IsAbs` discards the POSIX-rooted `HOME` that Git Bash, MSYS2, and Cygwin set - silently disabling redaction instead of failing. Regression: `TestUsableHomeCandidate_AcceptsBothPlatformSpellings`, `TestHomeCandidates_AreSeparatorSpellingIndependent`.
- `PRStep.buildPRContent` is the single render boundary: it drafts through `draftPRContent` and returns `redactPRContent(content)`, and `Execute` publishes exactly that. Every source that can reach a PR body - agent prose, extracted intent, findings, fix summaries, step errors, artifact `path`, artifact captions, and captured output embedded from evidence files - is covered there, so a new rendering path cannot reintroduce the leak. Redaction runs after every length cap, which is only safe because the placeholder is never longer than the path it replaces.
- The `artifacts[].path` description in `testFindingsSchema` (`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 the `pr.go` boundary's job; the schema only stops soliciting paths from elsewhere on the machine. Regressions: `TestTestFindingsSchema_DoesNotSolicitAbsolutePaths`, `TestTestFindingsSchema_KeepsEvidenceDirectoryPathsReportable`.
- Two other public surfaces deliberately do NOT share this rendering and are not covered: agent-authored commit subjects (`commitAgentFixes` -> `Commit.RenderFixMessage`), which reach the remote through Push, and the opt-in evidence branch (`test.evidence.store_in_repo`), which copies artifact files verbatim. Keep the `internal/safepath` package 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.
- Neutralize at the assembly choke point (`appendGeneratedSectionsToCleanBodyWithinLimit` plus the two intent paths), never per render path. `pipelineMD` alone carries the real marker and is left intact; `BuildPipelineSummaryFor` neutralizes its own step-detail blocks, which quote agent text. A first attempt put this in `escapePipelineFoldMarkers` - per-render-path - and shipped three live foreign markers to #831 anyway. Regressions: `TestPRStep_ForeignAttestationsInEveryComponentDoNotShadowTheRealOne` (all components at once), plus the per-component guards in `pr_test.go`.
- Regressions: `internal/safepath/redact_test.go`, `internal/pipeline/steps/pr_homepath_test.go`.

