# Agent-safe terminals के लिए ANSI escape sequence sanitization

Terminal output भी input है। जैसे ही कोई agent command result, repository file, build log, SSH banner या API response पढ़ सकता है, उस result का हर byte agent की समझ बदलने या पास के terminal के व्यवहार को प्रभावित करने की कोशिश कर सकता है। अपने command ने output बनाया है, इसलिए उसे trustworthy मानना गलती है। Command अक्सर ऐसा data आगे भेजता है, जिसे किसी और ने नियंत्रित किया था।

ANSI escape sequence sanitization उस boundary पर होनी चाहिए, जहां tool output agent context बनता है। यह model को text देने से पहले, human को live transcript दिखाने से पहले और session log को replayable बनाने से पहले होना चाहिए। Model के उन्हें पढ़ लेने के बाद color codes हटाना केवल सबूत साफ करता है।

इसका मतलब यह नहीं कि हर command result को बेकार, sterile blob बना दिया जाए। Agents को उपयोगी diagnostics चाहिए। लक्ष्य है output का semantic content बचाना, लेकिन terminal instructions, cursor movement, clipboard requests, hyperlinks और दूसरे control traffic को trust boundary पार करने से रोकना।

## Terminal transcript untrusted input है

Shell command अपने output का मालिक नहीं होता। `git log` commit messages दिखाता है। Compiler paths और source excerpts दिखाता है। Test runner किसी fixture को दोहरा सकता है। SSH client prompt देने से पहले server banner दिखाता है। Package manager remote registries से names, versions, metadata और error messages पढ़ता है। हर मामले में कोई बाहरी पक्ष उस text को प्रभावित कर सकता है, जो बाद में agent तक पहुंचेगा।

आम failure एक सुविधाजनक तरीके से शुरू होता है: stdout और stderr capture करें, उन्हें जोड़ें और result को agent की conversation में जोड़ दें। इससे output के दो काम हो जाते हैं। वह program result भी बताता है और language model के context में instructions की तरह भी काम करता है। Terminal controls पहला काम अविश्वसनीय बनाते हैं, जबकि prompt-जैसा content दूसरा खतरनाक बनाता है।

मान लें किसी repository में ऐसा file name है जिसमें carriage return और escape sequence है। File list करने वाला command ऐसा display बना सकता है, जो अपना शुरुआती हिस्सा overwrite कर दे। Reviewer को harmless path दिखाई देता है, लेकिन raw byte stream में कुछ और होता है। अगर tool byte boundaries दिखाए बिना या control policy लागू किए बिना उस stream को agent message बना दे, तो agent को अस्पष्ट artifact मिलता है।

Threat model को केवल malicious repository contributor तक सीमित न रखें। Build artifacts, dependency stack traces, network devices, database errors और remote command output भी इसी boundary को पार करते हैं। Compromised server को agent host पर shell access की जरूरत नहीं होती। वह transcript को प्रदूषित करने के लिए बनाया गया banner लौटा सकता है।

दो अलग risks हैं:

- Raw output देखने पर terminal emulator control instructions चला सकता है।
- Agent visible, hidden या reordered text को data के बजाय directions समझ सकता है।

साफ agent transcript दूसरा risk घटाता है। ऐसा viewer, जो terminal controls को कभी interpret न करे, पहला risk घटाता है। Captured output को humans inspect करें तो दोनों जरूरी हैं।

## Terminal controls केवल color नहीं जोड़ते

Terminal control sequences cursor चला सकते हैं, पिछला text मिटा सकते हैं, window title बदल सकते हैं, clickable link बना सकते हैं, clipboard में content रख सकते हैं, terminal से query कर सकते हैं या terminal feature को data भेज सकते हैं। SGR color इसका केवल परिचित हिस्सा है। `ESC[` के बाद digits और `m` हटाने वाला filter colors संभाल लेता है, लेकिन इससे कहीं अधिक सक्षम sequences बची रह सकती हैं।

ECMA-48 control functions और CSI sequences का सामान्य रूप define करता है। CSI आम तौर पर ESC और फिर `[` से शुरू होता है, उसके बाद parameter bytes, intermediate bytes और final byte आते हैं। यह 0x80 से 0x9f तक के single-byte C1 forms की भी अनुमति देता है। Terminal emulators इस standard के ऊपर private behavior जोड़ते हैं। xterm control-sequences documentation OSC, DCS, APC, PM और SOS strings का वर्णन करती है, जिनके terminators CSI sequence से अलग हो सकते हैं।

यह grammar इसलिए महत्वपूर्ण है क्योंकि controls हमेशा text की साफ line की तरह नहीं आते। Program एक chunk में ESC और अगले chunk में `[` लिख सकता है। Pseudo-terminal किसी भी byte पर output split कर सकता है। Tool BEL terminator वाला OSC string या ESC और backslash वाला दो-byte ST form भेज सकता है। सही boundary component को reads के बीच state बनाए रखनी होगी।

कुछ उदाहरण बताते हैं कि color stripper पर्याप्त क्यों नहीं है:

- `ESC[2J` terminal से display मिटाने को कहता है।
- `ESC[H` cursor को home position पर ले जाता है।
- `ESC]8;;URI ESC\\` उन terminals में OSC 8 hyperlink शुरू करता है, जो इसे support करते हैं।
- `ESC]52;... BEL` clipboard operation है, जिसे कई terminal emulators पहचानते हैं।
- Carriage return cursor को column zero पर लाता है और पहले की status line को overwrite कर सकता है।

किसी sequence का असर हर terminal पर हो, यह जरूरी नहीं। Output collector यह अनुमान नहीं लगा सकता कि छह महीने बाद engineer कौन-सा terminal इस्तेमाल करेगा, कौन-सा viewer log replay करेगा या कौन-सा parser control byte को visible token में बदलेगा। Collection के समय ambiguity हटा दें।

Escape sequences के बाहर के control characters पर भी ध्यान दें। Backspace, carriage return, form feed, bell और कई C1 bytes presentation बदल सकते हैं या line-oriented parsing को भ्रमित कर सकते हैं। केवल वही controls बचाएं, जिनकी output contract को जरूरत हो, आम तौर पर line feed और शायद tab। Carriage return तभी बचाएं, जब parser उसे स्पष्ट और tested अर्थ देता हो।

## Model तक पहुंचने से पहले controls हटाएं

सबसे सुरक्षित default सरल है: bytes collect करें, size की सीमा लगाएं, terminal controls को bytes की तरह parse करें, control instructions discard करें, बचे हुए printable content को तय error policy के साथ decode करें और cleaned text agent को भेजें। पहले decode करके यह उम्मीद न करें कि Unicode cleanup routine खतरनाक सामग्री ढूंढ लेगी। ESC ASCII byte है, C1 controls सीधे आ सकते हैं और malformed byte sequences filtering को skip नहीं करा पाने चाहिए।

व्यावहारिक output path में चार records होने चाहिए, भले agent को आप केवल एक दिखाएं। Raw byte stream को investigation के लिए protected storage में रखें। Model context के लिए normalized plain text बनाएं। Operators और logs के लिए removal report बनाएं। Exit status, duration, truncation state, byte count और raw content का cryptographic digest जैसे metadata रखें।

Removal report महत्वपूर्ण है। Silent deletion उस व्यक्ति से समस्या छिपा सकता है, जिसे investigation करनी है। उपयोगी report कह सकती है कि collector ने दो CSI sequences, एक OSC string, तीन carriage returns और एक invalid byte sequence हटाई। Payload को दोहराने की जरूरत नहीं। Report में OSC payload दोहराने से वही hazard वापस आ सकता है।

stdout और stderr को merge करने से पहले अलग-अलग साफ करें। Wrapper streams को जोड़ते समय programs ऐसे interleave कर सकते हैं कि source order सुरक्षित न रहे। अगर agent को एक narrative चाहिए, तो दोनों cleaned streams को label करें और collector द्वारा दिया गया sequence number शामिल करें। इससे उस transcript से अधिक सच्चाई बचती है, जो ऐसा सटीक order दिखाता है जिसे उसने कभी देखा ही नहीं।

Size limits भी इसी component में रखें। Attacker never-terminated OSC string या लगातार दोहराए गए output से memory और context भर सकता है। Total captured bytes पर hard limit और हर चल रहे control string पर छोटी limit लगाएं। Limit लगने पर process policy के अनुसार source बंद या drain करें, result को truncated चिह्नित करें और partial raw artifact को agent message से बाहर रखें।

Model से यह तय करने को न कहें कि escape sequence harmless है या नहीं। Models byte protocols नहीं, text parse करते हैं और surrounding context के आधार पर उनका निर्णय बदल सकता है। Model को output दिखने से पहले deterministic parser को यह फैसला करना चाहिए।

## Evidence बचाएं, behavior नहीं

Stripping और isolation अलग समस्याएं हल करते हैं। Stripping presentation instructions हटाकर usable text बनाता है। Isolation original bytes उस व्यक्ति के लिए उपलब्ध रखता है, जिसे उन्हें inspect करने का वैध कारण है। Raw capture को base64 encode करके उसे «sanitized» कहना transport और authorization को गड्डमड्ड करना है।

Agent को आम तौर पर failing build की शुरुआती कुछ सौ lines चाहिए, terminal session का byte-perfect replay नहीं। उसे truncation और removals के स्पष्ट markers वाला normalized text दें। अधिक detail चाहिए तो line range या search term से सीमित, cleaned excerpt मांगने दें। Raw capture को उसी context में paste करके जवाब न दें।

Investigator को original चाहिए तो उसे byte-oriented viewer में खोलें, जो control bytes को visibly render करे और उन्हें terminal तक कभी न भेजे। Hex dumps उपयोगी हैं, क्योंकि वे byte boundaries साफ करते हैं। ESC के लिए `^[` दिखाना मददगार हो सकता है, लेकिन viewer को C1 characters और string payloads भी सही ढंग से संभालने होंगे। Evidence पर `cat` चलाने वाला viewer forensic tool नहीं है।

यह shell command current terminal में controls चलाए बिना sample file बनाता है। File में red-text SGR, cursor-up CSI sequence, OSC 8 hyperlink और carriage return है:

```sh
printf 'build: \033[31mFAIL\033[0m\nnotice\033[1A\033]8;;https://example.invalid\033\\open\033]8;;\033\\\rPASS\n' > terminal-sample.bin
od -An -tx1c terminal-sample.bin
```

`od` output में ESC के लिए `1b` और carriage return के लिए `0d` होना चाहिए। इससे आपका terminal link follow या cursor move नहीं करना चाहिए, क्योंकि `od` bytes को replay करने के बजाय उनका representation दिखाता है।

Isolation को access control की भी जरूरत है। Raw artifact में गलती से छपे secrets हो सकते हैं। Terminal controls sanitize करने से tokens, passwords या personal data redact नहीं होते। Secret detection और redaction को अलग stage में, अपनी false-positive policy के साथ चलाएं। दोनों कामों को न मिलाएं, क्योंकि credential miss करने वाला redactor यह भी तय न करे कि OSC 52 request बचनी चाहिए या नहीं।

## Regex terminal parser नहीं है

एक regular expression लोकप्रिय है, क्योंकि वह colorful build output एक line में हटा देता है। Security boundary के रूप में यह fail होता है, क्योंकि terminal grammar stateful और streaming है। Long malformed input पर कई patterns performance समस्या भी पैदा करते हैं, ठीक वही input जो adversary दे सकता है।

ESC, CSI और string controls के लिए स्पष्ट behavior वाले byte state machine का इस्तेमाल करें। नीचे दिया Python function जानबूझकर सीमित है। यह tab और line feed बचाता है, carriage return को visible line break policy में बदलता है, दूसरे C0 और C1 controls हटाता है और OSC, DCS, APC, PM तथा SOS strings समेत ESC sequences हटाता है। यह chunks तभी स्वीकार करता है जब caller उन्हें पहले जोड़ चुका हो, इसलिए production version को reads के बीच state fields बनाए रखने चाहिए।

```python
def clean_terminal_bytes(data: bytes) -> tuple[str, dict[str, int]]:
    out = bytearray()
    counts = {"esc": 0, "csi": 0, "string": 0, "control": 0}
    i = 0

    while i < len(data):
        b = data[i]

        if b == 0x1b:  # ESC
            counts["esc"] += 1
            i += 1
            if i >= len(data):
                break
            nxt = data[i]

            if nxt == ord('['):  # CSI
                counts["csi"] += 1
                i += 1
                while i < len(data):
                    c = data[i]
                    i += 1
                    if 0x40 <= c <= 0x7e:
                        break
                continue

            if nxt in b']P_^X':  # OSC, DCS, APC, PM, SOS
                counts["string"] += 1
                i += 1
                while i < len(data):
                    c = data[i]
                    if c == 0x07:  # BEL
                        i += 1
                        break
                    if c == 0x1b and i + 1 < len(data) and data[i + 1] == ord('\\'):
                        i += 2
                        break
                    i += 1
                continue

            i += 1  # Two-byte ESC function or unknown ESC form
            continue

        if b == 0x9b:  # Single-byte C1 CSI
            counts["csi"] += 1
            i += 1
            while i < len(data):
                c = data[i]
                i += 1
                if 0x40 <= c <= 0x7e:
                    break
            continue

        if 0x80 <= b <= 0x9f or b < 0x20 and b not in (0x09, 0x0a):
            counts["control"] += 1
            i += 1
            continue

        out.append(b)
        i += 1

    return out.decode("utf-8", errors="replace"), counts
```

इस example की सीमाएं हैं। यह हर ECMA-48 control function model नहीं करता और unknown ESC forms को हटाने योग्य मानता है। Agent context में output जाने पर ऐसी conservatism उचित है। Terminal emulator को broad compatibility चाहिए, जबकि agent boundary को छोटा accepted surface चाहिए।

इस function को copy करके काम पूरा न मानें। CSI parameters और string controls के लिए maximum lengths जोड़ें। Read boundaries के बीच parser state बचाएं। Malformed और unterminated sequences गिनें। सबसे जरूरी बात, ऐसे tests लिखें जो साबित करें कि raw payload cleaned result में कभी नहीं आता। Security filter को केवल आकर्षक before-and-after screenshots नहीं, negative tests भी चाहिए।

## OSC sequences पर खास ध्यान दें

OSC strings से कई teams को पहली बार पता चलता है कि terminal output formatting से कहीं अधिक data ले जा सकता है। Xterm window titles और hyperlinks जैसी चीजों के लिए OSC commands document करता है। Modern terminal emulators अलग-अलग subsets implement करते हैं, इसलिए आज के local terminal पर आधारित allowlists खराब विकल्प हैं।

OSC 8 links साधारण दिखने वाले text को clickable destination बना सकते हैं। Visible label `build report` हो सकता है, जबकि target कहीं और जाए। Cleaned agent transcript पढ़ने वाले human को live link की जरूरत नहीं। अगर सुरक्षित parsing संभव हो, तो label को ordinary text की तरह रखें; अन्यथा पूरा OSC wrapper हटाकर केवल बाद के printable bytes बचाएं। URI targets तभी रखें, जब product की अलग URL validation और display policy हो।

OSC 52 terminal से clipboard content set करने को कह सकता है। कुछ terminals इसे disable करते हैं या setting मांगते हैं, लेकिन collector इस पर निर्भर नहीं रह सकता। Raw log permissive terminal में replay हो तो command attacker का चुना content operator के clipboard में डाल सकता है। अगला paste shell, ticket, chat या credential field में जा सकता है।

Title-setting sequences एक शांत समस्या पैदा करते हैं। वे terminal multiplexer, operating-system task switcher या recording में दिखने वाला window title बदल सकते हैं। ऐसा title, जो approval request या successful deployment जैसा लगे, कई windows देखने वाले operator को गुमराह कर सकता है। पूरा OSC traffic strip करने से ऐसी पूरी class हट जाती है और पुरानी पड़ने वाली list रखने की जरूरत नहीं रहती।

Model की समझ के लिए OSC strings बचाने की कोशिश न करें। Agent को terminal title update चलाने, terminal hyperlink पर click करने या clipboard operation पाने की कोई वैध जरूरत नहीं है। Command result में उपयोगी URL text हो तो command से उसे normal text की तरह print कराएं या terminal path में आने से पहले structured response से extract करें।

यही नियम device-control strings और application program commands पर लागू होता है। कुछ emulators उन्हें ignore करते हैं, दूसरे समय के साथ behavior जोड़ सकते हैं। Output boundary को fail closed होना चाहिए: control-string introducer पहचाने तो valid terminator या configured maximum length तक consume करें, anomaly record करें और payload को normal context से बाहर रखें।

## हर tool के लिए output contract तय करें

Sanitizer उस output interface को ठीक नहीं कर सकता, जो terminal transcript से database, report और user interface तीनों का काम लेना चाहता है। Tools को साफ बताना चाहिए कि वे agent को क्या लौटाते हैं: plain UTF-8 text, non-terminal channel पर बना structured JSON या isolated artifact का reference। Terminal-जैसे output को default interchange format के बजाय exception बनाएं।

जिन commands पर आपका नियंत्रण है, agent call के समय decoration बंद करें। कई programs no-color flag, machine-readable mode या environment setting देते हैं। Structured output तभी चुनें, जब schema आपके नियंत्रण में हो और उसका size सीमित हो। JSON में भी string fields के भीतर prompt injection हो सकता है, इसलिए हर field को data की तरह label करें और untrusted descriptions को instructions से अलग रखें।

जिन commands पर आपका नियंत्रण नहीं है, उन्हें pseudo-terminal के बजाय pipes के साथ चलाएं, जब तक command को terminal behavior की जरूरत न हो। Pseudo-terminal progress bars, cursor rewrites, title controls और अलग code paths को बढ़ावा देता है। Pipes output को safe नहीं बनाते, लेकिन संभालने वाले terminal protocol की मात्रा घटाते हैं।

Result के साथ provenance record करें। Agent और reviewer को executable, working directory, exit code, source stream, capture limit और sanitizer ने content हटाया या नहीं, यह दिखना चाहिए। Tool output को इन fields का रूप न लेने दें। Collector-generated metadata को untrusted text से बाहर distinct envelope में रखें।

उपयोगी envelope ऐसा दिख सकता है:

```json
{
  "command": "test-runner --report plain",
  "exit_code": 1,
  "stdout": "142 tests passed\n",
  "stderr": "fixture failed at tests/login.txt:18\n",
  "sanitizer": {"removed_controls": 4, "truncated": false},
  "raw_artifact": "restricted:sha256:..."
}
```

`raw_artifact` ऐसा reference होना चाहिए, जिसे सामान्य agent dereference न कर सके। अगर request पर content लौटाने के लिए नई authorization decision नहीं चाहिए, तो reference केवल दिखावटी था।

## Hostile output को daily shell के बाहर test करें

Green और red color codes वाले unit test को pass कर लेना sanitizer की शुरुआत भर है। ऐसे bytes test करें, जो parser states पार करें, अचानक terminate हों और display controls के साथ interact करें। Corpus को उसी exact capture path से चलाएं, जिसका agents इस्तेमाल करते हैं, जिसमें process spawning, buffering, log storage और transcript rendering शामिल हों।

एक छोटे corpus से शुरू करें, जिसमें एक chunk के अंत में ESC और अगले chunk की शुरुआत में `[` हो। Unusual parameter lengths वाले CSI sequences, BEL और ST दोनों से terminate होने वाले OSC strings, बिना terminator का OSC string, C1 CSI bytes, backspaces, repeated carriage returns और escape sequence के आसपास invalid UTF-8 जोड़ें। Assert करें कि agent-facing text में ESC, C1 range bytes या हटाए गए strings का payload न हो।

Display semantics भी test करें। `working 10%\rworking 20%\rfailure` जैसी status line को अपनी carriage-return policy से गुजारें। अगर newline के रूप में रखें, तो agent history देखेगा। अगर overwrite emulate करें, तो agent को केवल `failure` दिखेगा। दोनों policies चल सकती हैं, लेकिन उन्हें document और test करें। Parser update के दौरान silent behavior changes incident review को कठिन बनाते हैं।

यहां fuzzing उपयोगी है, क्योंकि escape grammars में states कम और malformed input space बहुत बड़ा होता है। ESC, BEL, backslash, C1 bytes और लंबे unterminated strings पर जोर देते हुए random byte streams बनाएं। Properties सीधी हैं: filter तय time और memory budget में खत्म हो, exception न फेंके और output में prohibited control bytes न हों।

Human path भी test करें। Log page, desktop notification, terminal pane और copied transcript content को अलग-अलग interpret कर सकते हैं। Raw corpus को केवल safe byte viewer में render करें। Cleaned text को हर normal interface में render करें। अगर engineer routine investigation में raw log entry को interactive terminal में copy कर सकता है, तो isolation boundary में hole है।

## Human approval transcript को sanitize नहीं करता

Approval prompt यह तय करता है कि agent कोई action कर सकता है या नहीं। यह तय नहीं करता कि लौटे हुए bytes display करने या model prompt में शामिल करने के लिए safe हैं। दोनों controls अलग रखें, वरना व्यक्ति समझ सकता है कि `ssh host command` approve करने का मतलब host से आया हर banner, file name और remote error भी approve करना है।

यह distinction action gateways के लिए महत्वपूर्ण है। Sallyport credentials को agent से दूर रख सकता है और action के लिए human authorization मांग सकता है, लेकिन action results agent को भेजने वाले हर integration को output normalization boundary की जरूरत होगी। Credential custody और transcript safety अलग सवालों के जवाब हैं।

कमजोर output handling की भरपाई repeated approval से न करें। किसी व्यक्ति से हर read approve कराने पर वह लंबे response के हर character की जांच नहीं करेगा, और response approval के बाद भी आ सकता है। Sensitive actions के लिए per-call review उपयोगी है, लेकिन इससे untrusted output trusted context नहीं बनता।

Decision record को sanitized result के पास रखें। Record करें कि operator ने process या call approve किया, फिर actual command result को sanitizer report के साथ untrusted tool data के रूप में record करें। यह separation incident review में दो सवालों का अलग जवाब देती है: operation को किसने authorize किया और operation ने कौन-सा data लौटाया?

## Raw capture को सामान्य agent पहुंच से बाहर रखें

एक आम escape hatch अच्छे design को बिगाड़ देता है: cleaned result अधूरा लगे तो agent को `read_raw_output` नाम का tool दे दिया जाता है। इससे safety boundary केवल speed bump बन जाती है। Attacker को इतना करना है कि cleaned summary भ्रमित लगे और agent original मांग ले।

इसके बजाय constrained retrieval इस्तेमाल करें। व्यक्ति artifact को safe viewer में खोल सके। Dedicated extractor सीमित hex range, digest comparison या उसी parser से process किया हुआ printable text लौटाए। अगर workflow को सचमुच raw protocol inspection चाहिए, तो explicit human decision लें और ऐसा viewer इस्तेमाल करें, जो terminal controls execute न करे।

Raw और cleaned retention policies अलग रखें। Raw output में secrets और hostile payloads हो सकते हैं, इसलिए उसकी retention छोटी रखनी पड़ सकती है। Cleaned transcripts agent session review के लिए अधिक समय तक उपयोगी रह सकते हैं, लेकिन उनमें business data फिर भी होता है। Hashes दोनों records को जोड़ते हैं, बिना routine users को raw bytes retrieve कराए।

पहला implementation task ऐसा prompt rule नहीं है, जो agents से terminal instructions ignore करने को कहे। Process output और हर agent-facing transcript के बीच byte parser रखें, unknown control syntax को गायब करें और original को केवल ऐसी जगह बचाएं, जहां सामान्य agents उसे fetch न कर सकें। इससे model, terminal या थके हुए operator को ambiguity समझने की जरूरत पड़ने से पहले ही एक पूरी risk class हट जाती है।
