Repository navigation
Feature: Content redaction for public-facing outputs #34906
Description
Feature: Content redaction for public-facing outputs
Motivation
Agent output posted as PR/issue comments, reviews, and discussions is publicly visible. Corporate policies typically require:
- Inclusive language — avoiding gendered, racially insensitive, or discriminatory phrasing
- PII protection — no names, emails, or internal identifiers leaking into public output
- Security disclosure discipline — an agent that discovers "SQL injection in
auth.go" must not post that publicly before the vulnerability is fixed - Corporate communication standards — brand voice, legal disclaimers, no internal codenames
Today the only mechanism is embedding policy in the agent's main prompt. This is unreliable:
- Context overflow — instructions get lost when the LLM's context is compacted or truncated
- No guarantee of execution — the primary LLM can skip the check under prompt pressure or competing instructions
- Shared context — the agent has full access to the vulnerability details it's supposed to suppress, making accidental disclosure a single token-generation error away
Vs. threat detection: Threat detection is a pass/fail gate (block or allow). Content redaction rewrites. "I found a SQL injection in auth.go" becomes "I found a potential issue in auth.go that should be reviewed privately."
Proposal
A content redaction step: a platform-enforced job in the safe-outputs pipeline that the compiler wires into the dependency chain agent → detection → content_redaction → safe_outputs, so the primary agent cannot bypass it. A fresh-context subagent — no tools, no repo access, no MCP — reviews all text-bearing output items, seeing only the text and the policy. It rewrites non-compliant text, or blocks items that cannot be made compliant.
Configuration
The agent field is a list of policy sources — URLs (fetched at runtime via curl), repo-relative paths (read from checkout), or inline strings — concatenated in order into the redaction agent's system prompt.
Full schema (all fields)
safe-outputs:
add-comment:
max: 3
content-redaction:
agent: # required — string or string[]
- "https://corp.example.com/policy.md" # URL: fetched at runtime via curl
- ".github/policies/redaction.md" # path: read from repo checkout
- "Never disclose CVE IDs before fix" # inline: literal prompt text
model: "gpt-4o-mini" # optional — override redaction model
on-failure: block # optional — "block" (default) | "warn"
scope: # optional — defaults to all text-bearing types
- add-comment
- create-issue
runs-on: ubuntu-latest # optional — runner override
continue-on-error: false # optional — defaults to falseMinimal — single inline policy
safe-outputs:
add-comment:
max: 1
content-redaction:
agent: "Do not disclose security vulnerabilities in public comments"Corporate URL + cost-effective model
safe-outputs:
add-comment:
max: 5
content-redaction:
agent: "https://corp.example.com/ai-content-policy.md"
model: "gpt-4o-mini"Array shorthand (the list is the agent)
safe-outputs:
add-comment:
max: 1
content-redaction:
- "https://corp.example.com/content-policy.md"
- ".github/policies/inclusive-language.md"
- "Never mention internal project codenames"Conditional — runtime toggle with full config
safe-outputs:
add-comment:
max: 3
content-redaction:
enabled: "${{ inputs.enable-content-redaction }}"
agent:
- "https://corp.example.com/content-policy.md"
- ".github/policies/inclusive-language.md"
model: "gpt-4o-mini"
on-failure: blockJob is always compiled in but skipped at runtime when inputs.enable-content-redaction is falsy.
Implementation notes (for implementer)
Follows the threat-detection pattern with key differences below.
Pipeline position & job wiring
- Slots between detection and safe_outputs:
activation → agent → detection → content_redaction → safe_outputs → conclusion - Built in
compiler_safe_output_jobs.go→buildSafeOutputsJobs() content_redaction.needs:=agent,activation, and (if enabled)detection;safe_outputs.needs:conditionally includescontent_redaction
Fail-closed by default
Opposite of threat detection (continue-on-error: true): if the redaction agent crashes/times out, safe_outputs does not run. Controlled via on-failure: block|warn; continue-on-error: true is an alias for on-failure: warn.
Sandboxing, engine & model
- Redaction
WorkflowDatabuilt withTools: map[string]any{},SafeOutputs: nil, no MCP, no bash;Agent,PermissionMode,MaxTurnsfrom main engine config explicitly excluded - Inherits workflow's engine provider (copilot/claude/codex) and
APITarget(GitHub Enterprise) modeloverrides for cost; falls back to engine's default detection model
Policy source assembly
Resolved at Actions runtime, not compile time:
- URLs: Go job builder emits
curlsteps downloading to/tmp/gh-aw/content-redaction/policies/policy_0.md,policy_1.md, … HTTPS-only validation at compile time; download at runtime so policies update centrally without recompile. - Paths: Checkout step runs if any source is a repo-relative path; JS assembler reads from
$GITHUB_WORKSPACE/<path>. - Inline: Passed via
CONTENT_REDACTION_AGENTenv var as{type: "inline", value: "..."}.
assemble_redaction_prompt.cjs concatenates all sources into /tmp/gh-aw/content-redaction/redaction_prompt.txt.
Agent output format
Structured CONTENT_REDACTION_RESULT block with per-item verdicts: pass, rewrite (replacement text + reason), or block (reason). Parsed by parse_content_redaction_results.cjs → redacted_agent_output.json, which downstream safe_outputs reads instead of the original agent output when redaction succeeded.
Scope filtering
Defaults to all 12 text-bearing safe-output types (comments, issues, PRs, discussions, reviews, project updates, memory). scope restricts to specific types; values use underscore form (add_comment, not add-comment) — validated at compile time.
Conditional compilation
When enabled: "${{ ... }}" is set, compiler always emits content_redaction with an if: condition. A redaction_guard step evaluates the expression at runtime and gates all downstream steps (curl fetch, prompt assembly, engine execution, results parsing).
Feature flag & error handling
Gated behind content-redaction feature flag during development:
features:
content-redaction: trueResults parser uses ADR-29031 resilience: outer try/catch ensures outputs always set; failure reasons categorized (agent_failure, parse_error, no_content) and surfaced as job outputs for downstream conditions.
Key files
| Area | File |
|---|---|
| Config type & parsing | pkg/workflow/content_redaction.go |
| Job compilation | pkg/workflow/content_redaction_job.go |
| Compile-time validation | pkg/workflow/content_redaction_validation.go |
| Prompt assembly (JS) | actions/setup/js/assemble_redaction_prompt.cjs |
| Results parser (JS) | actions/setup/js/parse_content_redaction_results.cjs |
| JSON schema | pkg/parser/schemas/main_workflow_schema.json |
| Constants | pkg/constants/constants.go |
| Fixtures | pkg/cli/workflows/test-copilot-content-redaction-*.md |
Activity
pelikhan commentedon Oct 7, 2026
Status (2026-10-07; Needs progress check): The 2026-05-26 proposal calls for a separate content-redaction step before public safe outputs, with policy sources and a block-on-failure option. No implementation progress is documented here; check whether this design has been adopted or needs further review.
pelikhan commentedon Oct 16, 2026
Current-state refresher — 2026-10-16
Verdict: Still relevant; the proposed enforced rewriting stage has not landed on current
main. Reviewed the full proposal/discussion and0d15bd1649f6.There was an implementation attempt: #44501 added
content-redactionconfiguration and a separate policy-driven rewrite/block job, but it was closed without merging. It should not be counted as shipped support.The current safe-output job builder creates threat detection and then primary safe-output jobs; it does not insert the requested content-redaction stage. Current safe-output configuration has URL/data sanitization controls, but those are not a fresh-context policy-based rewriting agent.
Remaining: decide whether to revive the separate-stage design, then implement policy loading, scoped rewrites/blocks, fail-closed dependency gating, and tests proving outputs cannot bypass it. Disclosure footers, ordinary secret sanitization, and threat detection do not satisfy that acceptance criterion. Static inspection only; no runtime redaction guarantee is claimed.
pelikhan commentedon Oct 22, 2026
2026-10-22 assessment: still relevant; recent redaction merges do not implement the proposed policy-rewriting stage.
Since the previous review, #70032 merged provenance-aware report AST/coarse-redaction helpers and #70875 merged successful-secret-redaction guards for agent artifact uploads. These are useful controls, but neither supplies a fresh-context, policy-driven rewrite/block job across public text outputs. #70032 explicitly identifies its controls as trusted runtime APIs, not new workflow frontmatter.
On current
main, the job builder still creates detection followed by safe-output jobs; the configuration has nocontent-redactionfield. The implementation proposal #44501 remains closed unmerged.Next action: review/revive the separate-stage design, using current provenance infrastructure where appropriate, and specify policy-source loading, tool-free execution, per-item rewrite/block scope, and fail-closed dependency gating. Acceptance needs regressions proving every selected text-bearing output uses the reviewed replacement and cannot publish after redactor failure. Secret masking, disclosure footers, and pass/fail threat detection alone do not meet that contract. Source/history assessment only.