Prompt injection vào coding agent: bề mặt tấn công
Coding agent coi file, PR, web page và tool output như instruction. Tìm các điểm injection trên server và cách xếp hạng phòng vệ để giảm thiệt hại.
Prompt injection nhằm vào coding agent là gì
Prompt injection nhằm vào coding agent có thể mô tả đơn giản như sau: văn bản mà agent đọc sẽ bị xem là instruction và agent sẽ làm theo. Agent mở một file, đọc comment trong pull request, truy cập một web page hoặc nhận kết quả từ một tool call. Tất cả đều đến dưới dạng cùng một loại văn bản như request của bạn. Nếu attacker kiểm soát bất kỳ phần văn bản nào trong đó, attacker đang ghi nội dung vào session của bạn.
Mọi sản phẩm agent hiện nay đều có đặc tính này. Model nhận một chuỗi token duy nhất. Request của bạn, system prompt, nội dung file và kết quả từ tool được nối vào nhau, rồi model dự đoán token tiếp theo. Token không có bit phân quyền. Định dạng này không cho biết phần nào đã được bạn cấp quyền và phần nào đến từ README của một người lạ.
Trang này trình bày threat model: văn bản do attacker kiểm soát có thể đến agent đang chạy trên server qua những điểm nào, attacker đạt được gì ở từng điểm và biện pháp phòng vệ nào đáng để triển khai. Các hướng dẫn về containment trong những guide khác của chúng tôi chỉ có ý nghĩa khi bạn biết mình đang ngăn agent tiếp xúc với điều gì.
Vì sao model không thể tách nội dung khỏi instruction
Việc training giúp giảm vấn đề này nhưng không giải quyết triệt để. Các model hiện nay được training để xem văn bản được truy xuất là đáng ngờ và từ chối nhiều cách tấn công thô sơ. Việc từ chối chỉ dựa trên xác suất, không phải một quy tắc cố định. Attacker có thể viết lại, thử lại và giấu văn bản trong một định dạng mà không ai dự tính; không có giới hạn nào về số cách diễn đạt mà họ có thể thử.
Dự án OWASP GenAI theo dõi vấn đề này dưới tên LLM01:2025 Prompt Injection và chia thành 2 loại. Direct injection là khi prompt của chính người dùng thay đổi hành vi của model. Indirect injection là khi nội dung bên ngoài, chẳng hạn website hoặc file, thay đổi hành vi trong lúc model xử lý nội dung đó. Indirect injection mới là vấn đề đáng quan tâm trên server, vì agent đọc nhiều văn bản hơn rất nhiều so với phần bạn nhập.
Nghiên cứu có hệ thống đầu tiên là công trình của Greshake và các đồng nghiệp, Không phải điều bạn đã đăng ký (2023). Kết luận cần ghi nhớ là: khi một ứng dụng đưa văn bản được truy xuất vào model có thể gọi tool, việc xử lý văn bản đó gần tương đương với thực thi mã tùy ý.
Điều kiện biến việc đọc thành một vụ xâm nhập
Đọc văn bản độc hại tự nó chưa gây thiệt hại. Thiệt hại chỉ xảy ra khi dữ liệu có đường thoát khỏi máy.
Simon Willison gọi tổ hợp này là bộ ba chí mạng vào tháng 6 năm 2025. Một agent có dữ liệu riêng tư, tiếp xúc với nội dung không đáng tin cậy và có thể gửi dữ liệu ra ngoài có thể bị dẫn dụ để đưa dữ liệu ở điều kiện đầu tiên ra ngoài qua điều kiện thứ ba.
Một coding agent trên VPS của bạn có đủ cả ba điều kiện ngay từ đầu. Dữ liệu riêng tư là source code, file .env, SSH keys và shell history của bạn. Nội dung không đáng tin cậy là mọi repository, trang web và kết quả từ tool mà agent đọc. Đường thoát là git push, curl, npm publish, nội dung pull request hoặc một link được in trong terminal mà bạn bấm vào.
Bạn không thể loại bỏ điều kiện thứ hai, vì đọc văn bản không đáng tin cậy chính là công việc bạn thuê agent thực hiện. Vì vậy, mọi biện pháp phòng vệ thực tế đều tập trung vào hai điều kiện còn lại.
Khi văn bản không đáng tin cậy đến coding agent trên server
Repository mà agent đang làm việc
Mọi file trong checkout đều là input. Comment trong source, README.md, changelog, test fixture, code vendored và chính các file hướng dẫn agent: CLAUDE.md, AGENTS.md cùng các file tương đương. Agent được yêu cầu tìm hiểu một codebase sẽ đọc chúng, vì đó là việc bạn yêu cầu.
Kẻ tấn công có thể tiếp cận mọi người clone repository rồi trỏ agent vào đó. Các file hướng dẫn là đường trực tiếp nhất, vì chúng được tạo ra để được đọc như chỉ dẫn. Một pull request thêm bốn dòng hữu ích vào CLAUDE.md và một dòng chuyển hướng agent có thể bị reviewer lướt qua.
Issue, pull request và comment trong code review
Bất kỳ nội dung nào người lạ có thể nhập vào tracker đều đến agent ngay khi bạn yêu cầu agent phân loại chúng. Tháng 5 năm 2025, Invariant Labs đã công bố một phát hiện về GitHub MCP có đúng dạng này. Agent của một developer có quyền truy cập vào một repository public và các repository private. Kẻ tấn công tạo một issue trong repository public. Khi developer yêu cầu agent xem các issue đang mở, agent đọc nội dung của repository private rồi ghi chúng vào một pull request ở phía public.
Báo cáo đó mô tả một vấn đề về kiến trúc, không phải lỗi code trong MCP server. Agent giữ một access token có phạm vi quá rộng, đọc từ một inbox public và có quyền ghi. Không có gì được cấu hình sai theo cách thông thường, nên cách xử lý là giới hạn scope chứ không phải vá lỗi.
Trang web mà agent fetch
Tài liệu, câu trả lời trên forum, trang của vendor, kết quả tìm kiếm. Bất kỳ nội dung nào cũng có thể chứa văn bản được viết cho agent thay vì cho bạn. HTML được chuyển thành text tạo thêm không gian cho kẻ tấn công, vì cả nội dung mà browser không hiển thị vẫn đến model.
Kẻ tấn công giành được quyền kiểm soát vào lúc agent ít bị theo dõi nhất. Không ai đọc toàn bộ nội dung của một trang mà agent đã fetch trong lúc tra cứu.
Output của MCP tool
MCP (model context protocol) là cách phổ biến để agent kết nối với các tool bên ngoài. Kết quả trả về dưới dạng text và đi thẳng vào context window. Ở đây có hai bề mặt, không phải một. Dữ liệu do tool trả về là bề mặt rõ ràng. Tên và description của chính tool, những thứ model đọc để quyết định khi nào gọi tool, là bề mặt còn lại; một server bạn không kiểm soát có thể thay đổi cả hai giữa các lần gọi.
Kẻ tấn công đưa được text vào output của một tool sẽ tiếp cận mọi tool khác mà agent có quyền dùng. Đó là cách một injection trong thành phần có giá trị thấp cuối cùng điều khiển thành phần có giá trị cao.
Log CI, output của quá trình build và metadata của dependency
npm install in text từ các package mà bạn không viết. Một test failure in thông báo assertion từ một library. Log của một job continuous integration (CI) có hàng nghìn dòng output từ bên thứ ba. Khi yêu cầu agent sửa một build bị lỗi, agent sẽ đọc toàn bộ nội dung đó.
Trong trường hợp này, kẻ tấn công có thể tiếp cận build machine, nơi thường chứa credential deploy và registry token, nhưng thường ít được chú ý hơn laptop.
Kẻ tấn công thực sự lấy được gì
Có 4 kết quả cần chủ động chuẩn bị.
Đánh cắp thông tin xác thực. Mọi thứ mà tiến trình agent có thể đọc đều nằm trong phạm vi: biến môi trường, ~/.aws/credentials, ~/.ssh, token gh, tệp cấu hình Docker. Việc gửi chúng ra ngoài không cần curl. Một commit vào branch, mô tả pull request, package được publish lên registry hoặc truy vấn DNS cho một tên miền do kẻ tấn công kiểm soát đều có thể đưa dữ liệu ra khỏi máy.
Thay đổi code mà bạn phê duyệt. Agent có nhiệm vụ viết code, nên khiến nó ghi một dòng sai tinh vi là kết quả dễ đạt được nhất. Ví dụ, agent có thể thêm một dependency hoặc thêm lệnh ghi log làm đưa token vào một log mà bạn chuyển đến nơi khác.
Duy trì quyền truy cập. Một tệp được ghi một lần có thể tiếp tục hoạt động mà không cần model: một hook trong .git/hooks, một script postinstall trong package.json, một dòng được nối thêm vào tệp khởi động shell hoặc một dòng bổ sung trong CLAUDE.md. Lệnh tiếp theo sẽ chạy nó.
Di chuyển trong mạng nội bộ. Agent chạy tại nơi bạn triển khai nó. Nếu máy đó truy cập được database trên loopback, service quản trị nội bộ, metadata service của cloud provider hoặc một host khác trong mạng riêng, thì bất kỳ thứ gì điều khiển agent cũng có thể truy cập các đích đó.
Chế độ tự động phê duyệt loại bỏ bước kiểm tra cuối cùng
Ở chế độ mặc định, Claude Code hỏi trước khi chạy command hoặc chỉnh sửa file. Prompt đó là bước kiểm tra của con người giữa mỗi bề mặt ở trên và hành động thực tế. Các chế độ loại bỏ prompt cũng loại bỏ bước kiểm tra này.
Tài liệu nói rõ về bypassPermissions: chỉ dùng chế độ này trong môi trường cô lập như container hoặc VM, nơi Claude Code không thể gây thiệt hại. Chế độ tự động ít mạnh tay hơn và tự động phê duyệt các lần gọi tool, đồng thời chạy các bước kiểm tra an toàn nền để xác minh hành động phù hợp với yêu cầu của bạn. Các bước kiểm tra đó phát hiện được nhiều vấn đề. Tuy nhiên, chúng vẫn là phán đoán của model về output của model, vì vậy hãy xem chúng là một bộ lọc, không phải một ranh giới bảo vệ.
Administrator có thể loại bỏ cả hai bước này. Đặt permissions.disableBypassPermissionsMode hoặc permissions.disableAutoMode thành "disable" trong một settings file, rồi đặt file đó vào managed settings để project đã checkout không thể ghi đè. Hướng dẫn của chúng tôi về chế độ tự động và quy tắc quyền của Claude Code giải thích từng rule có hiệu lực ở đâu.
Các biện pháp phòng thủ, xếp theo giá trị thực tế
Không biện pháp nào trong số này là cách khắc phục hoàn chỉnh. Mỗi biện pháp chỉ thu hẹp những gì agent có thể nắm giữ hoặc những gì agent có thể làm với chúng.
- Một máy có thể hủy và dựng lại, để sự cố bị xâm nhập chỉ khiến bạn mất một giờ thay vì phải xử lý một incident.
- Credential tách biệt với credential của bạn, chỉ có phạm vi trên một repository và có thời hạn ngắn.
- Không có secret tồn tại lâu trong environment mà các lệnh của agent kế thừa.
- Cơ chế thực thi của hệ điều hành đối với network egress và quyền truy cập file, áp dụng cho mọi process do agent khởi chạy.
- Giữ bật prompt phê duyệt đối với thao tác ghi và network call.
- Hook làm lớp chặn xác định được cho những hành động cụ thể mà bạn có thể nêu rõ.
- Đọc diff trước khi merge.
Thứ tự này rất quan trọng. Các mục 1 đến 4 vẫn có hiệu lực ngay cả khi model hoàn toàn nằm dưới quyền kiểm soát của kẻ tấn công. Các mục 5 đến 7 phụ thuộc vào việc con người vẫn chú ý, nhưng đó chính là điều thường không còn xảy ra trong một agent run kéo dài.
Đặt agent trên một máy có thể hủy bỏ
Một VPS chỉ chứa một checkout và một token có phạm vi giới hạn là mục tiêu nhỏ hơn nhiều so với một laptop chứa các key của bạn. Chạy agent bằng một user không có quyền đặc biệt riêng, không dùng login account của bạn và không chạy bằng root. Các hướng dẫn dựng một VM có thể hủy cho coding agent và dùng user có quyền tối thiểu trên VPS trình bày phần thiết lập; chạy Claude Code an toàn trên VPS trình bày mô hình sử dụng hằng ngày.
Đưa secret ra khỏi environment
Một environment variable có thể được mọi child process đọc, tức là mọi command mà agent chạy. Sandbox của Claude Code có thể unset các variable được chỉ định trước mỗi sandboxed command. Trên Linux, sandbox cần 2 package trước:
sudo apt-get install bubblewrap socatSau đó, trong ~/.claude/settings.json:
{
"sandbox": {
"enabled": true,
"network": {
"allowedDomains": ["github.com", "*.npmjs.org"]
},
"credentials": {
"envVars": [
{ "name": "GITHUB_TOKEN", "mode": "deny" },
{ "name": "NPM_TOKEN", "mode": "deny" }
]
}
}
}Một entry deny sẽ unset variable đó trước mỗi sandboxed command. allowedDomains chứa các sandboxed command được phép truy cập những host mà bạn liệt kê. Block credentials cần Claude Code v2.1.187 trở lên, được kiểm tra vào August 2026. Chạy /sandbox trong một session để xem layer nào đang active và dependency nào còn thiếu. Xác định secret nào thực sự cần tồn tại trên máy đó mới là phần lớn hơn của công việc; giữ secret ngoài tầm với của AI agent trình bày cách thực hiện.
Chặn network egress ở cấp hệ điều hành
Firewall rule không quan tâm model đã quyết định gì. Chạy agent bằng một user agent riêng, rồi drop lưu lượng mà user đó gửi:
table inet agentcage {
chain output {
type filter hook output priority filter; policy accept;
meta skuid "agent" ct state established,related accept
meta skuid "agent" oif lo accept
meta skuid "agent" counter drop
}
}Như vậy, user agent chỉ còn loopback. Traffic của user này phải đi qua một proxy chạy trên cùng máy, còn proxy giữ hostname allowlist. Khi https_proxy trỏ đến proxy đó, client gửi request CONNECT và proxy thực hiện name lookup. Vì vậy agent không cần tự thực hiện outbound DNS (domain name system). Kiểm tra bằng sudo nft list ruleset và theo dõi counter trên drop rule tăng lên khi agent cố truy cập một endpoint mới.
Giữ một SSH session thứ hai mở trong khi áp dụng thay đổi firewall. Đồng thời kiểm tra container runtime xử lý các rule này như thế nào: Docker tự ghi các chain riêng, và published Docker port bypass ufw mô tả sự cố bất ngờ do việc này gây ra.
Hook: bước kiểm tra mà model không thể tìm cách vượt qua
Permission rule và hook được Claude Code thực thi, không phải model. Tài liệu nêu rất rõ: instruction trong prompt hoặc CLAUDE.md định hướng những gì Claude cố thực hiện, nhưng không thay đổi những gì Claude Code cho phép. Đây là điểm cốt lõi. Một dòng trong CLAUDE.md ghi “không bao giờ chạy curl” chỉ là một đề xuất mà một đoạn prompt injection có thể tranh luận. Hook là một process trả về exit code.
Đăng ký hook PreToolUse trong .claude/settings.json:
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/no-egress.sh"
}
]
}
]
}
}Hook nhận tool call dưới dạng JSON qua standard input. Exit code 2 chặn call và hiển thị cho Claude lý do lấy từ standard error. Exit code 0 cho phép call tiếp tục qua luồng permission thông thường.
#!/usr/bin/env bash
# PreToolUse: stdin holds the tool call, exit 2 blocks it.
cmd=$(jq -r '.tool_input.command // ""')
if printf '%s' "$cmd" | grep -qE '(^|[;&|]|\s)(curl|wget|nc|ncat)(\s|$)'; then
echo "Blocked: this repository does not allow outbound network commands." >&2
exit 2
fi
exit 0Bây giờ là phần cần nói thẳng. Đây là denylist áp dụng trên một shell string, và denylist trên shell string luôn có kẽ hở. python3 -c có thể mở socket mà không dùng từ curl. Một target make deploy có thể ẩn cùng call đó sâu hơn một cấp. Hãy viết hook cho những lỗi bạn có thể nêu rõ, còn boundary mà bạn thực sự dựa vào phải được đặt trong kernel hoặc trên network.
Permission deny rule cũng có một giới hạn matching đáng biết. Rule Read và Edit bao phủ các file tool của Claude và các file command mà Claude nhận diện trong Bash, chẳng hạn cat, head, tail và sed. Chúng không bao phủ một script Python hoặc Node tự mở file. Rule được đánh giá theo thứ tự deny trước, rồi ask, rồi allow, nên deny rule không thể chứa một ngoại lệ allowlist.
{
"permissions": {
"deny": [
"Read(.env)",
"Read(./secrets/**)",
"Bash(git push *)"
]
}
}Theo dõi những gì rời khỏi máy và đọc diff
Một agent run tạo ra diff và một tập hợp network call. Cả hai đều cần được kiểm tra trước khi merge hoặc deploy. Một lượt security review tự host trên diff sẽ phát hiện loại thay đổi khác với việc con người đọc lướt, còn biết coding agent gửi gì ra khỏi máy giúp bạn hiểu traffic bình thường trông như thế nào, để các request bất thường dễ nổi bật.
Điều gì vẫn chưa được giải quyết
Hiện chưa có cơ chế đáng tin cậy để tách nội dung khỏi chỉ thị. Mọi biện pháp phòng vệ được phát hành đều là bộ lọc có tỷ lệ lỗi hoặc chỉ giới hạn hậu quả. Không có thành phần nào trong stack đánh dấu một đoạn văn bản là dữ liệu tuyệt đối không được tuân theo.
Bộ lọc có tác dụng, nhưng vẫn có thể thất bại. Một classifier bắt được phần lớn các nỗ lực prompt injection vẫn phải đúng trong mọi trường hợp, trong khi kẻ tấn công chỉ cần đúng một lần. Sự bất đối xứng đó khiến tỷ lệ thành công được công bố của một biện pháp phòng vệ chỉ là điểm bắt đầu cho lần thử tiếp theo, chứ không phải sự bảo đảm.
Công trình có triển vọng nhất hiện tập trung ở cấp độ thiết kế thay vì cấp độ model. CaMeL, trong Defeating Prompt Injections by Design (Debenedetti và cộng sự, 2025), trước tiên trích xuất control flow và data flow từ request đáng tin cậy, để dữ liệu không đáng tin cậy không thể thay đổi cách chương trình hoạt động, sau đó thực thi các kiểm tra capability khi tool được gọi. Các số liệu do chính bài báo công bố trên benchmark AgentDojo cho thấy cái giá phải trả.
The data behind this chart
[
{
"label": "Undefended agent",
"tasks_solved_pct": 84
},
{
"label": "CaMeL",
"tasks_solved_pct": 77
}
]Agent không được bảo vệ giải quyết 84 phần trăm số task. CaMeL giải quyết 77 phần trăm, kèm theo bảo đảm bảo mật. Đây là các số liệu được bài báo công bố trên một benchmark, không phải phép đo cho workload của bạn. Khoảng cách giữa hai con số này gần tương ứng với chi phí của một bảo đảm thực sự ở thời điểm hiện tại.
Cho đến khi một thiết kế như vậy được tích hợp vào các tool bạn dùng hằng ngày, hãy chuẩn bị cho khả năng agent bị compromise vào một thời điểm nào đó và khiến sự cố đó trở nên không đáng kể. Đó là toàn bộ lý do cần dùng một máy có thể loại bỏ, credential có phạm vi giới hạn, egress được kiểm soát và thói quen đọc diff.
FAQ
Tôi có thể ngăn prompt injection bằng cách yêu cầu agent bỏ qua các chỉ dẫn trong file không?
Không. Câu đó là văn bản trong cùng context window với cuộc tấn công, nên nó cạnh tranh với văn bản của kẻ tấn công trên cùng một cơ chế. Tài liệu của Claude Code phân biệt rất rõ: các chỉ dẫn trong prompt hoặc CLAUDE.md định hình những gì agent cố gắng thực hiện, nhưng không thay đổi những gì tool cho phép. Hãy xem file chỉ dẫn là một tuyên bố về ý định, còn mọi thứ bạn dựa vào phải được đặt trong permission rules, một hook PreToolUse hoặc firewall rule.
Prompt injection có thực sự nguy hiểm nếu agent chỉ truy cập repository của tôi không?
Có, vì repository của bạn chứa rất nhiều văn bản không phải do bạn viết. README của dependency, URL trong lockfile, test fixture, code vendored và output của npm install đều xuất hiện trong một tác vụ thông thường. Bất kỳ nội dung nào được lấy từ issue tracker hoặc trang tài liệu cũng xuất hiện theo cách tương tự. Agent đọc càng nhiều thì rủi ro càng tăng, mà một agent hữu ích thường đọc rất nhiều.
Chạy agent trong container có giải quyết được vấn đề này không?
Cách này giới hạn thiệt hại, nhưng chỉ khi bạn đồng thời thu hồi credentials. Một container được forward SSH agent, có cloud credentials trong environment và được unrestricted network access sẽ trao cho kẻ tấn công gần như toàn bộ quyền mà host có. Lợi ích thực sự của container là cung cấp một filesystem có thể xóa và một môi trường sạch để áp dụng egress rules. Hãy kết hợp với một token chỉ có scope trên một repository.
Thay đổi đơn lẻ nào giúp giảm rủi ro nhiều nhất?
Xóa credentials dài hạn khỏi environment mà các command của agent kế thừa, sau đó áp dụng default-deny egress policy cho máy đó. Hai việc này kết hợp sẽ phá vỡ điều kiện thứ ba trong bộ ba nguy hiểm: văn bản vẫn có thể chiếm quyền điều khiển agent, nhưng dữ liệu mà agent truy cập không còn đường hữu ích để đi ra ngoài. Approval prompt và diff review cũng hữu ích, nhưng chúng phụ thuộc vào việc con người luôn cảnh giác trong suốt một lần chạy dài. Vì vậy, chúng có mức ưu tiên thấp hơn hai thay đổi trên.