# Autonomous agents के लिए SSH port forwarding: अधिक सुरक्षित tunnels

Autonomous agents के लिए SSH port forwarding का design सामान्य developer access से अधिक सख्त होना चाहिए। कोई tunnel localhost तक सीमित database, admin panel या staging service को एक ही SSH command के ज़रिए agent की पहुँच में ला सकता है। अगर agent के पास व्यापक SSH credentials हैं, तो tunnel केवल एक लक्षण है। असली समस्या यह है कि उस command के पीछे destination, duration या authority को किसी ने सीमित नहीं किया।

मैंने teams को किसी tunnel को «temporary» कहते देखा है, सिर्फ इसलिए कि किसी ने terminal में `ssh -L` टाइप किया था। फिर terminal कई दिनों तक खुला रहा, उसका port किसी दूसरे process की dependency बन गया और security team के पूछने पर tunnel खोलने वाला व्यक्ति उपलब्ध नहीं था कि production के पास वाली service पर यह अनजान listener क्यों चल रहा है। Autonomous work इस pattern को और खराब करता है, क्योंकि agent किसी distracted human से कहीं अधिक लगातार retry, reconnect और tunnel का इस्तेमाल कर सकता है।

सुरक्षित तरीका सीधा है: agent को सीमित connection path दें, किसी खास temporary exposure को मंज़ूरी दें, इतना context रिकॉर्ड करें कि बाद में पूरी घटना समझी जा सके और closure को इच्छा नहीं, enforce होने वाली घटना बनाएँ।

## Tunnel केवल SSH behavior नहीं, network reachability बदलता है

SSH port forward, SSH connection के एक तरफ listener बनाता है और उसका traffic दूसरी तरफ से reachable destination तक पहुँचाता है। यह listener एक access path है। इसे केवल «SSH access» के रूप में review करने पर जोखिम का असली हिस्सा छूट जाता है: कौन connect कर सकता है, traffic कहाँ पहुँचता है और इसके ज़रिए क्या भेजा जा सकता है।

मान लें कि build host पर चल रहा agent `db-admin.internal.example` के port 5432 पर query करना चाहता है। Local forward इस service को build host पर `127.0.0.1:15432` के रूप में उपलब्ध करा सकता है। Database public internet से private रहता है, लेकिन उस host पर listener तक पहुँचने वाला हर process अब connection का प्रयास कर सकता है। SSH server database तक जाने वाला authorized route भी बन जाता है।

यह स्वीकार्य हो सकता है। लेकिन यह agent को «maintenance के लिए SSH इस्तेमाल करने» देने के बराबर नहीं है। Approval में यह exact route होना चाहिए:

- initiating workload और host
- listener address और port
- target host और port
- route का कारण
- expiry और approving person

Remote forward में listener की जगह उलट जाती है। अगर agent bastion से बाहर connect करके bastion को port 18080 पर listen करने को कहता है, तो उस listener तक पहुँचने वाला process agent host की ओर वापस traffic प्राप्त कर सकता है। Remote forwarding अक्सर teams को इसलिए चौंकाता है क्योंकि यह inbound firewall rule खोले बिना firewall के पीछे की service को expose कर सकता है।

OpenSSH इन modes को `ssh(1)` में अलग-अलग दर्ज करता है: `-L` local forwards, `-R` remote forwards और `-D` SOCKS proxy बनाता है। यह operationally उपयोगी अंतर है। «Port forwarding» को एक ही permission न मानें। हर mode अलग listener expose करता है और अलग restrictions माँगता है।

## Local, remote और dynamic forwarding के लिए अलग फैसले चाहिए

जब agent को controlled jump host के ज़रिए किसी एक ज्ञात service की ज़रूरत हो, तो local forward आम तौर पर सही विकल्प होता है। Listener agent की ओर होता है और SSH server internal destination से connect करता है। Command इस तरह दिख सकती है:

```sh
ssh -N \
  -L 127.0.0.1:15432:db-admin.internal.example:5432 \
  agent-db-tunnel@bastion.internal.example
```

`-N` SSH को remote command न चलाने के लिए कहता है। इससे connection harmless नहीं हो जाता। Bind address `127.0.0.1` listener को local host तक सीमित करता है, जबकि `db-admin.internal.example:5432` requested destination बताता है। ये अलग properties हैं और approval record में दोनों होनी चाहिए।

Remote forward तभी उचित है जब किसी ने remote side पर listener बनाने का स्पष्ट निर्णय लिया हो। नीचे दी गई command bastion को उसके loopback interface पर listen करने और traffic को agent machine के port 8080 तक वापस ले जाने को कहती है:

```sh
ssh -N \
  -R 127.0.0.1:18080:127.0.0.1:8080 \
  agent-return-path@bastion.internal.example
```

Remote loopback listener को अपने आप सुरक्षित न मानें। Bastion पर चलने वाले local process को agent की source machine से कहीं अधिक access हो सकता है और shared bastion पर कोई दूसरा user भी वहाँ पहुँच सकता है। इसे अनुमति देने से पहले remote host के users और local processes की समीक्षा करें।

Autonomous agents के लिए dynamic forwarding को default रूप से अस्वीकार करें:

```sh
ssh -N -D 127.0.0.1:1080 agent@bastion.internal.example
```

इससे SOCKS listener शुरू होता है। Client बाद में SOCKS requests के ज़रिए destinations चुनता है, इसलिए SSH command में किसी एक destination का नाम नहीं होता। Teams इसे पसंद करती हैं क्योंकि इससे internal web interface को browser या test harness में जल्दी चलाया जा सकता है। लेकिन यह सुविधा unattended work के लिए ज़रूरी destination boundary हटा देती है। Reviewer किसी एक route को approve नहीं कर सकता और सामान्य SSH destination restrictions arbitrary SOCKS traffic के लिए उपयोगी allowlist व्यक्त नहीं कर सकतीं।

इस समस्या को लंबे approval form से हल न करें। Agent account के लिए dynamic forwarding deny करें। अगर task को कई destinations चाहिए, तो हर forward अलग define करें या services के आगे purpose-built proxy रखें, जिसमें अपना authentication और logs हों।

## Bind address तय करता है कि listener का इस्तेमाल कौन कर सकता है

Bind address security decision का हिस्सा है, क्योंकि इससे पता चलता है कि कौन-सी machines tunnel listener से connect कर सकती हैं। Loopback पर bind होने वाला forward और हर interface पर bind होने वाला forward एक ही target इस्तेमाल कर सकते हैं, फिर भी exposure पूरी तरह अलग होगा।

Local forward के लिए explicit loopback address इस्तेमाल करें:

```sh
-L 127.0.0.1:15432:db-admin.internal.example:5432
```

Default पर निर्भर न रहें। Bind address न देने पर OpenSSH आम तौर पर local forwards को loopback पर bind करता है, लेकिन explicit address review, logs और incident investigation को स्पष्ट बनाता है। इससे बाद में client configuration बदलने पर listener scope चुपचाप बदलने से भी बचाव होता है।

यह खतरनाक version है:

```sh
-L 0.0.0.0:15432:db-admin.internal.example:5432
```

यह client host के हर IPv4 interface पर port 15432 expose करता है। Client तक पहुँचने वाला कोई भी host tunnel का प्रयास कर सकता है। Shared subnet में मौजूद build runner internal database तक bridge बन सकता है, भले ही database खुद केवल bastion से connections स्वीकार करता हो।

IPv6 पर भी यही ध्यान दें। `::1` loopback है, जबकि `::` सभी IPv6 interfaces पर listen करता है। दोनों address families जाँचें। मैंने teams को `127.0.0.1` को सुरक्षित मानने के बाद यह पता लगाते देखा है कि automation ने अलग configuration path के ज़रिए IPv6 listener भी खोल दिया था।

Remote forwards के लिए SSH server यह नियंत्रित करता है कि कौन-से bind addresses स्वीकार किए जाएँ। `sshd_config` में `GatewayPorts` remote forward bind behavior को प्रभावित करता है। OpenSSH के अनुसार remote forwards requested address और server policy के अधीन default रूप से loopback पर bind होते हैं। जब तक broader remote listeners की समीक्षा की हुई वजह न हो, `GatewayPorts no` रखें। `GatewayPorts clientspecified` agent account को बहुत अधिक control देता है।

Forward खोलने के बाद उस machine पर listener देखें जो उसका owner है। macOS या Linux पर यह local check उपयोगी है:

```sh
lsof -nP -iTCP:15432 -sTCP:LISTEN
```

Output में `ssh` process और `127.0.0.1:15432` या `::1:15432` जैसा address दिखना चाहिए। अगर `*:15432` दिखे, तो job रोकें और command तथा client configuration की जाँच करें। यह command केवल local listener address साबित करती है। यह साबित नहीं करती कि दूर वाला endpoint intended destination तक ही सीमित है।

## Dedicated SSH account को allowed route व्यक्त करना चाहिए

Agent tunnels के लिए server-side restrictions वाला dedicated SSH account न्यूनतम उचित सीमा है। Client configuration file ऐसे agent के विरुद्ध enforcement नहीं कर सकती जो अपने command arguments बदल सकता है। Limits वहीं रखें जहाँ SSH server connection स्वीकार करता है।

Local-only forwarding account के लिए `sshd_config` में इस तरह के match block से शुरू करें:

```text
Match User agent-db-tunnel
    PasswordAuthentication no
    KbdInteractiveAuthentication no
    PubkeyAuthentication yes
    PermitTTY no
    X11Forwarding no
    AllowAgentForwarding no
    AllowStreamLocalForwarding no
    AllowTcpForwarding local
    PermitOpen db-admin.internal.example:5432
    GatewayPorts no
    PermitUserEnvironment no
```

`AllowTcpForwarding local` local forwards की अनुमति देता है और remote forwards रोकता है। `PermitOpen` उस destination का नाम बताता है जिसे account request कर सकता है। यह restriction महत्वपूर्ण है, क्योंकि इसके बिना local forward command bastion से reachable किसी भी host और port की ओर जा सकती है। `PermitOpen` के बिना database की ज़रूरत वाला agent admin API, cache, metadata service या किसी दूसरे SSH server तक भी forward माँग सकता है।

Hostnames का इस्तेमाल सावधानी से करें। SSH server destination host को resolve करता है, इसलिए `PermitOpen` में दिया नाम वहीं resolve होना चाहिए। उस नाम को stable और आपके infrastructure के ownership में रखें। अगर DNS name मनमाने addresses में बदल सकता है, तो configuration संकीर्ण दिखेगी, लेकिन वास्तविक destination उसके नीचे बदलती रहेगी। Fixed appliance के लिए IP address अधिक स्पष्ट हो सकता है, हालांकि disciplined internal DNS control और service ownership होने पर hostname भी ठीक है।

OpenSSH का `sshd_config(5)` manual `PermitOpen` को `host:port` के रूप में destination restriction बताता है। यह account को policy engine नहीं बनाता। इसका एक स्पष्ट काम है: ऐसी forwarding request अस्वीकार करना जिसका destination सूची में नहीं है। Account design भी इतना ही सटीक रखें।

इस account को यह सोचकर shell न दें कि «agent इसका इस्तेमाल नहीं करेगा»। क्षमता ही हटा दें। Dedicated account एक connection role के लिए होना चाहिए। अगर interactive use के विरुद्ध अतिरिक्त guard चाहिए, तो authorized public key पर forced command लगाएँ जो आगे कुछ न होने पर exit कर दे, लेकिन अपने environment में forwarding behavior के साथ इसे ज़रूर test करें। कुछ forced-command patterns और wrappers अपेक्षित SSH sessions में बाधा डालते हैं। खराब restriction अक्सर deadline के दबाव में पूरा block हटवा देती है।

Agent forwarding से भी बचें। `AllowAgentForwarding no` connected server को client-side SSH agent से authentication requests sign करने से रोकता है। SSH tunnel account को traffic ले जाना चाहिए, दूसरे hosts तक पहुँचने का stepping stone नहीं बनना चाहिए।

अगर target पर दो वैध services हैं, तो दो explicit `PermitOpen` entries लिखें। Wildcard का इस्तेमाल न करें और केवल एक account बचाने के लिए general bastion account पर न लौटें। एक अतिरिक्त account बनाना उस स्थिति को समझाने से सस्ता है जिसमें code agent पूरे internal subnet तक पहुँच सकता था।

## Temporary का अर्थ enforced expiry और साफ़ closure है

Tunnel तभी temporary है जब अच्छी नीयत के अलावा कोई व्यवस्था उसे बंद करे। SSH connection तब तक खुला रखता है जब तक client उसे बंद न करे, network fail न हो या timeout न आ जाए। Autonomous agent test के सफल, असफल या timeout होने के बाद भी process चला छोड़ सकता है।

हर job को agent के नियंत्रण से बाहर fixed maximum session duration दें। जहाँ उपलब्ध हो, job runner इसे `timeout` से लागू कर सकता है:

```sh
timeout 20m ssh -N \
  -o ExitOnForwardFailure=yes \
  -o ServerAliveInterval=30 \
  -o ServerAliveCountMax=3 \
  -L 127.0.0.1:15432:db-admin.internal.example:5432 \
  agent-db-tunnel@bastion.internal.example
```

`ExitOnForwardFailure=yes` job को तब आगे बढ़ने से रोकता है जब SSH requested listener नहीं बना पाता। इसके बिना agent आगे चलता रह सकता है और tunnel setup failure के बजाय confusing application error report कर सकता है। Server alive settings missed replies के बाद SSH को exit कराती हैं, जो तब मदद करता है जब client host network connectivity चुपचाप खो दे। ये 20 minute ceiling का विकल्प नहीं हैं।

macOS में GNU `timeout` command default रूप से नहीं आती। Runner की timeout facility, deadline वाले supervised process या ऐसा छोटा wrapper इस्तेमाल करें जो `TERM` भेजे और फिर process के exit होने की पुष्टि करे। Missing deadline की जगह ऐसा cron job न लगाएँ जो «old SSH» को kill करे। इससे असंबंधित काम भी रुक सकता है और आप यह साबित नहीं कर पाएँगे कि कौन-सा connection बंद हुआ।

Job को अपने cleanup path में भी tunnel बंद करना चाहिए। जिस process को उसने शुरू किया है उसका PID रखें, task पूरी होने पर `TERM` भेजें, थोड़ी देर प्रतीक्षा करें और फिर `lsof` या `ss` से verify करें कि listener हट गया है। Deadline crashes पकड़ती है, cleanup path सामान्य completion संभालता है।

Connection multiplexing में विशेष सावधानी चाहिए। `ControlMaster` और shared `ControlPath` के साथ अलग SSH invocations एक master connection reuse कर सकती हैं। Human terminal session में इससे setup time बचता है। Agent work में ownership और expiry धुंधली हो जाती है: एक job किसी दूसरी job के लिए approved connection inherit कर सकती है और एक client बंद करने से master बंद नहीं हो सकता। Tunnel account के लिए multiplexing बंद करें या हर approved job के लिए unique control path बनाएँ।

अगर maintenance workflow को recurring access चाहिए, तो recurring लेकिन अलग-अलग सीमित sessions माँगें। केवल इसलिए tunnel खुला न छोड़ें कि उसे फिर बनाना असुविधाजनक लगता है। Setup में बचाए गए कुछ seconds हर investigation में ambiguity बन जाते हैं।

## Approval में वह connection लिखा होना चाहिए जिसे reviewer फिर से बना सके

Approval record से reviewer को यह समझ आना चाहिए कि route किसने authorize किया, उसे किसने खोला और क्या वह बताई गई सीमा में रहा। «approved agent access» जैसा generic ticket comment evidence नहीं है। उसमें listener, destination या duration की जानकारी नहीं होती।

Tunnel शुरू होने से पहले approval रिकॉर्ड करें। Human approver की identity, timestamp, job या run identifier, agent process identity, source machine, dedicated SSH account और public key fingerprint दर्ज करें। फिर requested forward पूरी तरह दर्ज करें: direction, bind address, listener port, target host, target port, purpose और expiry।

Result record में अलग fields चाहिए। SSH ने forward बनाया या नहीं, client process identifier, start time, final exit status, close time और closure का कारण शामिल करें। Timeout, normal cleanup, operator revocation और network failure review में अलग अर्थ रखते हैं।

Prose के बजाय structured records इस्तेमाल करें, ताकि किसी को fields का अनुमान न लगाना पड़े। कई internal systems के लिए यह shape पर्याप्त है:

```json
{
  "run_id": "run-7c31",
  "approved_by": "oncall-engineer",
  "approved_at": "2025-04-12T14:03:00Z",
  "ssh_account": "agent-db-tunnel",
  "direction": "local",
  "listen": "127.0.0.1:15432",
  "destination": "db-admin.internal.example:5432",
  "expires_at": "2025-04-12T14:23:00Z",
  "purpose": "run migration compatibility check"
}
```

इस example के identifiers placeholders हैं, कोई अनिवार्य schema नहीं। Field names से अधिक महत्वपूर्ण discipline है: एक record में person, process, route और time window को एक साथ बाँधें।

उसी agent को approval generate करके उसे human authorization न मानने दें। Agent बता सकता है कि उसे connection क्यों चाहिए, लेकिन allow decision किसी human या अलग governed workflow को लेना चाहिए। अगर action इतना routine है कि automatic approval उचित है, तो उस decision को expiry वाले narrow job definition में लिखें और अनुमति देने वाले rule का version भी सुरक्षित रखें।

Approval fatigue design failure है। हर packet को approve करने को कहना meaningless clicks पैदा करता है। हर temporary और specific route के लिए एक approval माँगने से approver के पास ठोस निर्णय लेने के लिए कुछ होता है। Prompt छोटा रखें, लेकिन bind address और exact destination ज़रूर दें। ये दो strings कई गलत requests पकड़ लेती हैं।

## Credentials agent से बाहर और authority उसके prompt से बाहर रखें

Autonomous agent को private key देने के बजाय connection request करने दें, ऐसी key नहीं जो बाद में arbitrary connections बना सके। Agent के process environment या workspace में broad SSH key पहुँचते ही वह key copy कर सकता है, दूसरा SSH client चला सकता है, approved job के बाद उसका इस्तेमाल कर सकता है या उसे किसी ऐसे tool में दे सकता है जिस पर आपने भरोसा करने की योजना नहीं बनाई थी। Prompt में «इसे केवल database के लिए इस्तेमाल करो» लिखने से इनमें से कोई action सीमित नहीं होता।

बेहतर design में SSH credentials अलग execution boundary में रहते हैं। Agent route fields के साथ requested action भेजता है। Boundary जाँचती है कि vault उपलब्ध है, session authorized है और requested credential के लिए आवश्यक approval मौजूद है। फिर वह SSH action करती है और private key agent को दिए बिना result लौटाती है।

Sallyport macOS पर इसी model का इस्तेमाल करता है: उसका bundled `sp-ssh` helper SSH actions करता है, जबकि SSH keys app के encrypted vault में रहती हैं और MCP agent तक नहीं पहुँचतीं।

Failure review करते समय यह अंतर महत्वपूर्ण है। Credential custody का सवाल है, «क्या agent secret को कहीं और reuse कर सकता था?» Route restriction का सवाल है, «क्या यह account unapproved destination तक पहुँच सकता था?» Approval का सवाल है, «इस खास use की अनुमति किसने दी?» Teams अक्सर इन तीनों को एक control में मिला देती हैं और फिर पाती हैं कि audit record में key के इस्तेमाल की जानकारी तो है, लेकिन key ने कौन-सा route सक्षम किया, यह नहीं।

Request interface को raw SSH arguments से संकीर्ण रखें। `destination_host`, `destination_port`, `listen_address`, `listen_port` और `max_duration` जैसे fields स्वीकार करें। Proxying, forwarding direction, remote command execution, identity files या control sockets बदलने वाले client-supplied options अस्वीकार करें। अगर arbitrary `ssh` command string स्वीकार की, तो policy interpretation string parsing को सौंप दी। कोई `-R`, `-D`, `ProxyCommand` या दूसरा forward जोड़ते ही यह तरीका विफल हो जाता है।

Tunnel workflow के escape hatch के रूप में general shell न दें। जब action में एक constrained channel बनाना ही शामिल है, तो agent को interactively `ssh` चलाने की ज़रूरत नहीं। Narrow actions को approve और revoke करना आसान होता है।

## Audit evidence को hostile review में भी टिकना चाहिए

Tunnel logs तभी उपयोगी हैं जब compromised agent या local process बाद में उन्हें बदल न सके। Agent workspace के पास रखे plain text logs सुविधाजनक हैं, लेकिन जब वही principal tunnel command और history दोनों edit कर सकता हो, तो वे बहुत कम साबित करते हैं।

Approval event, request event, execution result और closure result को shared run identifier के साथ अलग events के रूप में log करें। केवल rendered command line के बजाय original request fields सुरक्षित रखें। Command line config file में छिपे values को छिपा सकती है या listener को प्रभावित करने वाले defaults छोड़ सकती है।

एक से अधिक जगहों से corroborating records लें। Bastion के SSH logs accepted authentication और forwarding failures दिखा सकते हैं। Job runner process creation और exit दिखा सकता है। Destination service bastion से आया connection दिखा सकती है। इनमें से कोई अकेला पूरी घटना नहीं समझाता, लेकिन साथ में contradictions दिखाई देती हैं।

उदाहरण के लिए, approval 20 minutes के लिए `127.0.0.1:15432` से एक database तक route authorize कर सकता है। अगर client log कहता है कि वह 14:23 पर बंद हुआ, लेकिन destination को 15:10 पर bastion से traffic दिखता है, तो तुरंत जाँच करें। Agent ने दूसरा connection खोला हो सकता है, shared master session reuse किया हो सकता है या कोई उसी account से किसी दूसरे path का इस्तेमाल कर रहा हो।

Sallyport encrypted और hash-chained audit log से session तथा individual call journals project करता है, और `sp audit verify` vault key की आवश्यकता के बिना offline उस chain की जाँच करता है। यह उस log file से मजबूत property है जिसे agent process बदल सकता है, हालांकि इससे कमजोर account restrictions सही नहीं हो जातीं।

Verification केवल incident के बाद नहीं, review के दौरान भी चलाएँ। Tamper-evident chain बताती है कि recorded history में continuity है। यह नहीं बताती कि शुरुआत में पर्याप्त fields रिकॉर्ड किए गए थे या नहीं। खराब दिन में आपको क्या जानना होगा, पहले तय करें और connection शुरू होने से पहले वे fields event में आना सुनिश्चित करें।

## कहानी की जाँच से पहले path revoke करें

जब आपको किसी गलत या abused tunnel का संदेह हो, तो पहले connection path रोकें। Investigation कुछ मिनट प्रतीक्षा कर सकती है; exposed route उस दौरान data भेज सकता है या lateral access दे सकता है, जबकि लोग यह तय कर रहे हों कि कौन-सा log authoritative है।

Client process terminate करें और पुष्टि करें कि उसका listener गायब हो गया है। Dedicated SSH account disable करें या accepting server पर उसकी public key हटा दें। अगर tunnel के ज़रिए ऐसा credential गया हो जो untrusted process के सामने आ सकता था, तो service की अपनी प्रक्रिया के अनुसार उसे rotate करें। केवल tunnel मौजूद होने के कारण असंबंधित credentials rotate न करें।

फिर evidence सुरक्षित करें। Approval record, requested route, process logs, SSH server logs, destination service connection records और closure result बचाएँ। समय को एक ही timezone में दर्ज करें, बेहतर होगा UTC में। Incident review में लोग तब घंटों खो देते हैं जब job runner local time, bastion UTC और application बिना timezone के timestamps इस्तेमाल करती है।

Event को contained घोषित करने से पहले दूसरे listener की जाँच करें। Agent host पर requested port और आसपास चल रहे forwarding processes देखें। Bastion पर dedicated account के active SSH sessions और authentication logs देखें। Destination पर intended expiry के बाद शुरू हुए connections खोजें। लक्ष्य जल्दी से साफ narrative बनाना नहीं, बल्कि बचे हुए access paths ढूँढना है।

अगर human approval ने गलत route enable किया, तो पहले request surface ठीक करें। Arbitrary SSH flags स्वीकार करने वाली बेहतर review screen वही गलती दोहराएगी। अगर server restriction विफल रही, तो repair के बाद exact rejected command test करें और failure output रखें। Controls पर भरोसा तब बनता है जब आप पुष्टि करते हैं कि वे वही route deny करते हैं जिसे deny करना था।

## Agent के tunnel पर निर्भर होने से पहले denial paths test करें

Tunnel design तभी तैयार है जब approved connection और forbidden variations दोनों test हो जाएँ। Happy path tests typos पकड़ते हैं। Denial tests बताते हैं कि boundary वास्तव में मौजूद है या नहीं।

Nonproduction account से ऐसा forward आज़माएँ जिसे काम करना चाहिए:

```sh
ssh -N -o ExitOnForwardFailure=yes \
  -L 127.0.0.1:15432:db-admin.internal.example:5432 \
  agent-db-tunnel@bastion.internal.example
```

फिर ऐसी destination आज़माएँ जिसकी अनुमति `PermitOpen` नहीं देता:

```sh
ssh -N -o ExitOnForwardFailure=yes \
  -L 127.0.0.1:15433:admin-api.internal.example:443 \
  agent-db-tunnel@bastion.internal.example
```

दूसरी command forwarding setup के दौरान fail होनी चाहिए। Client output और server log entry दोनों capture करें। इसके बाद `-R` और `-D` आज़माएँ। `AllowTcpForwarding local` वाले account के लिए दोनों fail होने चाहिए। अंत में `0.0.0.0` के साथ nonloopback listener माँगें और पुष्टि करें कि SSH चलने से पहले आपका request boundary उसे reject करती है, या local environment वह exposure रोकता है जिसे आप रोकना चाहते हैं।

Expiry को छोटी duration के साथ test करें। Approved tunnel शुरू करें, supervisor deadline आने दें और तीन चीज़ों की पुष्टि करें: SSH process exit हो, `lsof` listener न खोज पाए और closure record deadline का नाम दे, task के सामान्य रूप से समाप्त होने का झूठा दावा न करे। यह छोटा exercise production work के किसी ऐसे tunnel पर निर्भर होने से पहले orphaned process की समस्या पकड़ लेता है जो कभी बंद नहीं होता।

दबाव बढ़ने पर एक सरल rule रखें: autonomous agent को current job के लिए आवश्यक minimum route ही मिले, bounded time के लिए और named approval के तहत। अगर आपका setup उस route को एक line में बता और बाद में उसका closure prove नहीं कर सकता, तो agent के पास task से अधिक network authority है।
