終端機逸出序列在日誌中屬於有條件的安全:當抵達日誌的每一個位元組都由已知來源產生並經過你的應用程式驗證時,它是安全的;但一旦未受信任的文字可能帶著原始控制位元組被重新渲染,它就變得不安全了。這個風險與 SGR 參數編號本身無關,後者由 ECMA-48 第五版 所標準化,並與 xterm 控制序列說明文件 廣泛交叉比對。風險來自於把原始的 ESC[...m 控制位元組貼進終端機,而終端機會把它們解讀為指令。一個惡意或單純骯髒的酬載可以在假空白後藏住文字、插入一行覆寫前一行、畫出一個看似可點擊的連結,或在日誌檢視器調整視窗大小很久之後變更主機終端機的標題列。儲存原始位元組、之後再倒進 TTY 的日誌會面臨上述所有問題。較安全的模式是保留兩條路徑:一條給支援終端機的消費者使用的彩色路徑,以及一條用於封存、議題、聊天訊息與共用檔案的已消毒或可見跳脫路徑。ANSI 色彩代碼產生器 同時支援這兩條路徑,它會將序列以字面 \x1b 字串顯示,讓你可以儲存、複製、檢視,卻不會輸出任何控制字元。

are terminal escape sequences safe in logs when using ansi escape codes
are terminal escape sequences safe in logs when using ansi escape codes

為何原始逸出序列會是日誌安全問題

SGR 序列以逸出位元組起頭,後接一個開括號、以分號分隔的十進位參數,以及字母 m。可見的表示法會把逸出位元組寫成 \x1b,以便在不改變網頁外觀的前提下檢視序列;但同一個位元組一旦抵達終端機模擬器,它就不再是可列印文字,而變成一條控制指令。ECMA-48 把這條指令標準化;xterm 與許多其他模擬器實作了它;Windows 虛擬終端機處理程序則加上了一層相當的機制。這條指令在互動式終端機能做的事情遠超過設定顏色:控制序列可以移動游標、清除螢幕、變更標題列、設定捲動區域、切換字元集,或把終端機困在替代畫面模式中。

當日誌儲存原始位元組,而下游工具又把這些位元組倒進 TTY 時,上述每一項能力都會跟著日誌行一起走。能夠注入日誌行的攻擊者因此可以偽造視覺輸出。乾淨的日誌行可能會被某個東西悄悄竄改——例如用假空白藏住原始文字、在下一行加以覆寫、畫出一個看似超連結的底線字,或把終端機標題設成一個令人困惑的字串。純文字日誌在任何實質意義上都不是互動式的,但在開發者用 cat、tail -f、less -R、IDE 主控台,或會解讀控制位元組的聊天用戶端重新打開它的那一刻,它就變成互動式的了。

何時 ANSI 代碼適合放進日誌——何時不適合

這個決定很少是全域性的「要顏色或不要顏色」;而是「哪個接收端會讀取這個位元組流」。支援 ECMA-48、xterm 擴充功能與 Windows 虛擬終端機處理程序的 TTY 能有意義地呈現 SGR 參數,而同一個位元組流在重新導向的管線、JSON 日誌彙整器、CI 構件、GitHub 留言、票證內容或電子郵件訊息中,最好的情況看起來像垃圾,最壞的情況就是一起安全事件。NO_COLOR 慣例保留了使用者與工具透過環境變數關閉色彩的權利,所以任何在輸出原始位元組時不理會 NO_COLOR 的記錄器,對於明確要求純文字的使用者來說也是行為不當。

一條可運作的安全處理規則:

  • 把原始 SGR 位元組保留在單一程式內部,由它寫入已確認的 TTY,並使用 isatty 形式的目的端檢查。
  • 在控制位元組抵達檔案、封存與共用介面之前,先加以去除或以可見方式跳脫。
  • 永遠在彩色執行區段之後附加 ESC[0m,讓後續輸出不會繼承格式設定。產生器在建立前綴時一律會附加重設指令;該重設是一道護欄,並不能證明每個下游消費者都會以同樣方式解讀這個位元組流。
  • 提供純文字或可見跳脫的記錄模式,一旦日誌目的端未知就立即切換過去。

使用 ANSI 色彩代碼產生器安全地建構並檢查 SGR 序列

這是把產生的序列維持在安全邊界內的核心工作流程。每一步都在瀏覽器中執行,因此沒有任何位元組會離開頁面,也不會上傳或在任何殼層中執行。

  1. 開啟 ANSI 色彩代碼產生器,輸入你想要格式化的終端機文字。選擇性設定前景、背景、粗體與底線。介面只公開三種樣式:粗體參數 1、斜體參數 3(為了相容性而保留於邏輯中),以及底線參數 4。不支援的樣式與色彩編號會被拒絕。
  2. 點擊產生。頁面會渲染兩種輸出。一種是可見的跳脫表示法,其中逸出位元組顯示為 \x1b,且參數清楚可讀。另一種是終端機會解讀的原始控制位元組字串。請先閱讀可見的跳脫形式,以確認參數符合你的預期;這才是可以安全貼進日誌檔或議題留言中的形式。
  3. 只把原始序列複製到受信任且支援終端機的環境中:本機來源字串、除錯用的 fixture,或你所掌控的互動式終端機。接著端對端測試重設行為。一個亮紅色前景搭配藍色背景、粗體與底線的樣式會使用參數 1;4;91;44,且該執行區段應永遠以 ESC[0m 結尾,讓下一個提示回到預設值。

所有文字、選擇、產生的位元組、搜尋詞與剪貼簿操作都保留在瀏覽器內。這個頁面沒有終端機模擬器,所以它無法判斷目的端是否支援 ECMA-48、xterm 擴充功能、Windows 虛擬終端機處理程序、NO_COLOR 慣例,或重新導向的非互動式輸出。該確認必須由目的端自行提供。

16 色調色盤的 SGR 參考

色彩名稱(調色盤位置)前景背景亮前景亮背景
黑色304090100
紅色314191101
綠色324292102
黃色334393103
藍色344494104
洋紅354595105
青色364696106
白色374797107

基本前景 30–37 與背景 40–47 是 ECMA-48 中的標準 SGR 參數;亮色擴充 90–97 與 100–107 來自 xterm 控制序列說明文件,並由每個支援擴充前景的常見終端機模擬器所對應。這裡的色彩名稱描述的是調色盤位置,而非保證的 RGB 值。終端機模擬器、主題、使用者設定檔、輔助設定、多工器、遠端環境與應用程式都可能重新對應這些位置,所以同一個參數在不同的終端機中可能呈現為不同的顏色。

日誌與終端機的消毒模式

最安全的處理模式是將彩色輸出路徑與封存輸出路徑分開,並且絕不讓兩條路徑共用原始位元組流。如需更深入了解 SGR 參數格式,以及在輸出前驗證使用者提供的 SGR 字串,請參考指南 ANSI 逸出代碼:SGR 參數格式與安全處理,它從互補的角度檢視同一道邊界。

具體而言,在實際上線程式碼中能發揮作用的模式如下:

  • 在輸出彩色內容之前,以等效於 isatty 的檢查來偵測目的端,並在 NO_COLOR 環境變數設定時去除所有 SGR 參數以示尊重。
  • 去除或消毒任何會流入 TTY 的使用者提供文字。最基本的消毒方式,是把 tab 與換行以外的控制位元組替換為 caret 表示法,或直接移除。
  • 永遠以參數 0 重設來結束彩色執行區段;產生器會自動執行此動作。沒有重設時,每一行跟在色彩標籤之後的輸出都會繼承該格式。
  • 為日誌與聊天提供可見跳脫模式:把逸出位元組替換為 \x1b,並保留可讀的參數清單。可見跳脫模式正是產生器在原始序列旁邊所顯示的內容。

請注意粗體並非可靠的色彩提示:某些較舊的終端機會用粗體(參數 1)來存取亮色調色盤。現代模擬器通常把粗體視為加強亮度,所以在相同色彩中套用粗體,即使參數相同也會產生不同的視覺結果。底線的形狀與顏色也會有差異,因此不要依賴它們承載語意。

在真實終端機中測試序列

可見跳脫檢視只是一項健全性檢查,並非最終測試。請在使用者實際執行的終端機主題與輸出模式下驗證行為。你可以對產生的序列執行的實務測試包括:

  • 將產生的原始序列透過 cat -v 導出,確認逸出位元組以 ^[ 顯示。
  • 在同一行中使用 less -R 以及 less -R -+F 開啟,確認重設能乾淨地結束彩色執行區段。
  • 在 tmux 或 screen 內執行,確認多工器不會把彩色樣式洩漏到下一個提示。
  • 檢查序列在經過 JSON 編碼或 base64 編碼再解碼後是否仍能正確解析;許多序列化器會特別處理逸出位元組。
  • 確認把同一份輸出重新導向到檔案時,會捨棄色彩或以可見方式跳脫,而不是在檔案裡留下原始位元組。

產生器隨附的外部 fixture 會鎖定基本前景與背景配對,並強制要求 16 個不重複的代碼資料列、一個亮色過濾結果,以及一組精確的多樣式原始與跳脫序列。這些 fixture 是用來保護產生器本身;它們並不會驗證任何目的端終端機。若要可靠使用,請產生最小必要的參數集合、檢查可見表示法、只複製到受信任的環境中、重設格式,並在使用者實際執行的終端機主題與輸出模式下驗證行為。兩條路徑——彩色給已確認的 TTY,純文字或可見跳脫給封存——能把原始控制位元組保留在一道界定明確的邊界內。

如果你正在權衡選項,產生 ANSI 色彩代碼時如何避免錯誤 對此有詳細說明。