# Approval request binding क्लिक के बाद होने वाले बदलाव रोकती है

मानवीय मंज़ूरी तभी अर्थपूर्ण होती है जब वह ऐसी कार्रवाई को अधिकृत करे जो बाद में बदली न जा सके। अगर agent की प्रस्तावित HTTP request या SSH command दिखाई जाए और फिर वही agent बाद में अंतिम विवरण भेज सके, तो time-of-check और time-of-use के बीच इतना बड़ा अंतर रह जाता है कि उसमें destructive request आसानी से निकल सकती है।

मैंने यह गलती भरोसा जगाने वाले रूप में सामने आते देखी है: सुंदर approval sheet, process identity badge, हरे रंग की confirmation state। फिर implementation client के पास रखा approval token protocol के ज़रिए वापस भेजती है और उसके साथ नया URL या command स्वीकार कर लेती है। व्यक्ति ने एक चीज़ approve की थी। Executor ने दूसरी चलाई। यह authorization design की विफलता है, भले ही किसी ने जानबूझकर उसे bypass करने की कोशिश न की हो।

## Preview को immutable authorization object का वर्णन करना चाहिए

Approval screen को stored action object render करना चाहिए और approval के बाद executor को उसी object को dispatch करना चाहिए। Screen authoritative state का view है, client से अपने proposal को अधिक सावधानी से दोहराने का अनुरोध नहीं।

Approval दिखाने से पहले object बनाएं। उसे एक random action identifier दें, requesting process और session से जोड़ें, channel-specific inputs resolve करें और executor के नियंत्रण वाली memory या encrypted local storage में रखें। Client को pending status और identifier मिल सकता है। उसे ऐसी capability नहीं मिलनी चाहिए जिससे वह बाद में execution parameters जोड़ सके।

इसे permission slip नहीं, restaurant ticket समझें। Server ticket को approve करने के बाद kitchen ग्राहक से dinner का order फिर से बताने को नहीं कहता। वह अपने पास मौजूद ticket तैयार करता है। अगर order बदलता है, तो kitchen को नया ticket मिलता है और ग्राहक बदलाव देखता है।

Immutable object में compact preview में दिखने वाले fields से अधिक जानकारी होनी चाहिए। इसमें process identity, session identifier, creation time, expiry time, channel, credential reference, normalized action details और binding digest भी चाहिए। Digest accidental mutation और deliberate substitution का पता लगाने में मदद करता है, लेकिन असली सुरक्षा object पर executor का स्वामित्व देता है।

इसलिए approval response का अर्थ केवल इतना होना चाहिए: \"approve action 7c91... with binding digest sha256:...\"। इसका अर्थ यह नहीं होना चाहिए कि \"यह process एक HTTP call कर सकता है\" या \"caller बाद में command X चला सकता है।\" ये व्यापक statements session authorization में उपयोगी हो सकते हैं, लेकिन किसी खास side effect के लिए consent का स्थान नहीं ले सकते।

यह अंतर autonomous agents के मामले में सबसे महत्वपूर्ण है, क्योंकि client कोई भरोसेमंद clerk नहीं है जो मानवीय निर्णय को बिना गलती के लिखता जाए। वह एक active program है, जो retry कर सकता है, follow-up calls जोड़ सकता है, tool result के बाद अपना plan बदल सकता है या उसमें कोई bug हो सकता है। Preview के बाद भी जिस field पर उसका प्रभाव बना रहता है, उसे संदिग्ध मानें।

## HTTP preview में हर प्रभावी input का नाम होना चाहिए

HTTP approval में method, पूरा destination, effective headers, body और dispatch behavior bind होना चाहिए, क्योंकि ये request का अर्थ बदल सकते हैं। \"POST to billing service\" जैसा preview किसी state-changing call को approve करने के लिए व्यक्ति को बहुत कम जानकारी देता है।

RFC 9110 request method, target, fields और content को अलग-अलग रखता है, क्योंकि server इनमें से हर एक को अलग अर्थ दे सकता है। Authorization code को भी यही अलगाव बनाए रखना चाहिए। Approval sheet के लिए एक सुविधाजनक वाक्य बनाकर नीचे की layer को अस्पष्ट हिस्से भरने की अनुमति न दें।

Outbound request के लिए templates और defaults resolve करने के बाद ये values capture करें:

- Scheme, hostname, port, path और query string।
- HTTP method और यह कि request redirects follow कर सकती है या नहीं।
- हर effective header name और value, उस secret material को छोड़कर जिसे vault inject करता है।
- Final body bytes, उनका content type, length और digest।
- Credential reference और injection mode, जैसे bearer या custom header।

Credential value preview या agent के context में नहीं होनी चाहिए। फिर भी व्यक्ति के पास उसके इस्तेमाल का आकलन करने के लिए पर्याप्त जानकारी होनी चाहिए। \"Production payments credential sent only to api.example.test as a bearer token\" और \"credential attached\" से मिलने वाला अर्थ बिल्कुल अलग है। Executor token उजागर किए बिना vault record से यह विवरण बना सकता है।

Unexpanded template को approve न करें। मान लीजिए agent `POST /users/{id}/role` प्रस्तावित करता है और body में `{role}` है। अगर कोई दूसरा component approval के बाद इन variables को expand करता है, तो वह effective target या privilege बदल सकता है। पहले variables resolve करें, फिर action object बनाएं। जब कोई input जानबूझकर बाद तक उपलब्ध नहीं है, तो उसके concrete होते ही approval मांगें।

Headers को जितनी सावधानी चाहिए, उतनी अधिकतर teams नहीं देतीं। Duplicate header, बदला हुआ `Content-Type`, जोड़ा गया override header या अलग query encoding server behavior बदल सकता है, जबकि friendly summary वही दिखती रहे। जब protocol या target server के लिए order का अर्थ हो, तो repeated fields को उसी क्रम में रखें। Ambiguous inputs को चुपचाप commas से जोड़ने के बजाय reject करें।

एक उपयोगी internal data shape कुछ ऐसी हो सकती है। यह implementation pattern है, endpoint contract नहीं:

```
{
  "action_id": "7c91e2d4",
  "binding": "sha256:4f06...",
  "caller": {"session": "p-481", "process_start": "..."},
  "http": {
    "method": "PATCH",
    "url": "https://api.example.test/v1/users/42",
    "headers": [["content-type", "application/json"]],
    "body_sha256": "a4d8...",
    "body_length": 31,
    "redirects": "deny"
  },
  "credential_ref": "vault:payments-prod"
}
```

व्यक्ति के इस object को approve करने के बाद transport layer अपने stored record से `http` और `credential_ref` पढ़ती है। वह client से दूसरा URL, header map या body स्वीकार नहीं करती। यही अंतिम नियम bug को रोकता है। Digest केवल यह साबित करता है कि approval किस record के लिए था।

## SSH commands के साथ resolved execution context भी bind करें

SSH command preview में remote execution context bind होना चाहिए, क्योंकि अकेली command string यह नहीं बताती कि वास्तव में क्या चलेगा। अलग user, host, shell, working directory, environment या stdin stream के तहत वही text अलग प्रभाव डाल सकता है।

RFC 4254 command execution के लिए SSH connection protocol request परिभाषित करता है, लेकिन वह human-readable approval boundary नहीं बनाता। SSH server तक command string पहुंचाता है। Remote shell interpretation, account configuration, forced commands और command wrappers ऐसा अर्थ जोड़ सकते हैं जिसे basic preview छोड़ देता है।

Hostname, port, remote user, host-authentication expectation, identity reference, exact command bytes, execution mode, terminal allocation, working directory, supplied environment और stdin digest तथा length को store और present करें। SSH helper के ज़रिए की गई file operation के लिए remote path, operation type, file digest और overwrite behavior को भी bind करें।

Execution mode केवल दिखावे की चीज़ नहीं है। ये दोनों एक-दूसरे के विकल्प नहीं हैं:

```
/usr/local/bin/deploy --environment=staging
sh -lc '/usr/local/bin/deploy --environment=staging'
```

पहली command direct execution मांगती है, अगर helper इसे support करता है। दूसरी स्पष्ट रूप से shell से string parse करने को कहती है। Shell parsing में expansion, command substitution, shell startup behavior और quoting rules शामिल होते हैं। अगर आपका product दोनों modes स्वीकार करता है, तो उन्हें साफ़-साफ़ label करें और approval के बाद एक को दूसरे में कभी न बदलें।

Display-formatted command से preview बनाकर अलग से assembled array execute न करें। Display string empty argument, newline, nonprinting character या `--file=/tmp/a b` और दो अलग arguments के बीच का अंतर छिपा सकती है। Helper जिस exact byte sequence या argument vector का इस्तेमाल करेगा, उसी से safe escaped representation render करें। अगर command में ऐसे bytes हों जिन्हें सुरक्षित रूप से दिखाया नहीं जा सकता, तो उसे reject करें या स्पष्ट encoded representation दिखाएं जिसे user जांच सके।

Remote destination में बदलावों पर भी HTTP redirects जैसा अनुशासन चाहिए। Local SSH configuration alias, proxy jump या host configuration lookup छोटे host name को अलग destination में बदल सकते हैं। Approval से पहले final connection plan resolve करके bind करें। जिन systems में DNS rebinding threat model का हिस्सा है, उनमें host-address policy सावधानी से अलग जोड़ें। Address pin करने से सामान्य failover टूट सकता है, जबकि hostile resolver को नज़रअंदाज़ करने पर valid hostname गलत machine तक जा सकता है। यह अलग समस्या है, इसलिए command preview को उसका समाधान न बताएं।

## व्यक्ति के click करने के बाद vulnerable interval शुरू होता है

विफलता तब होती है जब UI proposed details जांचती है, yes रिकॉर्ड करती है और बाद का execution path client से mutable details पढ़ता है। यह अक्सर सामान्य asynchronous code के पीछे छिपी रहती है।

मान लीजिए कोई agent process gateway से funds transfer करने को कहता है:

1. Client `POST https://api.example.test/transfers` भेजता है, जिसमें 50 units का body है।
2. Gateway preview बनाकर व्यक्ति की approval का इंतज़ार करती है।
3. Destination और amount पढ़ने के बाद व्यक्ति approve करता है।
4. Client approval identifier के साथ `execute` भेजता है और नया body देता है, जिसमें 5,000 units हैं।
5. Gateway केवल यह जांचती है कि approval identifier valid है, credential inject करती है और नया body भेज देती है।

इस sequence के लिए compromised UI या stolen credential की कोई आवश्यकता नहीं है। Client ने gateway द्वारा दी गई API का इस्तेमाल किया। ऐसा test जो original request approve करके केवल identifier के काम करने की पुष्टि करता है, pass हो जाएगा। ऐसा test जो final outbound bytes की तुलना approved object में दर्शाए गए bytes से करता है, fail होगा। यही परिणाम चाहिए।

यही fault तब भी दिखता है जब gateway mutable request object को reference के रूप में store करती है। Preview worker उसे पढ़ता है, agent-side retry handler उसे बदलता है और dispatch worker बाद में बदला हुआ version भेज देता है। Object copy करना भी पूरा समाधान नहीं है, अगर nested maps, body buffers या callbacks अब भी shared हों। ऐसा immutable value बनाएं जिसमें client-owned references न हों, या उसे authoritative representation में serialize करके केवल उसी representation से दोबारा बनाएं।

Expiry mutable state को ठीक नहीं करती। Thirty-second approval भी पहले ही second में भेजी गई बदली हुई request को authorize कर सकती है। Rate limits भी इसे ठीक नहीं करतीं। वे केवल यह घटाती हैं कि client गलती का कितनी बार फायदा उठा सकता है, यह नहीं कि व्यक्ति ने effect approve किया था या नहीं।

State machine इतनी छोटी रखें कि उसका audit हो सके:

```
proposed -> frozen -> awaiting_human -> approved -> dispatched
                         |                 |
                         v                 v
                      rejected           expired
```

केवल `approved` से `dispatched` वाला transition external action कर सकता है। इस transition को action identifier से frozen record load करना चाहिए, caller और expiry फिर से check करनी चाहिए और record को सीधे HTTP या SSH executor को देना चाहिए। Details बदलने वाली कोई भी request वापस `proposed` में जाए और नया identifier बनाए।

## जहाँ semantics बदलने की गुंजाइश हो, वहाँ bytes bind करें

Binding के लिए stable representation चाहिए। वरना दो components यह मान सकते हैं कि उन्होंने एक ही request approve की है, लेकिन wire data अलग बना सकते हैं। कठिनाई यह तय करने में है कि किस representation को authoritative माना जाए।

Simple JSON requests के लिए documented rules के तहत structured form normalize करें, उसे एक बार serialize करें और final body bytes तथा digest सुरक्षित रखें। Preview readable JSON दिखा सकती है, जबकि dispatch path stored bytes भेजे। Approval के बाद parse, pretty-print और reserialize कभी न करें, जब तक transformed output ही approved object न रहा हो।

Forms, multipart uploads, repeated headers या arbitrary binary input के लिए bytes आमतौर पर अधिक सुरक्षित boundary होते हैं। Exact bytes, content type और transfer framing decisions bind करें, क्योंकि ये server interpretation को प्रभावित करते हैं। Digest UI को approval sheet में private document डाले बिना बड़े body की पहचान करने देता है, लेकिन executor को trusted state से उन्हीं exact bytes को सुरक्षित रखना या फिर से बनाना होगा।

URL canonicalization भी एक trap है। Hostname का case normalize करना आमतौर पर harmless है। Path को decode और re-encode करना, query pairs sort करना, empty value हटाना या `+` और `%20` को समान मानना application के routing या request validation को बदल सकता है। एक narrowly defined canonical form चुनें, उसे document करें और ऐसी input reject करें जिसकी एक से अधिक plausible interpretation हो। \"We normalize URLs\" कोई security property नहीं है।

SSH के लिए shell string की जगह argument vector बेहतर है, जब protocol helper बिना shell invoke किए उसे execute कर सकता हो। हर argument को अलग byte sequence के रूप में bind करें। जब remote side को अनिवार्य रूप से एक shell command string ही मिले, तो उस string को exact रूप में bind करें और उसकी escaping ईमानदारी से दिखाएं। यह दावा न करें कि local machine का parser किसी arbitrary remote shell startup file के behavior को साबित कर सकता है।

Approval digest में versioned और domain-separated encoding शामिल होनी चाहिए। Encoded data के आगे `action-v1/http` या `action-v1/ssh` जैसा fixed label लगाएं, सभी immutable fields को unambiguous lengths या deterministic serializer के साथ शामिल करें और फिर hash बनाएं। Versioning upgrade के बाद पुरानी interpretation को चुपचाप नई interpretation बनने से रोकती है। Domain separation यह रोकती है कि केवल serialized bytes के संयोग से कोई HTTP object कभी SSH object से match कर जाए।

## Caller identity और action binding अलग समस्याएं हल करते हैं

Per-session authorization बताता है कि कौन सा running process actions request कर सकता है। Action binding बताता है कि वह process कौन सी exact action execute कर सकता है। दोनों चाहिए, और इनमें से कोई भी दूसरे की जगह नहीं लेता।

Action को client द्वारा भेजे गए नाम से नहीं, gateway द्वारा देखी गई process identity से bind करें। macOS पर इसमें process code-signing authority, process identifier और process start instance शामिल हो सकते हैं। Start instance महत्वपूर्ण है, क्योंकि operating system process identifier दोबारा इस्तेमाल कर सकता है। अगर process exit हो जाए, तो session authority के साथ उसकी pending और approved actions भी discard करें।

किसी child process को केवल इसलिए broad approval inherit न करने दें कि उसे अपने parent का identifier पता है। Gateway boundary पर हर connecting process को observe करें। अगर tool architecture में यह संभव नहीं है, तो limitation साफ़-साफ़ बताएं और ऐसी confidence का दिखावा करने के बजाय approval period छोटा रखें जो वास्तव में आपके पास नहीं है।

Human approval का lifetime भी छोटा और सीमित होना चाहिए। Expiry frozen object और approval decision में होनी चाहिए, केवल UI के timer में नहीं। Expiry बीतने के बाद executor dispatch reject करे, भले ही client ने पुराना response पूरे समय संभालकर रखा हो। Revoked session को उसकी approved लेकिन unused actions का dispatch भी रोकना चाहिए।

Sallyport का per-session authorization यह तय कर सकता है कि नया connected agent process किसी run का इस्तेमाल कर सकता है या नहीं। फिर भी action gateway को preview की गई हर side effect के लिए यह अलग immutable boundary चाहिए। Session decision जानबूझकर broad होता है, जबकि HTTP request या SSH command approval exact रहना चाहिए।

Process identity को कम detail दिखाने का कारण न बनाएं। Trusted signed code में भी defects, unexpected prompt injection inputs या ऐसा plugin हो सकता है जो अपने author की अपेक्षा से अलग branch ले। Human gate का मूल्य यही है कि वह concrete effect को machine से बाहर जाने से पहले देख सकता है।

## Retries और redirects approved boundary के भीतर रहने चाहिए

Transport retry approval का फिर से इस्तेमाल तभी कर सकता है जब वह recorded conditions के तहत उसी frozen action को दोबारा भेजे। कई retry implementations mutable client state से requests दोबारा बनाकर इस rule का चुपचाप उल्लंघन करती हैं।

Action freeze करते समय allowed retry behavior तय करें। उदाहरण के लिए, HTTP response आने से पहले connection failure के बाद एक resend की अनुमति दें, जिसमें वही URL, headers, body bytes और credential reference हों। हर attempt को उसी action identifier के साथ record करें। Uncertain response के बाद non-idempotent request को तब तक retry न करें, जब तक protocol और target API आपको भरोसेमंद idempotency mechanism न दें।

Idempotency token तभी उपयोगी है जब वह खुद bound हो। अगर executor इसे generate करता है, तो action freeze करते समय generate करें और approved header set में सुरक्षित रखें। अगर client इसे देता है, तो उसे approved request का हिस्सा मानें। Retry पर इसे बदलने से एक approval दूसरा side effect पैदा कर सकता है।

Authentication recovery पर भी उतना ही संदेह रखें। अगर vault internally credential refresh करता है, लेकिन identical approved request को identical destination पर भेजता है, तो action boundary के भीतर रह सकता है। अगर recovery tenant, destination, scope या application को दिखाई देने वाले headers बदलती है, तो यह अलग request है और नई approval चाहिए। ऐसे background mechanism से बचें जो authorization failure को बिना review के alternate call में बदल दे।

Approved actions के लिए default HTTP redirect behavior deny होना चाहिए। हर relevant field को बनाए रखने वाला redirect भी किसी दूसरे authority तक जा सकता है। अगर product redirects follow करने देता है, तो dispatch से पहले हर hop inspect करें और host, port, method या body बदलने पर नया action object मांगें। Same authority पर relative redirect को explicit rule के अनुसार संभालें, underlying client library के संयोगवश behavior के अनुसार नहीं।

SSH reconnects भी इसी rule का पालन करते हैं। केवल bound host plan, bound identity और command पर reconnect करें। नया host fingerprint, बदला हुआ proxy route या host resolution result आपके threat model के अनुसार नई human decision मांग सकता है। Retry code इतना सीधा होना चाहिए कि auditor देख सके कि वह original approval का दायरा बढ़ा ही नहीं सकता।

## Approval और execution को अलग events के रूप में audit करें

Audit record में व्यक्ति द्वारा review की गई action और executor द्वारा किए गए attempts दोनों सुरक्षित होने चाहिए। केवल eventual request रिकॉर्ड करने वाला log यह साबित नहीं कर सकता कि preview उससे match करती थी। केवल approval रिकॉर्ड करने वाला log यह साबित नहीं कर सकता कि dispatch हुआ था।

Frozen action का identifier, binding digest, caller identity, preview-safe fields, credential reference identifier, approval time, expiry और outcome record करें। फिर हर dispatch attempt को उसी identifier, transport result, retry reason अगर कोई हो, और actual sent body या command के digest के साथ record करें। Secret values को user-facing journal और agent response दोनों से बाहर रखें।

यहीं hash-chained log उपयोगी साबित होती है। जब verification stored sequence को cover करती है, तो chain पिछली entries को हटाने या उनका क्रम बदलने का पता लगा सकती है। लेकिन वह अस्पष्ट event को उपयोगी नहीं बना सकती। अगर record में केवल \"approved SSH action\" लिखा है, तो chain उस अस्पष्ट event को हमेशा के लिए सुरक्षित रखेगी।

जो gateway write-blind encrypted और hash-chained log रखती है, उसके लिए offline verifier को vault access से स्वतंत्र रूप से ciphertext chain check करनी चाहिए। Sallyport `sp audit verify` के ज़रिए यह check उपलब्ध कराता है। यह उपयोगी है, क्योंकि investigator credential vault खोले बिना continuity verify कर सकता है।

Audit output में binding साफ़ दिखाई देनी चाहिए। Reviewer देख सके कि approval `7c91e2d4` digest `4f06...` को cover करती थी, dispatch ने वही digest इस्तेमाल किया और executor ने किसी mismatched proposal को reject कर दिया। ऐसे logs से बचें जो approval के समय human summary और execution के समय अलग raw request print करते हों, लेकिन दोनों के बीच कोई durable join न हो।

## Client को ऐसे test करें जैसे वह अपना मन बदलना चाहता हो

सामान्य integration test साबित करता है कि approved action सफल होती है। Security test को साबित करना चाहिए कि approved action किसी दूसरी action में नहीं बदल सकती, भले ही व्यक्ति के click करने के बाद client खराब व्यवहार करे।

ऐसा harness बनाएं जो approval के तुरंत बाद pause हो जाए। Dispatch के इंतज़ार के दौरान हर client-controlled input को एक-एक करके बदलें: method, host, port, path, query, header values, repeated headers, body, credential selector, redirect setting, SSH user, command, environment, stdin और file digest। Executor को या तो original stored action dispatch करनी चाहिए या attempt reject करना चाहिए। Mutation को कभी dispatch नहीं करना चाहिए।

Race conditions को field changes की तरह जानबूझकर test करें। एक approved identifier के लिए दो execute messages भेजें। Approval cancel करके उसी क्षण execute भेजें। Requesting process को exit होने दें, फिर नया process reconnect करके पुराने identifier का इस्तेमाल करने की कोशिश करे। Approval के इंतज़ार में UI restart करें। Pending-action store को expiry cleanup चलने तक भरें। ये सामान्य lifecycle events हैं और authorization code अक्सर इनके बीच की seams में fail होता है।

ऐसे fake HTTP server का इस्तेमाल करें जो received exact method, target, duplicate headers और body digest report करे। Controlled SSH endpoint इस्तेमाल करें जो remote user, command bytes, terminal request, environment और stdin digest record करे। Assertions gateway के intent object पर ही नहीं, इन observations पर आधारित हों। उद्देश्य intent और transport के बीच का divergence पकड़ना है।

अंत में failed tests को पढ़ने योग्य बनाएं। उपयोगी failure बताए कि approval digest `4f06...` ने `PATCH /v1/users/42` को cover किया था, जबकि attempted dispatch में अलग digest था और उसे reject कर दिया गया। अस्पष्ट \"authorization failed\" message engineers को फिर print statements की ओर भेजता है और test green होने तक checks कमजोर करने के लिए प्रेरित कर सकता है।

## Immutability boundary को executor के पास रखें

Credentials रखने और HTTP या SSH connection खोलने वाले component के पास frozen approved action का स्वामित्व होना चाहिए। इससे पहले की कोई boundary दूसरी layer को उसे दोबारा interpret, rebuild या replace करने की छूट देती है।

यह design human interaction को भी ईमानदार रखता है। व्यक्ति उस object से बना preview देखता है जो भेजा जाएगा, agent को credentials नहीं बल्कि results मिलते हैं और audit trail में intent, approval और execution को जोड़ने वाला एक identifier होता है। ये अलग-अलग लाभ हैं, लेकिन सभी post-click changes स्वीकार न करने के एक ही नियम पर निर्भर हैं।

अगर आपका current flow agent को approval token लौटाता है और बाद में fresh action fields स्वीकार करता है, तो बेहतर copy, अधिक policy controls या एक और approval dialog जोड़ने से पहले इसे ठीक करें। पहले request freeze करें। फिर executor से साबित कराएं कि उसने frozen record का ही इस्तेमाल किया।
