# MCP process trees: अनपेक्षित child processes खोजें

स्थानीय MCP servers की वजह से साफ protocol boundary को साफ execution boundary समझना आसान हो जाता है। Agent tool call भेजता है, server result लौटाता है और transcript सीमित दिखाई देता है। इस बीच server shell, package runner, language runtime, compiler, SSH client या project directory में डाउनलोड किया गया helper शुरू कर सकता है। Process tree बताता है कि काम वास्तव में कहां गया।

Review के लिए मैं MCP server को एक process नहीं मानता। Server और उसके द्वारा शुरू किए गए हर descendant को तब तक एक execution family मानता हूं, जब तक वे descendants बंद न हो जाएं। यह नियम उन समस्याओं को पकड़ता है जिन्हें tool transcripts अक्सर नहीं दिखाते: ऐसा argument जिसने shell से कुछ और चलवा दिया, `PATH` के जरिए चुना गया helper, API token अपने पास रख लेने वाला child, या ऐसा लंबे समय तक चलने वाला program जिसने मूल request खत्म दिखने के बाद socket खोल लिया।

## एक tool call कई स्थानीय programs में बदल सकती है

MCP server अक्सर सामान्य कारणों से child processes शुरू करता है। Repository tool `git` चला सकता है, code tool formatter बुला सकता है, infrastructure tool `ssh` चला सकता है और package-aware server runtime के बाद package executable शुरू कर सकता है। Child का मौजूद होना breach का प्रमाण नहीं है।

काम की बात **expected helper** और **unaccounted execution path** में फर्क करना है। Expected helper का documented काम, ज्ञात executable location, request से मेल खाते arguments और काम के अनुरूप lifetime होता है। Unaccounted path इनमें से किसी एक अपेक्षा को तोड़ता है। वह फिर भी harmless हो सकता है, लेकिन credential, filesystem access या network reachability देने से पहले उसका कारण समझना ज़रूरी है।

केवल parent PID पर्याप्त नहीं है। Unix process creation एक lineage बनाता है, security contract नहीं। Descendant को अक्सर parent का user ID, current working directory, environment variables, resource limits और कभी-कभी खुले file descriptors मिल जाते हैं। सटीक inheritance इस बात पर निर्भर करती है कि parent उसे कैसे शुरू करता है, लेकिन child को आम तौर पर इतना context मिल जाता है कि वह महत्वपूर्ण जगहों पर parent की तरह काम कर सके।

इसीलिए «MCP server केवल एक command चलाता है» कोई उपयोगी assurance नहीं है। `tool-server` `/bin/sh -c` शुरू कर सकता है, जो `node` शुरू करे, जो package script चलाए, जो `curl` शुरू कर दे। केवल `tool-server` दर्ज करने वाला reviewer chain के सबसे कम महत्वपूर्ण हिस्से को document कर रहा है।

POSIX का `exec` model इस जोखिम को स्पष्ट करता है। Process अपना image किसी दूसरे program से बदल सकता है और PID वही रह सकता है। इसलिए request से पहले और बाद में ली गई साधारण process list थोड़े समय के लिए चलने वाले intermediary को छोड़ सकती है। जब task पर्याप्त संवेदनशील हो, तो tree view और event evidence दोनों चाहिए।

## Agent को काम देने से पहले baseline बनाएं

Baseline उन children का रिकॉर्ड है जिन्हें server किसी साधारण, स्वीकृत request के दौरान शुरू करता है। इससे बाद के observations की तुलना की जा सकती है। इसके बिना हर interpreter संदिग्ध लगता है और हर warning पर बहस होने लगती है कि सामान्य क्या है।

Server को अलग Terminal session में manually शुरू करें। उसका PID तुरंत दर्ज करें, फिर पूरी command lines के साथ process table लें:

```sh
ps -axo pid,ppid,user,etime,stat,command > mcp-process-baseline.txt
```

Columns महत्वपूर्ण हैं। `PID` process की पहचान करता है, `PPID` direct parent बताता है, `USER` बताता है कि account किसका है, `ETIME` चलने का समय देता है और `STAT` stopped या zombie process दिखा सकता है। `COMMAND` उतनी ही पूरी होगी जितनी operating system रिपोर्ट कर सके, लेकिन shell wrapper, temporary path या अनपेक्षित flag खोजने की यह पहली जगह है।

macOS में `ps` के साथ universal tree display उपलब्ध नहीं होता। `pgrep` से direct children निकाले जा सकते हैं:

```sh
pgrep -P 48192 -alf
```

`48192` को server PID से बदलें। सामान्य output कुछ ऐसा दिखता है:

```text
48207 /usr/bin/python3 /Users/me/tools/format_request.py
48211 /usr/bin/ssh -o BatchMode=yes build.example
```

फिर हर child PID के लिए यही command दोहराएं, जब तक कोई नया descendant न मिले। यह काम थोड़ा tedious है और one-off review में सचमुच ऐसा लग सकता है। लेकिन इससे documentation के किसी diagram पर भरोसा करने के बजाय वास्तविक chain दिखाई देती है। अगर आपने अपने सामान्य package process से `pstree` इंस्टॉल किया है, तो `pstree -p 48192` अधिक पढ़ने योग्य snapshot देता है। Display tool को security claim का हिस्सा न बनाएं। वह केवल typing बचाता है।

कुछ expected requests चलाएं: read-only repository query, formatter action और यदि server उसे support करता है तो SSH action। हर request का snapshot save करें। Executable paths, सामान्य arguments, working directories और helpers बंद होते हैं या नहीं, यह लिखें। Build request के दौरान दिखने वाला compiler अपेक्षित हो सकता है। Text file का सारांश बनाने वाली request के दौरान वही compiler deviation है।

Mutable wrappers और package scripts से भरे development checkout के आधार पर baseline बनाकर उसे trusted न कहें। वह केवल बताता है कि आपकी मौजूदा machine इस समय क्या चला रही है। यह नहीं बताता कि server को क्या चलाने की अनुमति होनी चाहिए। Expected executable paths अलग से लिखें।

## Shell wrappers उस process को छिपा देते हैं जिसे आप देखना चाहते हैं

Dynamic commands को आसानी से जोड़ने के लिए shell invocation लोकप्रिय है। लेकिन इससे argument boundary text में बदल जाती है, और text quoting, expansion, globbing, redirects, command substitution और shell functions के जरिए अर्थ हासिल कर सकता है। Server लेखक formatter चलाना चाहता है, जबकि वास्तविक child process ऐसी shell शुरू कर देता है जो तय करती है कि formatter command का अर्थ क्या होगा।

यह reviews को गुमराह करने वाला आम स्रोत है। कोई source code में allowlisted binary name देखता है और मान लेता है कि वही binary चली। Runtime evidence `/bin/sh -c ...` दिखाता है और shell बाकी command को बाद में resolve करती है। ये दो अलग दावे हैं।

Server implementation में इन दो patterns की तुलना करें:

```js
spawn("/usr/bin/git", ["status", "--short"], {
  cwd: repositoryPath,
  shell: false
});
```

```js
exec(`git -C ${repositoryPath} status --short`);
```

पहला pattern executable और arguments को अलग रखता है। फिर भी `repositoryPath` की validation ज़रूरी है, लेकिन shell parsing से बचा जा सकता है। दूसरे pattern में quoting गलत होने पर `repositoryPath` command grammar बदल सकता है। इसमें tree में shell process भी जुड़ता है, जो final command शुरू होने से पहले और processes बना सकता है।

«हम input sanitize करते हैं» को argument vector का विकल्प न मानें। Options बढ़ने के साथ sanitizers कमजोर पड़ते हैं। Developer feature जोड़ते हैं, spaces की अनुमति देते हैं, conditional flag जोड़ते हैं और पुराना filter command grammar को describe करना बंद कर देता है। Explicit executable और arguments की array code और audit output दोनों में boundary साफ दिखाते हैं।

अगर server को वास्तव में shell syntax चाहिए, तो उसे सीमित करें। Agent-writable workspace के बाहर रखी fixed script इस्तेमाल करें, positional parameters के जरिए data दें और deployment notes में script का path तथा digest दर्ज करें। Tool input से shell line बनाकर उसे केवल इसलिए expected child न कहें कि वह आपकी source tree से निकली है।

Package tooling इसका असुविधाजनक उदाहरण है। Package runners जैसे commands अक्सर project द्वारा दिए गए lifecycle scripts चलाते हैं। इसलिए agent-edited checkout के भीतर package command चलाने वाला server project configuration में agent द्वारा रखे गए commands भी शुरू कर सकता है। यह package manager की गलती नहीं है। यह आपका execution decision है, जिसमें आपने mutable repository को trusted instructions का स्रोत मान लिया।

## PATH और working directory command का अर्थ बदल देते हैं

`git` executable identity नहीं है। यह lookup request है। Process `PATH` खोजकर इसे resolve करता है, और अगर server project-local directories या inherited shell configuration शामिल करता है तो agent-controlled repository उस search को प्रभावित कर सकती है। ऐसी directory में मौजूद `git` नाम वाली file `/usr/bin/git` से पहले चल सकती है।

सिर्फ tool transcript में दिखाई गई command नहीं, बल्कि process को मिला environment भी inspect करें। अपने process के लिए macOS के कई versions में `ps` से environment information पूछी जा सकती है, हालांकि availability अलग हो सकती है। व्यावहारिक तरीका है कि server startup पर जानबूझकर छोटा और redacted environment log करे: `PATH`, `HOME`, `TMPDIR`, current directory और fixed executables के paths। Access tokens, session cookies या पूरा environment dump shared project log में न लिखें।

संवेदनशील commands को absolute paths से resolve करें। Agent working tree बदल सकता हो तो यह केवल औपचारिकता नहीं है। उदाहरण के लिए:

```sh
/usr/bin/git -C /Users/me/work/repo status --short
/usr/bin/ssh -o BatchMode=yes -o IdentitiesOnly=yes host.example
```

Absolute paths एक lookup समस्या हटाते हैं। वे target को सुरक्षित नहीं बनाते। कुछ configurations में `git` hooks या external diff programs चला सकता है। SSH configuration पढ़ सकता है और helpers बुला सकता है। Working directory relative file reads, project configuration और temporary outputs को भी नियंत्रित करती है। Child inspect करते समय इसे दर्ज करें।

Process की current directory और खुले files देखने के लिए `lsof` इस्तेमाल करें:

```sh
lsof -nP -p 48207 | sed -n '1,35p'
```

File descriptor column में `cwd`, `txt` के नीचे executable और project, temporary directories या credential locations की files खोजें। `lsof` snapshot देता है। तेज़ी से चलकर बंद होने वाला child आपके command चलाने से पहले गायब हो सकता है, लेकिन output ऐसे server को पकड़ने में अच्छा है जो चुपचाप helper को जीवित रखता है।

`/private/var/folders/...` से शुरू हुआ child managed application directory से शुरू हुए child की तुलना में अधिक जांच मांगता है, खासकर जब उसका नाम किसी सामान्य utility जैसा हो। Temporary locations build products के लिए उचित हैं। वे disposable files के बीच किसी process को छिपाने के लिए भी सुविधाजनक हैं। पूछें कि इसे किस component ने बनाया और वहां executable की ज़रूरत क्यों पड़ी।

## Network activity को requested action से मेल खाना चाहिए

स्थानीय child MCP request की boundary से बाहर पहुंच सकता है, भले ही वह कोई संदिग्ध file न लिखे। Formatter को आम तौर पर outbound connection नहीं खोलना चाहिए। SSH helper को requested host से connect होकर exit करना चाहिए। Package installation registries से संपर्क कर सकती है, लेकिन इसकी explicit review ज़रूरी है क्योंकि यह नया code fetch और execute कर सकती है।

Server और पर्याप्त समय तक चलने वाले हर child के sockets inspect करें:

```sh
lsof -nP -i -p 48211
```

Output में आम तौर पर protocol, local address, remote address और connection state होते हैं। `-nP` flags name और service lookups रोकते हैं, जिससे output literal रहता है और जांच के दौरान अतिरिक्त resolver traffic नहीं होता। `LISTEN` का अर्थ है कि process local या network connections स्वीकार करता है। `ESTABLISHED` का अर्थ है कि उसका active peer है। हर entry को उस action से मिलाएं जिसने उसे पैदा किया।

हर network library process पर तुरंत प्रतिक्रिया न दें। कुछ developer tools update services, certificate status या dependency metadata जांचते हैं। फिर भी मैं इसे server को और सीमित करने का कारण मानता हूं। जो tool call local code पढ़ने का दावा करती है, उसे केवल इसलिए undeclared outbound channel नहीं मिलना चाहिए कि किसी helper को यह सुविधाजनक लगा।

Credential injection को process observation से अलग रखें। Process inspection बता सकता है कि `curl` चला। वह यह गारंटी नहीं दे सकता कि environment variable में दिया गया token copy, log या grandchild को inherit नहीं हुआ। आम सलाह है, «एक command के लिए API key child environment में डाल दें।» Demo में यह आसान और काम करने वाला तरीका है। Agent-run actions के लिए यह गलत है, क्योंकि child अपना environment print कर सकता है, उसे आगे भेज सकता है या visible command लौटने के बाद भी जीवित रह सकता है।

Sallyport अपने HTTP और SSH channels के लिए अलग boundary रखता है: agent को stored API या SSH credential नहीं मिलता और app खुद action करती है। इससे local MCP children को inspect करने की ज़रूरत खत्म नहीं होती, लेकिन हर helper process को secret रखने वाला संभावित process बनने से रोका जा सकता है।

## Failure अक्सर एक सुविधाजनक wrapper से शुरू होती है

एक local repository MCP server की कल्पना करें, जिसमें `run_test` tool है। लेखक flexible command चाहता है, इसलिए handler repository में जाकर package command के जरिए project-defined test script चलाता है। Agent को checkout edit करने की अनुमति है, क्योंकि code edit करना ही उसका काम है।

Agent proposed fix के हिस्से के रूप में project script बदल देता है। Server test tool चलाता है। Package command shell शुरू करती है, shell runtime शुरू करती है, runtime modified script चलाता है। Script output को temporary file में redirect करके background helper शुरू करती है। Foreground command सफलतापूर्वक समाप्त होती है, इसलिए tool «tests passed» लौटाती है।

इस sequence के लिए किसी exotic exploit की ज़रूरत नहीं है। समस्या यह है कि server ने agent-writable project metadata को approved executable configuration मान लिया। Reviewer को केवल मूल MCP request और success result दिख सकता है। Process tree असली कहानी दिखाता है:

```text
mcp-repo-server(48192)
  package-runner(48230)
    sh(48233)
      runtime(48234)
        test-script(48240)
          helper(48247)
```

Background helper जांच का मुख्य बिंदु है। उसकी पूरी command line, executable path, parent chain, working directory, खुले files, sockets और start time देखें। जांचें कि server session समाप्त होने के बाद भी वह जीवित रहता है या नहीं। फिर उस project change को inspect करें जिसने script दी। इसे केवल agent failure न कहें। Server ने mutable instructions पर execution दिया और result को test कहा।

Repair intended product behavior पर निर्भर करता है। सावधान server fixed arguments के साथ fixed test executable चला सकता है। अगर project-defined scripts ज़रूरी हों, तो script file और package metadata को executable inputs मानें: उन्हें approval के लिए दिखाएं, constrained environment में चलाएं और जहां संभव हो backgrounding रोकें। कम से कम server को अपने द्वारा शुरू किए गए हर descendant की रिपोर्ट करनी चाहिए, जिसमें वह भी शामिल है जो tool call से अधिक समय तक चलता है।

मुख्य फर्क यह है कि agent server से known test command चलाने को कह रहा है या server के चलाने से पहले test command का अर्थ बदल रहा है। MCP transcript में दोनों `run_test` जैसे दिख सकते हैं। उनका जोखिम बहुत अलग है।

## केवल snapshots नहीं, process births भी देखें

Snapshots बताते हैं, «अभी क्या जीवित है?» वे यह नहीं बताते कि «200 milliseconds तक क्या चला और बंद हो गया?» संदिग्ध या संवेदनशील tool calls के लिए request चलने के दौरान process births देखें।

macOS में required entitlement वाले security products के लिए Endpoint Security शामिल है, लेकिन सामान्य local tooling इस पर भरोसा नहीं कर सकती। जब तक आप यह capability वास्तव में ship और operate नहीं करते, privileged event collection पर निर्भर design न बनाएं। Developer investigation के लिए executable के चारों ओर controlled wrapper रखें और start तथा exit details collect करें, या अपने environment में उपलब्ध process monitor के तहत server चलाएं।

जब server का command path आपके नियंत्रण में हो, तो simple wrapper execution को स्पष्ट बना सकता है:

```sh
#!/bin/sh
printf '%s pid=%s ppid=%s cwd=%s argv=%s\n' \
  "$(date -u +%Y-%m-%dT%H:%M:%SZ)" "$$" "$PPID" "$(pwd)" "$*" \
  >> "$HOME/.local/state/mcp-exec.log"
exec /usr/bin/git "$@"
```

यह artifact wrapper की identity दर्ज करता है और फिर `exec` के जरिए wrapper को `git` से replace कर देता है। इससे wrapper एक बेकार parent की तरह बना नहीं रहता। यह `git` द्वारा बनाए जा सकने वाले हर descendant को capture नहीं करता और इसे arguments में secrets नहीं मिलने चाहिए। इसे controlled hypothesis जांचने के लिए इस्तेमाल करें, complete audit system के रूप में नहीं।

मौजूदा process के लिए macOS का `dtruss` system calls दिखा सकता है, लेकिन इसके लिए अक्सर elevated privileges चाहिए और output बहुत अधिक होता है। यह investigation tool है, routine monitoring का साधन नहीं। Process tree, `lsof` और application logs से शुरुआत करें। जब किसी specific सवाल का जवाब चाहिए, जैसे child ने दूसरा path execute किया या socket से connect किया, तभी syscall tracing इस्तेमाल करें।

Timestamps UTC में रखें और हर event के साथ parent PID दर्ज करें। Parent और time के बिना command line कमजोर evidence है। Process बंद होने के बाद PID reuse हो सकता है, इसलिए देर से लिया गया snapshot गलती से नए, असंबंधित process को पुराने incident से जोड़ सकता है। `ps` का process start time या अपना event log इस गलती से बचने में मदद करता है।

## Approval में executable boundary स्पष्ट होनी चाहिए

«Allow run_test?» जैसी confirmation human को बहुत कम जानकारी देती है। मंज़ूरी देने वाले व्यक्ति को पता होना चाहिए कि action किस signed process ने शुरू किया, कौन-सा executable चलेगा, working directory क्या है, action network तक पहुंच सकती है या नहीं और क्या वह आगे programs शुरू कर सकती है। अस्पष्ट approval card लोगों को अस्पष्ट actions approve करना सिखाता है।

हर `fork` call के लिए prompt दिखाकर समस्या हल करने की कोशिश न करें। इससे approval fatigue पैदा होगी, फिर users mechanically approve करेंगे या prompts बंद कर देंगे। महत्वपूर्ण transitions की समीक्षा करें: नया executable path, local work से network access की ओर बदलाव, mutable project file से आया command या ऐसा child जो request खत्म होने के बाद भी जीवित रहे।

macOS पर code signing information उपयोगी identity signal देती है, लेकिन इसे बढ़ा-चढ़ाकर न बताएं। Executable inspect करें:

```sh
codesign -dv --verbose=4 /path/to/executable 2>&1 | sed -n '1,20p'
```

Command मौजूद होने पर signing authority बताती है और unsigned code के लिए error देती है। इससे पता चलता है कि macOS द्वारा inspect की गई file को किसने sign किया। इससे यह पता नहीं चलता कि command arguments सुरक्षित हैं, configuration trustworthy है या वह बाद में unsigned script चलाएगी। Child path और execution context को अलग से inspect करें।

Sallyport का session authorization connecting process की code-signing authority से शुरू होता है, जिससे यह तय करने में मदद मिलती है कि नया agent process बिल्कुल action कर सकता है या नहीं। Per-call key setting उस संकरे मामले के लिए है जिसमें किसी खास credential use पर हर बार human decision चाहिए। इनमें से किसी control को इस दावे के लिए इस्तेमाल नहीं करना चाहिए कि parent की approval अपने आप उसके हर descendant को समझा देती है।

Approval language को consequences के आधार पर बनाएं। «यह request `/usr/bin/ssh` को आपके account के रूप में चलाएगी और `host.example` से connect करेगी» review की जा सकती है। «इस tool को access चाहिए» नहीं। अगर tool project scripts चला सकती है, तो यह बात सीधे लिखें। Concrete request होने पर humans informed decision ले सकते हैं।

## Child की जांच से पहले server को contain करें

अनपेक्षित child मिलने पर उसे समझाने के लिए पर्याप्त evidence सुरक्षित करें, फिर पूरी execution family रोक दें। Temporary files delete करने या reboot करने से शुरुआत न करें। इन actions से आपके पास मौजूद path, command line और timing evidence मिट सकता है।

Child अभी चल रहा हो तो यह क्रम अपनाएं:

1. Server, उसके parent और ज्ञात descendants का `ps` output capture करें। PIDs, PPIDs, elapsed time और पूरी commands दर्ज करें।
2. संदिग्ध process के लिए `lsof -nP -p <pid>` और `lsof -nP -i -p <pid>` चलाएं। Output agent workspace के बाहर save करें।
3. अगर सामान्य termination सुरक्षित है तो `kill <pid>` से suspicious descendant रोकें। वह बंद न हो और उसका चलते रहना अस्वीकार्य जोखिम पैदा करता हो तभी `kill -KILL <pid>` तक जाएं।
4. MCP server रोकें और वह agent session revoke या समाप्त करें जिसने action शुरू की थी। Server के बंद होने के बाद reparent किए गए children भी देखें।
5. Executable, उसका launch source और उस repository या configuration में हुए changes inspect करें जिनसे command बनी।

`kill` अपने आप process group या सभी children को समाप्त नहीं करता। Backgrounded descendant direct parent के बंद होने के बाद भी चल सकता है। इसलिए cleanup से पहले tree चाहिए। Controlled server में helpers को dedicated process group या supervision scope में रखें, ताकि session समाप्त होने पर server पूरा group रोक सके। इस पर भरोसा करने से पहले जानबूझकर backgrounded helper के साथ behavior test करें।

Persistence भी जांचें। macOS में किसी specific label या domain के साथ loaded launch services देखने के लिए `launchctl print` इस्तेमाल किया जा सकता है। केवल नाम अपरिचित लगने पर असंबंधित jobs को blindly unload न करें। पहले suspected child को उसके executable path, launch configuration और timestamps से मिलाएं। Clean reboot या fresh server start के बाद लौटने वाला process उस process से अलग investigation मांगता है जो केवल test session में मौजूद था।

## हर action को बाद में समझने योग्य बनाएं

एक उपयोगी audit record user-approved agent process को request, executed action और observed result से जोड़ता है। Process telemetry में executable path, parent chain, working directory, start और exit time तथा network activity का destination भी होना चाहिए। Record में process tree न हो तो यह पता नहीं चल सकता कि helper request का हिस्सा था या machine का कोई असंबंधित process।

Audit records को पूरा बनाने के लिए secrets उनमें न डालें। Credential names या opaque identifiers, reviewers के लिए आवश्यक request metadata और redaction के बाद action result रखें। Bearer tokens copy करके provenance हल करने वाला log कमजोर controls वाला दूसरा credential store बना देता है।

Incident के बाद tamper evidence महत्वपूर्ण है, क्योंकि local logs उसी account द्वारा बदले जा सकते हैं जिसने command चलाई थी। Sallyport अपने Sessions और Activity journals को encrypted, hash-chained audit log से project करता है, और `sp audit verify` vault key के बिना offline इस chain को check करता है। Agent action के लिए यह उपयोगी evidence है, लेकिन process records में फिर भी इतना context होना चाहिए कि local server ने क्या शुरू किया, यह समझाया जा सके।

Server की operating procedure में एक review question रखें: «इस request के कारण कौन-सा executable चला और इस working directory में उस executable के मौजूद होने की अनुमति क्यों थी?» अगर request record और process capture से कोई इसका जवाब नहीं दे सकता, तो tool का scope घटाएं। जो local MCP server अपने descendants का हिसाब नहीं दे सकता, उसके पास operators की सुरक्षित समीक्षा क्षमता से अधिक authority है।
