Skip to content

Safe-outputs sanitizer breaks Slack mrkdwn links (<url|label>) in custom safe-job string inputs #47643

Description

@Calidus

Summary

The safe-outputs sanitizer unconditionally rewrites Slack mrkdwn links of the form <url|label> in the string inputs of custom safe-output jobs (safe-outputs.jobs.*). convertXmlTags in sanitize_content_core.cjs treats the link as an unknown XML tag and converts the angle brackets to parentheses, which breaks the link once it reaches Slack. There is currently no way to opt out per input.

Reproduction

  1. Define a custom safe-output job that posts to Slack (e.g. wrapping slackapi/slack-github-action with chat.postMessage), with a text: { type: string } input.

  2. Have the agent call the generated safe-output tool with valid Slack mrkdwn:

    Tracking issue: <https://github.com/octo-org/octo-repo/issues/123|Build failure — Build #456>
    
  3. After the "Ingest agent output" step, agent_output.json contains:

    Tracking issue: (https://github.com/octo-org/octo-repo/issues/123|Build failure — Build #456)
    
  4. When that text is posted, Slack auto-linkifies the now-bare URL. The auto-detected URL runs to the first whitespace, so it swallows |Build, and Slack percent-encodes the pipe (its own link delimiter). The message renders as a broken link:

    Tracking issue: (https://github.com/octo-org/octo-repo/issues/123%7CBuild failure — Build #456)
    

Note the domain here is github.com, which is on the default allowed-domains list — this is not URL redaction. Step 3 is reproducible by calling sanitizeContent from gh-aw-actions directly on the string in step 2 (verified at github/gh-aw-actions@5c2fe86 / v0.79.6, and the relevant code is unchanged on current main).

Root cause

  • collect_ndjson_output.cjsvalidateFieldWithInputSchema runs sanitizeContent() on every string input of a custom safe-output job.
  • sanitizeContentCoreconvertXmlTags rewrites any <...> whose "tag name" is not on the allowed HTML tag list to (...). A Slack link <https://…|label> parses as tag name https, so it is rewritten.

The sanitization is understandable as prompt-injection/XSS defense for content destined for GitHub issues/PRs, but custom safe-output jobs deliver content to arbitrary sinks with their own syntaxes (Slack mrkdwn being a common one), where this rewrite is destructive.

Related but distinct: #18829 covers <...> inside code regions; this report is about plain-text content of custom safe-job inputs.

Suggested fixes (any of these would work)

  1. Make convertXmlTags autolink-aware: preserve <https://…> and <https://…|label> spans (angle-bracket autolinks are also valid CommonMark/GFM, so today's behavior mangles legitimate markdown autolinks too). The inner URL would still be subject to the existing protocol/domain filtering.
  2. Per-input opt-out on custom safe-job input schemas, e.g. sanitize: false or a format: slack-mrkdwn hint, so component authors can accept the trade-off explicitly for non-GitHub sinks.
  3. At minimum, document the limitation so component authors know to convert markdown links to mrkdwn after ingestion, inside the safe-output job itself.

Environment

  • gh-aw compiler v0.79.6 (lock file compiler_version: v0.79.6; runtime scripts from github/gh-aw-actions v0.79.6, SHA 5c2fe865)
  • Custom safe-output job under safe-outputs.jobs.* posting via slackapi/slack-github-action@v3.0.1

Metadata

Metadata

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions