SSD Nodes Learn Hosting plans →
নির্দেশিকা Matt Connorদ্বারা Matt Connor

AI agent থেকে secret ও API key নিরাপদে রাখুন

একটি tool call-এই agent আপনার API key ফাঁস করতে পারে। আসল key না দিয়ে credential gateway-এর আড়ালে সীমিত-পরিসরের স্বল্পস্থায়ী token ব্যবহার করুন।

AI agent-এর কাছ থেকে secret বাইরে রাখার অর্থ

একটি AI agent হলো একটি সাধারণ Linux process, যা command চালায়। সেই process-এর কাছে থাকা প্রতিটি environment variable তার চালানো code পড়তে পারে। তাই agent-এর environment-এ থাকা একটি API key এমন একটি key, যা agent তার কাছে পৌঁছানো যেকোনো host-এ পাঠাতে পারে। Agent-এর কাছ থেকে secret বাইরে রাখার অর্থ হলো তাকে key না দিয়ে একটি handle দেওয়া: স্বল্পস্থায়ী, সীমাবদ্ধ-পরিসরের token, অথবা এমন একটি placeholder, যা network boundary-তে অন্য কোনো ব্যবস্থা আসল মান দিয়ে প্রতিস্থাপন করে।

এটি কোনো model hostile হয়ে যাওয়ার ঘটনা নয়। প্রক্রিয়াটি আরও সাধারণ। একটি agent এমন web page, README বা issue comment পড়ে যাতে নির্দেশনা থাকে, এবং সেগুলো অনুসরণ করে। কারণ language model-এর কাছে আপনার লেখা text এবং তার fetch করা text-এর মধ্যে কোনো পার্থক্য নেই। এটিই prompt injection। এটি ঘটার পরে ক্ষতির সীমা ঠিক একটি বিষয়ের ওপর নির্ভর করে: process কী পড়তে পারে। আপনি যদি এখনো কোনো boundary নির্ধারণ না করে থাকেন, server-এ coding agent নিরাপদে চালানো এই guide-এর ভিত্তি হিসেবে ব্যবহৃত isolation স্তরগুলো ব্যাখ্যা করে।

সহজ ভাষায় threat model

এটি আপনার agent যে user হিসেবে চলে, সেই user হিসেবে চালান।

tr '\0' '\n' < /proc/self/environ | grep -iE 'key|token|secret|password'

এটি যে প্রতিটি line প্রিন্ট করে, তা একজন অপরিচিত ব্যক্তির server-এ একটি HTTP request-এর মাধ্যমে পৌঁছানোর মতো। এখন agent-এর কাছাকাছি disk-এ কী আছে তা দেখুন।

grep -rIl --exclude-dir=.git -e 'API_KEY' -e 'SECRET' ~/projects
find ~ -maxdepth 3 -name '.env' -o -name 'credentials' -o -name '*.pem'

shell-সহ একটি agent-এর ওই data বাইরে পাঠাতে কোনো জটিল exploit দরকার হয় না। চারটি সাধারণ পথই যথেষ্ট, এবং log-এ চারটিকেই স্বাভাবিক কাজের মতো দেখায়:

  • যেকোনো host-এ outbound curl বা fetch, যেখানে value query string-এ থাকে।
  • এমন একটি repository-তে git commit এবং git push, যেখানে agent লিখতে পারে।
  • একটি package install script, যা agent-এর user হিসেবে নির্বিচারে code চালায়।
  • এমন hostname-এর DNS lookup, যার মধ্যে value থাকে; HTTP egress blocked থাকলেও এটি বাইরে চলে যায়।

শুধু review করে এই ঝুঁকি দূর করা যাবে না। সমাধান হলো, agent-এর নাগালের মধ্যে মূল্যবান কিছু না রাখা।

একটি secret working tree-তে থাকলে সেটি context window-তেও থাকে

একজন agent file পড়ে। যে repository-তে agent কাজ করছে, সেখানে থাকা একটি .env file পড়া হবে। পড়ার পর সেটি context window-তে চলে আসে। অর্থাৎ সেটি transcript-এ, আপনার সংরক্ষিত যেকোনো log-এ এবং agent পরবর্তী সময়ে যা লিখবে তাতেও থাকবে।

আগে, agent-এর working tree-তে key থাকা অবস্থায়:

cd ~/projects/billing
cat .env
# STRIPE_SECRET_KEY=sk_live_...
# DATABASE_URL=postgres://app:hunter2@db.internal:5432/billing

পরে, file-টি agent-এর নাগালের বাইরে সরিয়ে নেওয়ার পর:

sudo install -d -m 750 -o root -g agent-review /etc/agent-review
sudo install -m 640 -o root -g agent-review ~/projects/billing/.env /etc/agent-review/billing.env
rm ~/projects/billing/.env

এখন agent-এর user আর file-টি খুলতে পারে না, কারণ working tree-তে file-টি নেই। Agent-এর নিজস্ব config-এর deny rule দ্বিতীয় স্তরের সুরক্ষা, প্রথমটি নয়। Claude Code project-এর .claude/settings.json file থেকে permission rule পড়ে:

{
  "permissions": {
    "deny": ["Read(./.env)", "Read(./secrets/**)", "Read(./**/*.pem)"]
  }
}

এটি exploration-এর সময় agent-এর ভুল করে কোনো file খুলে ফেলা ঠেকায়। এই নিয়ম file tool-এর বাইরেও প্রযোজ্য। Claude Code Bash-এ শনাক্ত করা file command-এর ক্ষেত্রেও এই নিয়ম পরীক্ষা করে, যেমন cat এবং head। Redirection-এর target-এর ক্ষেত্রেও এটি প্রযোজ্য, যেমন < .env। তবে এটি কোনো injected instruction-কে project directory-এর ভেতরে grep -r KEY . চালানো থেকে আটকায় না। একইভাবে, নিজে .env খুলে ফেলা কোনো ছোট Python script-কেও এটি আটকায় না। কারণ permission documentation অনুযায়ী, file-এর নাম উল্লেখ না করে file পড়া command বা নিজেরা file খোলা subprocess-এর ক্ষেত্রে deny rule প্রযোজ্য নয়। এসব ক্ষেত্রে docs Claude Code-এর sandbox ব্যবহারের নির্দেশ দেয়। এটি operating system level-এ block কার্যকর করে। এমন command চালানোর আগে আপনাকে জিজ্ঞেস করা হবে কি না, তা session-এর permission mode-এর ওপর নির্ভর করে। interactive Claude Code session-এর built-in starting mode এখন auto mode, তাই আপনি যে server monitor করছেন না, সেখানে আরও বেশি command আপনার অনুমতি ছাড়াই চলতে পারে। Agent-এর permission-এর পরিবর্তে তার অভ্যাস নিয়ন্ত্রণ করে এমন ব্যবস্থার ক্ষেত্রেও একই সীমা প্রযোজ্য। যে skill agent-কে কাজের জন্য প্রয়োজনীয় সবচেয়ে ছোট পরিবর্তনে সীমাবদ্ধ রাখে, সেটি agent-কে অপ্রয়োজনীয় file খোলার দিকে চলে যাওয়া থেকে বিরত রাখে। তবে এটি এখনও এমন পরামর্শ, যা model-কে যুক্তি দিয়ে অমান্য করানো সম্ভব। Config-কে guardrail এবং filesystem permission-কে wall হিসেবে বিবেচনা করুন। Container-এর ভেতরেও একই বিভাজন প্রযোজ্য। Docker Compose-এ env file এবং secret এই সমস্যার এক স্তর নিচের সংস্করণ ব্যাখ্যা করে।

প্রতিটি agent-এর জন্য আলাদা unprivileged user দিন

agent আপনার user হিসেবে চললে এটি আপনার SSH key, cloud credential এবং shell history পেয়ে যায়। একটি আলাদা user তৈরি করতে মাত্র একটি command লাগে এবং এগুলোর সবকিছুর access বন্ধ হয়ে যায়।

sudo adduser --disabled-password --gecos "" agent-review
sudo chmod 700 /home/agent-review
sudo -u agent-review cat ~/.ssh/id_ed25519

শেষের line-টি cat: /home/you/.ssh/id_ed25519: Permission denied সহ ব্যর্থ হতে হবে। এর বদলে যদি একটি key print হয়, তাহলে agent-এর user ওই file-এ পৌঁছাতে পারছে। কোনো file-এর নাম ব্যবহার করে সেটি open করতে file-টির ওপর read permission নয়, বরং তার উপরের প্রতিটি directory-তে search (x) permission প্রয়োজন। তাই 711 mode-এর এমন home directory, যা অন্য কেউ list করতে পারে না, তবুও key file-টি readable হলে key-তে পৌঁছানোর পথ খোলা রাখতে পারে। chmod 700 ~ path-টি বন্ধ করে, আর chmod 600 ~/.ssh/id_ed25519 key-টির permission ঠিক করে। agent user-কে sudo-এ যোগ করবেন না এবং তার প্রয়োজনীয় একমাত্র command-এর চেয়ে বিস্তৃত কোনো NOPASSWD rule দেবেন না। VPS-এ least privilege user group এবং sudoers-এর বিস্তারিত ব্যাখ্যা করে। একাধিক session চালানোর পরও এই separation বজায় রাখুন, কারণ একটি Claude Code session সরাসরি অন্য session-এ text পাঠাতে পারে, এবং প্রথম session-এ থাকা যেকোনো তথ্য একটি message-এই ওই channel দিয়ে চলে যেতে পারে।

একটি cloud VPS-এ আরও একটি boundary যোগ করা দরকার। instance metadata service একটি নির্দিষ্ট link local address-এ response দেয় এবং যে কোনো requester-কে প্রায়ই role credential দেয়। কোন local user call করছে, endpoint-এ তা যাচাই করা হয় না। EC2-এর ক্ষেত্রে AWS-এর documentation অনুযায়ী IMDSv1 call-এর জন্য token প্রয়োজন হয় না। কোনো user-কে এই service থেকে দূরে রাখার জন্য AWS-এর নিজস্ব পরামর্শ হলো ওই user-এর ID ব্যবহার করে একটি local firewall rule তৈরি করা।

sudo iptables -I OUTPUT 1 -p tcp -m owner --uid-owner agent-review -d 169.254.169.254 -j REJECT

-I দিয়ে rule-টি শুরুতেই insert করুন; -A দিয়ে শেষে append করবেন না। default allow outgoing policy-সহ ufw চালু থাকা host-এ ufw-এর শেষ OUTPUT chain প্রতিটি নতুন TCP connection accept করে। তাই শেষে append করা rule-এ কখনো পৌঁছানো হয় না। reboot-এর পরে iptables rule-ও অদৃশ্য হয়ে যায়, যদি না আপনি সেগুলো persist করেন। metadata service-এর IPv6 endpoint চালু থাকলে, [fd00:ec2::254]-এর জন্য একই rule ip6tables দিয়ে যোগ করুন।

agent-এর দিক থেকে এটি পরীক্ষা করুন। sudo -u agent-review curl -s --max-time 3 http://169.254.169.254/-এর কোনো output থাকা উচিত নয় এবং এটি non zero exit করবে, কারণ packet box ছাড়ার আগেই reject করা হয়। যদি এটি একটি listing print করে, তাহলে rule-এ পৌঁছানো হচ্ছে না।

সীমারেখায় credential injection

যে পদ্ধতিটি বাস্তবে সমস্যার সমাধান করে, সেটি হলো credential injection। Agent কখনো আসল key ধারণ করে না। এটি একটি local gateway-এর মাধ্যমে request পাঠায়, এবং gateway বাইরে পাঠানোর সময় placeholder-এর বদলে আসল secret বসায়। Secret থাকে gateway-এর storage-এ, একটি ভিন্ন process-এ, এবং ভিন্ন user-এর মালিকানায়।

OneCLI এর একটি open source implementation। এর ee/ directory-গুলো ছাড়া এটি Apache-2.0 license-এর অধীনে প্রকাশিত; ee/ directory-গুলোর জন্য আলাদা enterprise license প্রযোজ্য, এবং production ব্যবহারের জন্য subscription প্রয়োজন। এটি agent-এর পাশে container-এ চলে। October 2026 অনুযায়ী, project-এর documentation-এ এই Docker Compose setup দেখানো হয়েছে। এটি compose file-এর পাশে থাকা একটি .env file থেকে তৈরি করা তিনটি secret পড়ে:

git clone https://github.com/onecli/onecli.git && cd onecli/docker
cat > .env <<EOF
SECRET_ENCRYPTION_KEY=$(head -c 32 /dev/urandom | base64)
GATEWAY_INTERNAL_SECRET=$(head -c 32 /dev/urandom | base64)
BETTER_AUTH_SECRET=$(head -c 32 /dev/urandom | base64)
COMPOSE_PROFILES=runner
EOF
chmod 600 .env
docker compose up -d --wait

ওই .env file-এর backup রাখুন, কারণ SECRET_ENCRYPTION_KEY হারালে এর সঙ্গে সংরক্ষিত প্রতিটি secret orphaned হয়ে যায়। Dashboard port 10254-এ এবং gateway port 10255-এ listen করে; default অবস্থায় উভয়ই 127.0.0.1-এ bind থাকে। আসল credential একবার সংরক্ষণ করুন। এরপর প্রতিটি agent-কে key-এর পরিবর্তে একটি placeholder value এবং তার নিজস্ব scoped access token দিন। Agent token-টি একটি Proxy-Authorization header-এ পাঠায়। Gateway host ও path দেখে outbound request-এর সঙ্গে মিলিয়ে নেয়, সংশ্লিষ্ট credential decrypt করে এবং সেটি বসিয়ে দেয়। Agent-এর environment-এ চুরি করার মতো কোনো তথ্য থাকে না।

এখানে মূল বিষয় encryption নয়। মূল বিষয় হলো, “এই agent কী ব্যবহার করেছে এবং কখন ব্যবহার করেছে” প্রশ্নটির উত্তর একটি log query-তে পাওয়া যায়। ছয়টি environment-এর কোনটিতে key-এর একটি copy ছিল, তা অনুমান করার বদলে আপনি একটি audit trail পরীক্ষা করেন।

গোপন তথ্য environment-এ নয়, process-কে দিন

আপনি যদি agent-টি systemd-এর অধীনে চালান, তাহলে environment variable একেবারেই প্রয়োজন হয় না। LoadCredential= গোপন তথ্যটি এমন একটি private directory-তে রাখে, যা শুধু service-এর user পড়তে পারে। Unit file-এ এটি %d হিসেবে এবং process-এর ভিতরে $CREDENTIALS_DIRECTORY হিসেবে প্রকাশিত হয়। এই সীমাবদ্ধতা service অনুযায়ী নয়, user অনুযায়ী কার্যকর হয়। তাই প্রতিটি agent service-এর জন্য আলাদা user ব্যবহার করুন। একই user হিসেবে চলা অন্য কোনো process-ও ওই directory খুলতে পারবে, যদি না unit-এ filesystem namespacing ব্যবহার করা হয়। যেমন PrivateMounts=yes ব্যবহার করলে directory-টি অন্য service-এর কাছ থেকে আড়াল থাকে। মানটি কখনো /proc/<pid>/environ-এ দেখা যায় না। তাই ps eww এটি দেখাতে পারে না। Service বন্ধ হলে directory-টিও মুছে যায়।

প্রথমে credential-টি machine-এর জন্য encrypt করুন। এই command-গুলো systemd documentation থেকে নেওয়া এবং systemd 250 বা পরবর্তী সংস্করণে কাজ করে। এর মধ্যে Ubuntu 24.04 এবং Debian 13 অন্তর্ভুক্ত:

echo -n 'sk-example-value' > /tmp/plain.txt
sudo systemd-creds encrypt --name=api_key /tmp/plain.txt /etc/credstore/api_key.cred
shred -u /tmp/plain.txt
sudo systemd-run -P --wait -p LoadCredentialEncrypted=api_key:/etc/credstore/api_key.cred \
  systemd-creds cat api_key

শেষ command-টি sk-example-value প্রদর্শন করে। এর মাধ্যমে নিশ্চিত হওয়া যায় যে encrypted file-টি এই host-এ decrypt করা যাচ্ছে। এরপর unit file-এ সেটি উল্লেখ করুন:

[Service]
User=agent-review
LoadCredentialEncrypted=api_key:/etc/credstore/api_key.cred
Environment=AGENT_KEY_FILE=%d/api_key
ExecStart=/usr/local/bin/agent-worker

প্রয়োজনের সময় আপনার agent code $AGENT_KEY_FILE-এ থাকা file-টি খুলে মানটি পড়বে। File read অল্প সময়ের জন্য থাকে। Environment variable process-এর সম্পূর্ণ lifetime জুড়ে থাকে এবং process যে প্রতিটি child তৈরি করে, সেখানেও থাকে।

দীর্ঘমেয়াদি key-এর পরিবর্তে স্বল্পমেয়াদি token ব্যবহার করুন

মেয়াদ শেষ হয় না এমন একটি cryptographic key কয়েক মাস পরে log বা transcript-এ প্রকাশ পেলেও তখনও বৈধ থাকে। কোনো service session token দিলে সেটিই ব্যবহার করুন এবং কাজের জন্য যত কম lifetime সম্ভব সেট করুন।

aws sts assume-role \
  --role-arn arn:aws:iam::123456789012:role/agent-readonly \
  --role-session-name agent-review \
  --duration-seconds 900

AWS STS (security token service) সর্বনিম্ন 15 মিনিটের lifetime গ্রহণ করে। সাধারণত একটি agent task-এর জন্য এটিই যথেষ্ট। GitHub-এর ক্ষেত্রে agent user-এর জন্য আলাদা gh login দিন এবং এমন fine-grained token ব্যবহার করুন, যার scope শুধু সেই single repository-তে সীমাবদ্ধ যেখানে agent কাজ করে। এতে ওই session-এর মধ্যে gh auth token চালালে এমন credential ফেরত আসে, যা অন্য কোনো resource-এ access করতে পারে না। প্রথমে resource অনুযায়ী scope সীমাবদ্ধ করুন, তারপর সময় অনুযায়ী।

পরীক্ষা করুন, তারপর নিয়মিত পরীক্ষা চালিয়ে যান

কোনো agent-এর সেটআপ পরিবর্তন করার পরে তিনটি পরীক্ষা চালানো উচিত। এগুলো নিজের পরিচয়ে নয়, agent-এর user হিসেবে চালান।

sudo -u agent-review env | grep -iE 'key|token|secret'
sudo -u agent-review ls -la /home/you/ 2>&1 | head -3
sudo -u agent-review curl -s -o /dev/null -w '%{http_code}\n' --max-time 5 https://api.github.com/user

প্রথম কমান্ডটি একেবারেই কোনো output দেবে না। দ্বিতীয়টি ls: cannot open directory '/home/you/': Permission denied দেখাবে। তবে এতে শুধু বোঝা যায় যে directory-টির তালিকা দেখানো যায় না; এর ভেতরের কোনো file পড়া যায় না—এটি প্রমাণ করে আগের cat test। তৃতীয়টি জানায়, agent-এর network path কোন পরিচয় ব্যবহার করছে। Gateway pattern-এর উদ্দেশ্যও এই প্রশ্নের উত্তর দেওয়া। 401 মানে request এমন কোনো credential ছাড়াই GitHub-এ পৌঁছেছে যা GitHub গ্রহণ করে; credential একেবারেই না-ও থাকতে পারে, অথবা সেটি invalid কিংবা expired হতে পারে। 200 মানে request-এ একটি valid credential আছে। তাই কোন token ব্যবহার হচ্ছে তা আপনার জানা উচিত। আপনি যদি unattended agent চালান, তাহলে VPS-এ AI agent-এর খরচ নিয়ন্ত্রণ এই access limit-এর সঙ্গে প্রয়োজনীয় budget limit-গুলো ব্যাখ্যা করে।

FAQ

আমি কি শুধু মডেলকে বিশ্বাস করতে পারি যে এটি আমার key ফাঁস করবে না?

না, কারণ এই threat model-এ আক্রমণকারী মডেল নয়। Agent web page, repository এবং issue tracker থেকে text পড়ে, আর সেই text-এ instruction থাকতে পারে। মডেল তার নিজের instruction এবং fetch করা text-এর মধ্যে নির্ভরযোগ্যভাবে পার্থক্য করতে পারে না। মডেল সঠিক সিদ্ধান্ত নেবে—এমন নিয়ন্ত্রণ প্রথম convincing injected instruction-এর সময়ই ব্যর্থ হবে। তাই নিয়ন্ত্রণটি operating system বা network স্তরে থাকতে হবে।

Agent-এর secret-এর জন্য environment variable কি সত্যিই এত খারাপ?

একটি নির্দিষ্ট কারণে এগুলো খারাপ: এগুলো inherited হয়। Agent যে প্রতিটি child process চালায়, সেটি একটি copy পায়। এর মধ্যে build script, test runner এবং যেকোনো package install hook-ও রয়েছে। একই user-এর মাধ্যমে /proc/<pid>/environ ব্যবহার করেও variable পড়া যায়। তাই agent কোনো variable নিজে pass না করলেও agent যে program চালায়, সেটি variable পড়তে পারে। ব্যবহারের সময় file পড়লে, LoadCredential= বা gateway ব্যবহার করে exposure কেবল সেই সময়ের মধ্যে সীমিত রাখা যায়।

Vault-এ secret রাখলে কি একাই এই সমস্যা সমাধান হয়?

শুধু আংশিকভাবে। Vault storage-এর সমস্যা সমাধান করে। আপনি নিজে সেই vault host করলে সেটিকেও আলাদাভাবে harden করতে হবে, কারণ Vaultwarden server সাধারণত তার admin token বা backup file-এর মাধ্যমে breach হয়, এতে থাকা encrypted item-এর মাধ্যমে নয়। তবে শেষ ধাপের সমস্যা এটি সমাধান করে না। কোনো কিছু vault থেকে secret নিয়ে agent-কে environment variable হিসেবে দিলে আপনি আবার আগের অবস্থায় ফিরে যান। আসল বিষয় হলো substitution কে করছে। Agent secret fetch করলে secret agent-এর কাছেই থাকে। Gateway বা init system agent-এর process-এর বাইরে substitution করলে agent কখনো secret ধরে না।

কোনো agent ইতিমধ্যে কিছু leak করেছে কি না তা কীভাবে বুঝব?

সাধারণত ঘটনার পরে তা নিশ্চিতভাবে জানা যায় না। এটাই gateway ব্যবহারের পক্ষে যুক্তি। Gateway ছাড়া আপনার evidence shell history, agent-এর transcript এবং outbound connection log-এ ছড়িয়ে থাকে। এগুলোর মধ্যে outbound connection log আপনি সম্ভবত রাখছেন না। Credential gateway থাকলে credential ব্যবহারের প্রতিটি ঘটনা agent identity এবং timestamp-সহ একটি line-এ নথিভুক্ত হয়। Leak হয়েছে বলে সন্দেহ হলে আগে key rotate করুন, পরে investigation করুন। Rotation সস্তা; নিশ্চিততা নয়।

আজ আমার ন্যূনতম কী করা উচিত?

প্রতিটি .env file agent যে directory-গুলোতে কাজ করে, সেগুলো থেকে সরিয়ে নিন। প্রতিটি agent-এর জন্য একটি করে unprivileged user তৈরি করুন। এই দুটি পরিবর্তন করতে প্রায় দশ মিনিট লাগে এবং সবচেয়ে সাধারণ পথ বন্ধ করে। সেই পথে agent এমন একটি credential file পড়ে, যার code-এর পাশে থাকার কোনো কারণ ছিল না। Gateway এবং short-lived token পরের ধাপ, প্রথম ধাপ নয়। একই starting point যেকোনো agent runtime-এর ক্ষেত্রেই প্রযোজ্য, যার মধ্যে VPS-এ autonomous agent নিরাপদে চালানো অন্তর্ভুক্ত।

এই লেখাটি কি কাজে লেগেছে?