Skip to content

ci(security-gate): allow escape sequences when fetching changed .py blobs - #126

Merged
tarekziade merged 2 commits into
huggingface:mainfrom
iam-kira:fix/security-gate-escape-sequences
Oct 8, 2026
Merged

tarekziade merged 2 commits into
huggingface:mainfrom
iam-kira:fix/security-gate-escape-sequences

Conversation

@iam-kira

@iam-kira iam-kira commented Sep 20, 2026 •

Copy link
Copy Markdown

Fixes #125.

What

get-changed-files aborts on any fork PR that touches a .py file containing an ANSI escape byte, whether or not the PR's own diff introduces one.

The fetch step runs gh api -H "Accept: application/vnd.github.raw" ... > /tmp/pr_py/$f. gh refuses to emit a body containing terminal escape sequences unless --allow-escape-sequences is passed:

the response contains terminal escape sequences; pass --allow-escape-sequences to output it anyway

It exits 1 having written nothing, the step is bash -e, so the job fails, approve-pr-ci is skipped, and the PR's CI is never approved.

Live instance

huggingface/transformers #48543 is approved and cannot merge. tests/utils/test_modeling_utils.py carries a literal ESC on main at line 1754, in a test asserting on colourised output. That line predates the PR and the diff does not touch it.

Since the caller gates on author_association, this only ever fires for outside contributors — a maintainer editing the same file never sees it.

Fix

One flag. Safe here because the body is redirected to a file and parsed by Bandit's AST, never rendered to a terminal — which is the case the protection exists for.

Verified against the real blob:

without the flag:  exit=1  bytes=0
with the flag:     exit=0  bytes=189664

Deliberately left out

The issue floated a [ -s ... ] || exit 1 guard so an empty blob fails loudly instead of being scanned clean by Bandit. I did not include it: an empty __init__.py is a perfectly ordinary changed file, and the guard would reject legitimate PRs. If you want that hole closed, it needs to compare against the blob size the API reports rather than testing for non-empty — happy to do it in a separate PR.

🤖 Generated with Claude Code

iam-kira and others added 2 commits September 20, 2026 20:12
…lobs

gh refuses to emit a response body containing terminal escape sequences
unless --allow-escape-sequences is passed. It exits 1 having written
nothing, and the step runs under `bash -e`, so get-changed-files aborts
and approve-pr-ci never runs.

A .py file may legitimately contain an ANSI escape byte: transformers has
one in tests/utils/test_modeling_utils.py, in a test asserting on
colourised output. Any fork PR touching that file is blocked, even when
its own diff is nowhere near the line.

The flag is safe here — the body is redirected to a file and parsed by
Bandit's AST, never rendered to a terminal.
@tarekziade

Copy link
Copy Markdown
Collaborator

@askserge review this PR. focus on security

@tarekziade
tarekziade self-requested a review October 8, 2026 09:28

@sergereview sergereview Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤗 Serge says:

Verdict

Clean, minimal, well-reasoned security fix; adding --allow-escape-sequences to the gh api raw-blob fetch is safe and does not weaken existing controls.

Security

  • Flag is correctly scoped: gh's escape-sequence guard prevents TTY injection, but output is redirected to > "/tmp/pr_py/$f" and parsed by Bandit's AST, never rendered, so the flag only enables the file write.
  • No other control is weakened: allowlist (rules 1–3) and protected-path checks run before the fetch; fetch still reads via contents API at head SHA, not a fork checkout, preserving "pwn request" hardening.
  • No new attack surface: fetched content is already attacker-controlled fork PR head blob; escape sequences in Python source are inert bytes for Bandit's static AST parser.
  • Omitted [ -s ] guard is correct: under bash -eo pipefail, failed gh api aborts the step, preventing a 0-byte file from scanning clean; only genuinely empty files (e.g. __init__.py) are 0-byte. The separate empty-blob-clean hole would need size comparison, not a blocker.

Comment accurately describes behavior and safety rationale; no inline issues.

serge v0.1.0 · model: Qwen/Qwen3.8-27B:novita · 7 LLM turns · 7 tool calls · 356.8s · 66609 in / 16475 out tokens

@tarekziade tarekziade self-assigned this Oct 8, 2026
@tarekziade
tarekziade merged commit 310a9d7 into huggingface:main Oct 8, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Security gate blocks any fork PR touching a .py file that contains an ANSI escape byte

2 participants