Skip to content

Feature: Content redaction for public-facing outputs #34906

Open

Description

@T-Gro

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:

  1. Context overflow — instructions get lost when the LLM's context is compacted or truncated
  2. No guarantee of execution — the primary LLM can skip the check under prompt pressure or competing instructions
  3. 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 false

Minimal — 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: block

Job 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 includes content_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 WorkflowData built with Tools: map[string]any{}, SafeOutputs: nil, no MCP, no bash; Agent, PermissionMode, MaxTurns from main engine config explicitly excluded
  • Inherits workflow's engine provider (copilot/claude/codex) and APITarget (GitHub Enterprise)
  • model overrides 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 curl steps 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_AGENT env 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: true

Results 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

  1. locked and limited conversation to collaborators on May 26, 2026
  2. unlocked this conversation on May 26, 2026
  3. pelikhan commented on Oct 7, 2026

    @pelikhan
    Collaborator

    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.

  4. pelikhan commented on Oct 16, 2026

    @pelikhan
    Collaborator

    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 and 0d15bd1649f6.

    There was an implementation attempt: #44501 added content-redaction configuration 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.

  5. pelikhan commented on Oct 22, 2026

    @pelikhan
    Collaborator

    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 no content-redaction field. 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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions