# AI-assisted development में SSH agent forwarding के जोखिम

SSH agent forwarding के जोखिम को कम आंकना आसान है, क्योंकि निजी कुंजी डेवलपर की मशीन पर ही रहती है। यह बात सही है, लेकिन पूरी सुरक्षा नहीं देती। जिस रिमोट होस्ट को forwarded agent socket मिलता है, वह स्थानीय एजेंट से SSH authentication challenges पर हस्ताक्षर करने को कह सकता है। जिस सिस्टम पर वह पहचान स्वीकार होती है, वहां उपलब्ध access की अवधि तक रिमोट होस्ट आपकी तरह काम कर सकता है।

AI-assisted development इस गलती को करना आसान और पहचानना कठिन बना देता है। Coding agent रिमोट shell खोल सकता है, deployment commands चला सकता है, repositories देख सकता है और उस SSH config का पालन कर सकता है जो आपने वर्षों पहले किसी interactive session के लिए लिखी थी। अगर एजेंट ऐसी मशीन तक पहुंच जाए जिसमें आपका forwarded socket है, तो सुरक्षा सीमा पहले ही बदल चुकी है। Forwarding को डिफॉल्ट रूप से बंद रखें और जब किसी remote job को सचमुच दूसरे hop की जरूरत हो, तो उसे अपनी सीमित पहचान दें।

## Forwarded agent पहले होस्ट से आगे भी authentication कर सकता है

SSH agent forwarding निजी कुंजी की कॉपी नहीं, बल्कि signing service उपलब्ध कराता है। आपका स्थानीय `ssh-agent` एक या अधिक निजी पहचानें रखता है। Forwarding चालू करके कनेक्ट करने पर SSH रिमोट मशीन पर एक socket बनाता है और encrypted connection के जरिए signing requests को उस स्थानीय एजेंट तक पहुंचाता है।

यह अंतर अक्सर गलत जोखिम निर्णयों की वजह बनता है। लोग सुनते हैं, «कुंजी मेरे लैपटॉप से कभी बाहर नहीं जाती», और मान लेते हैं कि रिमोट मशीन पर हमला करने लायक कुछ नहीं है। निजी bytes स्थानीय रह सकते हैं, लेकिन SSH authentication के लिए रिमोट मशीन के पास वे bytes होना जरूरी नहीं है। उसे authentication request पर एक वैध signature चाहिए। Forwarded socket उसे यह signature पाने का रास्ता देता है।

OpenSSH का `ssh_config(5)` manual `ForwardAgent` को साफ शब्दों में समझाता है और चेतावनी देता है कि रिमोट होस्ट पर file permissions को बायपास कर सकने वाले उपयोगकर्ता forwarded agent तक पहुंच सकते हैं। व्यवहार में इसका मतलब है कि रिमोट account, उस account के तहत चलने वाला process, कोई administrator या पर्याप्त नियंत्रण हासिल कर चुका attacker इस socket का इस्तेमाल कर सकता है। रिमोट होस्ट पर root access मिलते ही socket permissions का सवाल खत्म हो जाता है।

RFC 4252 public-key SSH authentication को connection-specific data पर किए गए signature के रूप में बताता है। Server उस signature को authorized public key से जांचता है। Compromised remote host एजेंट को मनमाने documents के लिए सामान्य signing oracle में नहीं बदल सकता, लेकिन वह उन SSH signatures का अनुरोध कर सकता है जिनकी मदद से कहीं और SSH login का प्रयास किया जा सके। अगर आपकी पहचान source control, production hosts या internal administration systems पर काम करती है, तो attacker को ठीक यही क्षमता चाहिए।

आम failure कुछ ऐसा दिखता है:

1. Developer `ssh -A` के साथ `build.example.net` से जुड़ता है, क्योंकि उस मशीन को private repository तक पहुंचना है।
2. Developer का local agent `SSH_AUTH_SOCK` के जरिए build host पर दिखाई देता है।
3. कोई build script, compromised dependency या पर्याप्त access वाला दूसरा user उस socket से `git.example.net` या किसी internal host के लिए हस्ताक्षर करवाता है।
4. Destination developer की public key स्वीकार करता है और नए connection को developer के नाम से दर्ज करता है।

Destination पर SSH server को एक वैध cryptographic signature दिखाई देता है। वह यह नहीं बता सकता कि connection developer ने अपने laptop से शुरू किया था या पहले रिमोट machine पर चल रहे process ने forwarding के जरिए उसका अनुरोध किया था। Server logs accepted key और source address तो बताएंगे, लेकिन खोया हुआ अंतर वापस नहीं ला सकते।

## Configuration inheritance अनजाने exposure का कारण बनता है

ज्यादातर unsafe forwarding किसी जोखिम भरे session में लिए गए सचेत निर्णय से नहीं, बल्कि SSH configuration से शुरू होती है। `Host *` के नीचे लिखा एक पुराना `ForwardAgent yes` किसी developer, terminal tool या coding agent द्वारा संपर्क किए जाने वाले हर host पर लागू हो सकता है। जब कोई tool script से `ssh` चलाता है, तब भी यह नियम लागू रहता है और कोई warning दिखाई नहीं देती।

OpenSSH configuration में एक और महत्वपूर्ण बात है: हर parameter के लिए client आम तौर पर उसे मिलने वाली पहली value इस्तेमाल करता है। संकरी rule से पहले रखी गई broad rule उस exception को निष्प्रभावी कर सकती है जिसे आपने सोचा था कि आपने लिखा है। `~/.ssh/config` को जल्दी से देखकर भरोसा न करें। SSH से पूछें कि वह कौन सी configuration इस्तेमाल करेगा।

कनेक्ट करने से पहले स्थानीय मशीन पर चलाएं:

```sh
ssh -G deploy.internal.example | grep '^forwardagent '
```

सुरक्षित परिणाम है:

```text
forwardagent no
```

`ssh -G` files और command-line options को process करने के बाद प्रभावी client configuration दिखाता है। यह connection नहीं खोलता। इसलिए setup checks और संवेदनशील hosts की सूची पढ़ने वाली test script में यह उपयोगी है।

संबंधित config file के अंत के पास deny-by-default rule साफ तौर पर लिखें। संकरी exceptions उसके पहले रखें, क्योंकि OpenSSH पहली matching value लेता है:

```sshconfig
Host docs-bastion.example
    ForwardAgent yes

Host *
    ForwardAgent no
```

इस उदाहरण में अब भी एक खतरनाक exception है, इसलिए उसके पीछे कारण और जिम्मेदार व्यक्ति होना चाहिए। उद्देश्य यह है कि exception दिखाई दे और केवल एक host तक सीमित रहे, न कि हर नए environment पर चुपचाप लागू हो।

एक बार के connection के लिए config file में कुछ और लिखा होने पर भी सुरक्षित setting लागू करें:

```sh
ssh -o ForwardAgent=no developer@host.example
```

छोटा command `ssh -a developer@host.example` भी यही काम करता है। जिस मशीन की आपने जांच नहीं की है, temporary support host, training environment या vendor-managed system से जुड़ते समय इनमें से किसी एक का इस्तेमाल करें।

`IdentitiesOnly yes` को forwarding control न समझें। यह तय करता है कि server से authentication के समय SSH client कौन सी identities offer करेगा। यह रिमोट side पर agent socket बनाने से नहीं रोकता। इसी तरह, एक shell से `SSH_AUTH_SOCK` हटाना भी पूरी policy नहीं है। Child process कोई दूसरा environment inherit कर सकता है और अगली connection पर SSH config फिर forwarding चालू कर सकती है।

## AI workflows जांचे जाने वाले रास्तों को कई गुना बढ़ा देते हैं

AI coding agent को forwarding खतरनाक बनाने के लिए दुर्भावनापूर्ण होने की जरूरत नहीं है। उसे केवल ऐसा command चलाने की अनुमति चाहिए जो आपकी मौजूदा SSH आदतों तक पहुंच जाए। Agents repository instructions का पालन करते हैं, build scripts चलाते हैं, remote development hosts इस्तेमाल करते हैं और commands को छोटे बदलावों के साथ दोबारा चलाते हैं। ये सामान्य व्यवहार हैं। जब environment उन्हें दोबारा इस्तेमाल की जा सकने वाली developer identity देता है, तब यही व्यवहार जोखिम भरा बन जाता है।

चिंताजनक रास्ता अक्सर अप्रत्यक्ष होता है। आपका local agent development box से SSH session शुरू करता है। Global config rule के कारण वह session आपका agent forward करता है। Coding agent development box पर repository script चलाता है। Script dependency fetch करती है या दूसरा SSH connection खोलती है। दूसरा connection उस socket का इस्तेमाल कर सकता है जिसे पहली session वहां रख गई थी।

यह किसी इंसान के एक बार `git fetch` टाइप करने से अधिक जोखिम भरा है, क्योंकि agent कई tool calls कर सकता है और यह सवाल नहीं पूछता कि remote shell को किसी असंबंधित destination तक access की जरूरत क्यों है। Repository में मौजूद instructions agent को किसी खास host alias का इस्तेमाल करने को भी कह सकती हैं। वह alias operator से `ProxyCommand`, `Match` rule या inherited forwarding rule छिपा सकता है।

इन permissions को अलग-अलग समझें:

- Agent को remote shell खोलने की अनुमति।
- उस remote shell को दूसरे SSH destination तक पहुंचने की अनुमति।
- दूसरे destination को developer की identity स्वीकार करने की अनुमति।
- Agent को दूसरा connection शुरू करवाने की अनुमति।

Teams अक्सर इन चारों को «agent को SSH चाहिए» में समेट देती हैं। यह वाक्य boundary नहीं बताता। Remote shell और दोबारा इस्तेमाल की जा सकने वाली signing capability के परिणाम अलग होते हैं, इसलिए दोनों के लिए अलग निर्णय चाहिए।

जांचें कि agent process कैसे शुरू होता है। अगर वह interactive terminal से `SSH_AUTH_SOCK` inherit करता है, तो उसे direct SSH authentication के लिए स्थानीय agent identities तक पहले से access मिल सकता है। फिर अगर वह forwarding चालू वाले host से जुड़ता है, तो उस host को उन identities तक पहुंचने का दूसरा रास्ता मिल जाता है। Environment variable हटाने वाला wrapper आकस्मिक स्थानीय उपयोग घटा सकता है, लेकिन वह सुरक्षित SSH config या automation के लिए अलग identity का विकल्प नहीं है।

Agent instructions और automation wrappers में `ssh -A`, `scp -A` या ऐसे alias भी देखें जो इन commands में expand होते हों। `scp` और `sftp` SSH transport settings पर निर्भर करते हैं, इसलिए forwarding की आदत उस command से कहीं आगे फैल सकती है जिसने इसे पहली बार शुरू किया। Forwarding का निर्णय वहीं रखें जहां होना चाहिए, यानी connection definition में, और default साफ तौर पर no रखें।

## Jump host को आपके agent की जरूरत नहीं होती

कई developers forwarding इसलिए चालू करते हैं क्योंकि internal system तक पहुंचने से पहले bastion पार करना होता है। यह तब आम workaround था जब सबसे सुविधाजनक तरीका «bastion में login करो, फिर दोबारा SSH करो» हुआ करता था। Setup के दौरान यह जल्दी काम करता है, इसलिए आज भी लोकप्रिय है। लेकिन इससे bastion ऐसी मशीन बन जाता है जो आपकी identity दोबारा इस्तेमाल कर सकती है।

जब पहला host केवल traffic ले जाने का काम करता हो, तब `ProxyJump` इस्तेमाल करें। Local client jump host के जरिए tunnel बनाते हुए final host से authentication कर सकता है, बिना उस jump host पर agent socket forward किए।

```sshconfig
Host engineering-bastion
    HostName bastion.example
    User developer
    ForwardAgent no

Host release-host
    HostName release.internal.example
    User deploy
    ProxyJump engineering-bastion
    ForwardAgent no
```

इस व्यवस्था में local SSH client bastion के जरिए final SSH connection बनाता है। Bastion encrypted transport traffic आगे भेजता है। उसे ऐसा remote `SSH_AUTH_SOCK` नहीं मिलता जिससे वह आपके agent से signatures मांग सके।

Config पर भरोसा करने के बजाय route की जांच करें:

```sh
ssh -vvv release-host
```

Debug output में proxy jump connection देखें और सुनिश्चित करें कि उसमें agent forwarding की सूचना नहीं है। फिर final host में login करने के बाद remote environment जांचें:

```sh
printf 'SSH_AUTH_SOCK=%s\n' "${SSH_AUTH_SOCK:-}"
ssh-add -l
```

जब आपने forwarding जानबूझकर बंद किया हो, तब socket variable खाली होना अपेक्षित है। अगर `ssh-add -l` authentication agent से connection न होने की सूचना देता है, तो यह भी forwarded agent न होने के अनुरूप है। Troubleshooting करते समय केवल final host पर ये checks न चलाएं। हर interactive hop पर चलाएं, जहां किसी ने forwarding चालू की हो सकती है।

कुछ वैध स्थितियों में jump host को खुद किसी दूसरे host से authenticate करना पड़ सकता है, जैसे controlled release operation। इसका मतलब है कि jump host को अपनी deployment identity या short-lived workload identity चाहिए। इसका मतलब यह नहीं है कि उसे developer के laptop agent में लदी हर identity उधार लेनी चाहिए।

## Remote automation की अपनी identity होनी चाहिए

Remote build box, deployment host या AI tool को उस developer की तरह authenticate नहीं करना चाहिए जिसने session शुरू किया था। उसे अपनी assigned workload के रूप में authenticate करना चाहिए। यही बदलाव forwarding की जरूरत को कम करने के बजाय पूरी तरह हटाता है।

Workload identity को केवल उन services तक access देना चाहिए जिनका इस्तेमाल उस workload को करना है। Source repository के लिए, अगर service support करती हो, तो repository-scoped deploy identity इस्तेमाल करें। SSH destination के लिए dedicated account को dedicated public key authorize करें और जहां server ऐसी सीमाएं support करता हो, वहां उस account के commands या permissions सीमित करें। Certificate-based SSH setup में workload role से मेल खाने वाले principals वाले short-lived certificates जारी करें।

SSH certificates access को सीमित कर सकते हैं, लेकिन इन्हें जादुई सुरक्षा न समझें। Certificate का principal तभी तय करता है कि कौन से accounts उसे स्वीकार करेंगे जब server principals की सही जांच करे। Short validity period यह सीमित करता है कि signature कितनी देर तक authentication कर सकता है, लेकिन उस अवधि में compromised process इसका इस्तेमाल कर सकता है। Certificate authority पर भरोसा करने वाली server configuration और इन principals को इस्तेमाल करने वाले account rules की जांच करें।

CI worker या remote agent के लिए developer की personal public key को «temporary» identity के रूप में दोबारा इस्तेमाल न करें। इससे audit trail अस्पष्ट रहता है। Authentication record में personal key से login दिखने पर यह आसानी से पता नहीं चलता कि कार्रवाई developer ने की, build job ने या forwarding के जरिए compromised remote shell ने।

Agent-controlled HTTP और SSH actions के लिए एक और तरीका है कि credentials local action gateway में रखे जाएं और agent को केवल command या API result लौटाया जाए। Sallyport macOS पर यही model इस्तेमाल करता है: इसका vault SSH key रखता है और इसका `sp-ssh` helper agent को credential दिए बिना SSH action चलाता है।

यह सीमा शब्दों से नहीं, संचालन से बनती है। Agent named action का अनुरोध करता है, gateway credential लागू करता है और agent को output मिलता है। Agent को ऐसा agent socket नहीं मिलता जिसे वह किसी असंबंधित remote process में भेज सके। इससे approvals और records अर्थपूर्ण बनते हैं, क्योंकि वे भविष्य के अनियंत्रित signatures मांगने की खुली क्षमता के बजाय एक action से जुड़े होते हैं।

## Confirmation prompts exposure घटाते हैं, trust की समस्या नहीं सुलझाते

कभी-कभी छोटे maintenance task के लिए सचमुच forwarding चाहिए होती है और dedicated identity तैयार नहीं होती। ऐसी स्थिति में जितनी कम signing authority हो सके उतनी forward करें और बाकी हर उपयोग को दिखाई देने वाला बनाएं। यह अस्थायी control है, स्थायी architecture नहीं।

अपने रोजमर्रा के identities वाले agent को forward करने के बजाय अलग agent socket शुरू करें। उसमें केवल maintenance task के लिए जरूरी identity जोड़ें, कम lifetime रखें और confirmation जरूरी करें:

```sh
ssh-agent -a "$HOME/.ssh/maintenance-agent.sock" > "$HOME/.ssh/maintenance-agent.env"
. "$HOME/.ssh/maintenance-agent.env"
ssh-add -c -t 900 ~/.ssh/maintenance_ed25519
ssh -o ForwardAgent=yes operator@maintenance.example
```

`ssh-add -c` signature से पहले confirmation मांगता है। `-t 900` identity को 15 मिनट बाद हटा देता है। अपने installed OpenSSH version का `ssh-add(1)` manual देखें, क्योंकि confirmation का व्यवहार स्थानीय agent और उसके user interface पर निर्भर करता है।

इस काम के लिए अलग shell इस्तेमाल करें। काम पूरा होने पर identity हटाएं और agent बंद करें:

```sh
ssh-add -D
ssh-agent -k
```

इस sequence से उस temporary socket से आने वाले बाद के requests रुकते हैं। पहले जारी किए गए signatures, पहले से authenticated open connections या remote process द्वारा हासिल किए गए data वापस नहीं होते। Remote session बंद करें और उन destinations की जांच करें जहां वह identity access कर सकती थी।

OpenSSH के उन versions में `ssh-add -h` के जरिए destination constraints भी उपलब्ध हैं जिनमें यह feature शामिल है। ये constraints `known_hosts` में मौजूद host keys के आधार पर सीमित कर सकते हैं कि agent किन host paths के लिए sign करेगा। Controlled environment में इन्हें जांचना उपयोगी है, लेकिन इनके साथ ऐसी configuration dependencies आती हैं जिन्हें teams अक्सर बनाए नहीं रखतीं। पुरानी या अधूरी `known_hosts` file सबसे खराब समय पर safety measure को outage में बदल सकती है। Automation में इस्तेमाल होने वाले exact jump path और host aliases के साथ test करें।

Prompt fatigue को security boundary न मानें। Compromised host बार-बार signatures मांग सकता है और सामान्य infrastructure जैसे दिखने वाले destinations के नाम दे सकता है। Incident खत्म करने की जल्दी में अगर operators prompts तुरंत approve करते हैं, तो confirmation उनकी अपेक्षा से कम सुरक्षा देती है। Narrow identity और short lifetime prompt का blast radius छोटा रखते हैं।

## Logs को sessions और SSH actions में अंतर दिखाना चाहिए

SSH authentication logs बताते हैं कि किसी key ने server पर authentication किया। वे आमतौर पर यह नहीं बताते कि signature क्यों मांगा गया या agent forwarding ने वह रास्ता बनाया था या नहीं। अगर automated remote actions की अनुमति है, तो इतनी जानकारी दर्ज करें जिससे यह पता लगाया जा सके कि agent किसने शुरू किया, किस process को approval मिला, उसने किस destination से संपर्क किया और कौन सा action मांगा।

Session-level records को action-level records से अलग रखें। Session record बताता है कि किस agent process को access मिला और वह access कब खत्म हुआ। Action record बताता है कि उस access के तहत कौन सा SSH command या API call चला। दोनों को एक सामान्य event log में मिला देने से investigation धीमी हो जाती है, क्योंकि operator को कारण और परिणाम की कड़ी अलग-अलग fragments से अनुमान लगानी पड़ती है।

SSH server पर किसी संदिग्ध forwarding event के बाद उसके सामान्य authentication records देखें। सटीक स्थान operating system और service configuration पर निर्भर करता है, लेकिन आम जगहों में system journal entries और SSH daemon का authentication log शामिल हैं। Accepted public-key fingerprint, account name, source address और समय देखें। इनका पहले remote host के session history से मिलान करें।

IP address से निश्चित निष्कर्ष न निकालें। Forwarded connection build host, bastion, network address translation gateway या private overlay से आ सकता है। Server यह बता सकता है कि TCP connection कहां से आया, लेकिन यह नहीं कि developer की मशीन पर signature request किसने शुरू की।

Tamper-evident records तभी उपयोगी हैं जब system उन्हें agent के नियंत्रण से बाहर लिखे। जो process अपना action history बदल सकता है, वह किसी के देखने से पहले संदिग्ध दूसरे hop को मिटा सकता है। Sallyport agent sessions और individual calls को एक encrypted, hash-chained audit log में दर्ज करता है, और `sp audit verify` vault key के बिना offline उस chain की जांच कर सकता है।

कोई भी tooling चुनें, उसके records को एक वास्तविक failed authorization और एक वास्तविक successful SSH command के साथ test करें। सुनिश्चित करें कि उसमें agent run, destination, credential reference या fingerprint, result और time का नाम हो। केवल «tool completed» कहने वाले logs identity reuse से जुड़े incident question का उत्तर नहीं दे सकते।

## Exposure को automatic key theft नहीं, authorization abuse समझें

अगर आपको पता चले कि आपने agent को ऐसे host पर forward किया था जिस पर भरोसा नहीं है, तो मानकर चलें कि session के दौरान उस host ने आपकी identity का इस्तेमाल किया हो सकता है। इस proof का इंतजार न करें कि उसने private key निकाल ली। असली जोखिम unauthorized authentication है, और निजी कुंजी आपकी मशीन से बाहर गई ही न हो, यह संभव है।

पहले आगे का उपयोग रोकें। उस host के सभी SSH sessions बंद करें, exposed identity को agent से हटाएं और host-specific forwarding rule बंद करें। अगर identity shared agent में loaded थी, तो कामकाजी दिन के बीच बिना सोचे `ssh-add -D` न चलाएं और इसे समाधान न मानें। इससे वैध sessions रुक सकते हैं, जबकि प्रभावित public key servers पर authorized रह सकती है।

फिर उन destinations पर authorization हटाएं या revoke करें जहां वह identity पहुंच सकती है। सामान्य authorized-key setup में उन accounts से public key हटाएं जहां उसे अब काम नहीं करना चाहिए और जरूरत हो तो नई key लगाएं। SSH certificates के लिए अपने CA और server process के अनुसार certificate revoke करें, या short-lived certificate को expire होने दें, अगर आप exposure window को स्वीकार्य साबित कर सकते हैं। Repository access के लिए उस service के सामान्य controls से संबंधित deploy या user credential revoke या replace करें।

सीमित समय-सीमा की जांच करें, यानी forwarding उपलब्ध होने से लेकर remote session खत्म होने या local agent द्वारा requests स्वीकार करना बंद करने तक। Destination authentication records, भरोसेमंद होने पर remote shell history, job records और action logs देखें। अगर compromise का संदेह हो, तो remote host साफ करने से पहले logs सुरक्षित रखें।

अंत में वह रास्ता ठीक करें जिसने यह संभव बनाया। अगर global `ForwardAgent yes` से घटना हुई, तो केवल compromised host entry बदलने से अगला अज्ञात host exposed रहेगा। अगर AI workflow ने personal socket inherit किया था, तो उसे explicit connection setup और dedicated workload identity दें। सुधार ऐसा होना चाहिए जो unsafe route को default रूप से असंभव बनाए, केवल लोगों को flag याद रखने की सलाह न दे।

## सुरक्षित default को जल्दबाजी में भी सुरक्षित रहना चाहिए

Forwarding इसलिए बनी रहती है क्योंकि वह उसी क्षण friction कम करती है। Developer को एक और hop चाहिए, build को private fetch करनी है या agent को deadline से पहले task पूरा करना है। ये वास्तविक जरूरतें हैं। फिर भी इनके कारण हर उस machine पर broadly trusted developer identity रखना उचित नहीं हो जाता जो रास्ते में आ जाए।

Global स्तर पर `ForwardAgent no` सेट करें। Transport-only bastions के लिए `ProxyJump` इस्तेमाल करें। Remote automation को ऐसी identity दें जो बताती हो कि उसे क्या करने की अनुमति है। जब temporary exception टाली न जा सके, तो एक identity अलग रखें, confirmation जरूरी करें, short expiry तय करें और task खत्म होने पर उसे हटा दें।

इस सप्ताह आपकी team जिन host aliases का इस्तेमाल कर रही है, उन पर `ssh -G` चलाएं। यह छोटा check उस शांत configuration mistake को पकड़ सकता है, इससे पहले कि किसी remote process को ऐसी signing service मिल जाए जो उसे कभी मिलनी ही नहीं चाहिए थी।
