Skip to content

fix(redaction): catch ghs_ JWT-format app tokens - #18

Merged
jonatw merged 1 commit into
mainfrom
fix/redaction-ghs-jwt
Jul 25, 2026
Merged

fix(redaction): catch ghs_ JWT-format app tokens#18
jonatw merged 1 commit into
mainfrom
fix/redaction-ghs-jwt

Conversation

@jonatw-eagle

@jonatw-eagle jonatw-eagle Bot commented Jul 25, 2026

Copy link
Copy Markdown
Contributor

What

Adds a redaction rule for the new ghs_-prefixed JWT format that GitHub is rolling out for App installation tokens and Actions GITHUB_TOKEN. Format change only — scopes and permissions are unchanged.

Why — the existing rule goes blind

Current named rule:

"GitHub OAuth/app token":  /\bgh[ousr]_[A-Za-z0-9]{36}\b/,

It requires exactly 36 alphanumerics and a word boundary. The new token has dots and is ~10x longer, so it does not match.

Evidence (not speculation)

Minted both formats against this app's own installation using the temporary X-GitHub-Stateless-S2S-Token override header from the changelog:

mode format length dots permissions
disabled ghs_ opaque 40 0 contents/PR/issues = write
enabled ghs_ JWT 383 2 identical

Ran both regexes against a real new-format token:

existing rule  gh[ousr]_{36}   -> match: False   <-- the gap
proposed rule  ghs_ JWT        -> match: True
entropy backstop              -> match: True (segments 48/247/86, Shannon 4.77/5.52/5.44, all >= 4.2)

So this was a defence-in-depth gap, not an open hole — the entropy backstop already catches it. But a generic high-entropy token (383 chars) report is much weaker signal for a human than a named GitHub app token (JWT) hit.

Also confirmed the new format works end-to-end against the API (GET /repos/... -> 200), so no tooling changes are needed anywhere else.

Risk

Fail-safe. The rule only ever adds matches; worst case is one extra redaction. The old rule stays — both formats coexist during rollout.

Ref: https://github.blog/changelog/2026-05-15-github-app-installation-tokens-per-request-override-header/

GitHub is migrating App installation tokens (and Actions GITHUB_TOKEN)
from short opaque strings to a ghs_-prefixed JWT. Format only -- scopes
and permissions are unchanged.

Verified empirically against this app's own installation, minting both
formats via the temporary X-GitHub-Stateless-S2S-Token override header:

  disabled -> ghs_ opaque,  40 chars, 0 dots
  enabled  -> ghs_ JWT,    383 chars, 2 dots  (segments 48/247/86)

The existing named rule /\bgh[ousr]_[A-Za-z0-9]{36}\b/ does NOT match the
new format (wrong length, contains dots, no word boundary). The entropy
backstop still catches it (segment Shannon 4.77/5.52/5.44, all >= 4.2),
so this was a defence-in-depth gap, not an open hole -- but a named,
labelled hit is what makes a scanner report actionable.

Adds a JWT-shaped rule alongside the existing one; both formats coexist
during rollout. Fail-safe: worst case is an extra redaction.

Ref: https://github.blog/changelog/2026-05-15-github-app-installation-tokens-per-request-override-header/
@jonatw-eagle

jonatw-eagle Bot commented Jul 25, 2026

Copy link
Copy Markdown
Contributor Author

Review: APPROVE ✅(附一個 non-blocking 觀察)

一行 deny-list 新增,改動只有 scripts/redaction-scan.js,純加法、不動既有 pattern、不放寬任何東西。

我實測跑過這條 regex(不是只看 CI 綠) —— CI 的 redaction 綠只證明「掃全 repo 沒中」,不證明新 pattern 抓得到目標,所以自己驗:

樣本 新 pattern gh[ousr]_{36}
ghs_eyJhbGciOiJIUzI1NiJ9.eyJ...J9.abc-DEF_123(JWT 形) ✅ 中 ❌ 漏 ← 本 PR 要補的洞
ghs_ + 36 字元(classic) ✅ 中(既有那條顧著)

→ 覆蓋面確實補上、且與既有那條互補不重疊。ReDoS 探測:20k 字元無點輸入 0.07ms,三個 + 被字面 . 隔開,沒有 catastrophic backtracking。

Non-blocking:false positive 方向
[A-Za-z0-9_-]+ 無上界又沒收尾 \b,散文型 ghs_docs.example.comghs_token.json.bak 也會中 → HARD block。失敗方向是安全的(擋住而非漏出),而且 docs/redaction.md:104-107 已經把「寫 secret pattern 的文件會自己踩到 pin」記成明文教訓,屬已知取捨。真被誤擋再收斂即可,不擋這 PR。

merge:flightdeck 慣例人閘在 <@615684814736982038>,我只放行不按鍵。


(注:本 PR 由 jonatw-eagle[bot] 開,同一 App 無法 approve 自己的 PR → 以 comment 形式留結論。mergeable_state=blocked 需人類 review 放行。)

@jonatw
jonatw merged commit 5f068ce into main Jul 25, 2026
1 check passed
@jonatw
jonatw deleted the fix/redaction-ghs-jwt branch July 25, 2026 23:14
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.

1 participant