Tự host open-kritt trên VPS: Cài Docker và SSH tunnel
Chạy open-kritt trên VPS với Docker Compose, pin release, dùng SSH tunnel mở UI port 5173 và đặt budget provider trước lần scan đầu tiên.
Vì sao nên tự host open-kritt trên VPS thay vì laptop
Hãy tự host open-kritt trên một server mà bạn có thể hủy và dựng lại. Tool này chạy các analysis agent dưới quyền root bên trong các job container dùng một lần, cấp cho mỗi agent một bản sao code có quyền ghi và quyền truy cập Internet trực tiếp, đồng thời mount Docker socket của host vào engine service. Đây là một đánh đổi hợp lý trên một máy chỉ dành cho công việc này. Nhưng đó là lựa chọn tệ trên máy đang lưu SSH key của bạn.
Có 4 đặc điểm trong cấu hình mặc định dẫn đến khuyến nghị này. Cả 4 đều được nêu trong README và compose file của dự án.
Các agent được thiết kế để có quyền năng lớn. README cho biết các tool-enabled agent chạy dưới quyền root bên trong những job container dùng một lần, với bản sao repository có quyền ghi và quyền truy cập Internet trực tiếp. Nhờ đó, chúng có thể cài tool, biên dịch target, chạy test và tạo proof of concept. Một lần scan không chỉ là linter đọc file. Đó là thực thi code tùy ý mà bạn đã chủ động yêu cầu. Quyền truy cập Internet này có hai mặt: mọi nội dung agent tải xuống trong lúc nghiên cứu target đều là text không đáng tin cậy đi vào prompt của agent, tương tự rủi ro khi bạn cho agent tự thực hiện tìm kiếm trên web.
Engine giữ Docker socket. docker-compose.yml mount Docker socket của host vào engine service vì engine build và khởi chạy một scan container cho mỗi job. Bất kỳ process nào truy cập được socket đó đều có thể khởi động một container mount filesystem của host. Vì vậy, engine về thực chất có quyền root trên host đang chạy nó.
Không có màn hình đăng nhập. Backend được phát hành mà không có cơ chế xác thực ứng dụng. Có quyền truy cập vào port là có quyền truy cập vào kết quả scan của bạn và khoản credit của nhà cung cấp.
Code được scan thường không phải code của bạn. Khi trỏ agent vào một repository bên thứ ba, bạn đang chạy quy trình build của repository đó trên máy mình, dưới quyền root và có quyền truy cập mạng.
Nếu bạn đã đọc vì sao coding agent nên chạy trong VM dùng một lần, thì đây là cùng một threat model, chỉ mạnh hơn. Hãy cấp cho open-kritt một VPS không chạy thêm dịch vụ nào khác, rồi điều khiển VPS đó từ một user account có ít quyền riêng biệt thay vì root.
open-kritt thực sự làm gì
open-kritt (repository là Kritt-ai/open-kritt, được cấp phép theo AGPL-3.0) chia việc nghiên cứu lỗ hổng thành các tác vụ nhỏ, chạy các tác vụ đó song song trên nhiều AI agent, rồi loại bỏ các kết quả trùng lặp và xếp hạng những gì thu được. Bạn định nghĩa workflow dưới dạng chuỗi prompt tập trung, trong đó mỗi bước nhận context có cấu trúc từ các bước trước. Đối tượng scan là một git repository từ xa hoặc cục bộ. Engine phân tích là Codex hoặc Claude Code. Khi xuất hiện một candidate, các post-script tùy chọn có thể thử xác thực candidate đó hoặc dựng proof of concept. Thiết kế chuỗi này là công việc agent thông thường, không phải công việc bảo mật. Vì vậy, nếu prompt, tool và việc truyền context vẫn còn mới với bạn, tìm hiểu cách xây dựng agent sẽ giúp cải thiện kết quả nhiều hơn bất kỳ setting nào trong guide này.
Kết quả cuối cùng là một danh sách candidate đã được xếp hạng. Hãy xem đây là hàng đợi để triage, không phải report.
Những thứ cần chuẩn bị trước khi bắt đầu
- Một VPS chạy Ubuntu 24.04, Debian 12 hoặc Rocky Linux 9. Tài liệu cài đặt liệt kê đây là các bản phân phối đã được kiểm thử trên x86_64 và ARM64.
- Docker Engine có plugin Compose.
- Node.js 20 trở lên trên host, vì CLI
./krittchạy trên host thay vì bên trong container. - Một nhà cung cấp model: tài khoản Codex hoặc
OPENAI_API_KEY,CODEX_API_KEY,ANTHROPIC_API_KEYhayOPENROUTER_API_KEY. - Chỉ cần
GITHUB_TOKENnếu bạn định quét các repository private..env.exampleđi kèm nêu rõ: chỉ có GitHub token thì không thể chạy quá trình quét.
Cài Docker và Node 20 trước
curl -fsSL https://get.docker.com | sudo sh
sudo usermod -aG docker $USERĐăng xuất rồi đăng nhập lại để áp dụng membership mới của group, sau đó xác nhận Compose plugin đã có.
docker compose versionChuỗi phiên bản cho biết Compose đã được cài dưới dạng plugin. docker: 'compose' is not a docker command nghĩa là bạn đang dùng binary docker-compose độc lập cũ, còn open-kritt gọi docker compose. Membership của group docker tương đương quyền root trên host, vì vậy chỉ thêm account chạy open-kritt vào group này. Xem hướng dẫn chạy Docker trên VPS để biết cách thiết lập đầy đủ hơn.
Ubuntu 24.04 cung cấp Node 18 trong repository riêng, còn CLI sẽ thoát nếu phiên bản thấp hơn 20. Hãy dùng NodeSource.
curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash -
sudo apt install -y nodejs
node -vnode -v phải in ra v20. hoặc cao hơn. Trên Rocky Linux 9, cách tương đương là chạy sudo dnf module enable nodejs:20 -y rồi chạy sudo dnf install -y nodejs.
Clone open-kritt và cố định một bản phát hành được gắn tag
git clone https://github.com/Kritt-ai/open-kritt
cd open-kritt
git fetch --tags
git tag --list
git checkout v1.3.0main thay đổi theo thời gian. Tag thì không. Tính đến tháng 8 năm 2026, tag mới nhất là v1.3.0, được phát hành vào ngày 4 tháng 8 năm 2026. git tag --list hiển thị những gì tồn tại tại thời điểm bạn clone. Checkout một tag sẽ đưa repository vào trạng thái detached HEAD. Điều này là đúng trong trường hợp này: bạn dùng clone này như một deployment được cố định, không phải một branch để commit. Khi muốn upgrade sau này, hãy đọc release notes, sau đó chạy git fetch --tags, checkout tag mới và chạy lại ./kritt start, vì start sẽ build lại các image.
Không chạy ./kritt cùng với sudo. Tài liệu nêu rõ điều này. CLI quản lý các thư mục credential của project dưới .data/. Vì vậy, nếu chạy bằng root, các thư mục đó sẽ thuộc sở hữu của root và lần chạy bình thường tiếp theo sẽ không thể ghi vào chúng.
Cấu hình quyền truy cập model bằng ./kritt setup
./kritt setupLệnh này tạo .env từ .env.example nếu file chưa tồn tại, hiển thị trạng thái của từng credential và cho phép bạn đặt hoặc xóa chúng. Lệnh không bao giờ in các giá trị ra terminal. Cả .env và file credential của engine đều được ghi với mode 0600.
Nếu muốn tự thực hiện:
cp .env.example .env
chmod 600 .env
mkdir -p .data/codex
chmod 700 .data/codexSau đó sửa provider key vào .env và giữ file ở mode 0600. Dù dùng cách nào, trên server hiện đã có một provider credential đang hoạt động. Đây là thêm một lý do để không lưu bất kỳ thứ gì khác trên máy đó. Hãy tạo một key chỉ dành cho project này, để sau này thu hồi key không làm hỏng những thứ quan trọng với bạn. Giữ secret ngoài tầm với của các AI agent trình bày thói quen rộng hơn này.
Thiết lập hạn mức chi tiêu của provider trước lần scan đầu tiên
open-kritt được thiết kế để phân phối công việc đồng thời, và chính số lượng tác vụ phân phối này quyết định chi phí. Các giá trị mặc định trong .env.example ở v1.3.0 khá thận trọng: ENGINE_WORKER_COUNT=2, được mô tả trong file là giá trị mặc định thận trọng cho máy 2-vCPU nhỏ, và ENGINE_MAX_CONCURRENT_SCANS=1. Cao hơn các giá trị này là ENGINE_WORKERS_PER_ACCOUNT=15, số lượt gọi root model đồng thời tối đa được phép trên một tài khoản provider, và ENGINE_CODEX_MAX_SUBAGENTS_PER_SESSION=5, vì một phiên Codex có thể chạy tối đa năm child agent. Khi tăng số worker trên VPS lớn hơn, số lượt gọi model đang thực thi cũng tăng theo.
Không có gì trong repository giới hạn số tiền bạn chi. .env.example không có thiết lập ngân sách. Các điều kiện dừng của engine chỉ gồm những giới hạn của worker và ENGINE_HARNESS_TIMEOUT_SECONDS, mặc định là 7200 giây cho mỗi lần chạy harness. Trong ngữ cảnh này, harness là vòng lặp liên tục gọi model cùng tools và context cho đến khi có điều kiện kết thúc lần chạy. Vì vậy, timeout này là thời gian thực áp dụng cho chương trình bao quanh model, không phải giới hạn số tiền model sử dụng bên trong chương trình đó. Do đó, giới hạn chi phí phải được đặt ở provider. Mở console của provider và đặt hard limit hàng tháng trước lần scan đầu tiên, không phải sau đó. Kiểm soát chi phí của AI agent trên VPS trình bày các thiết lập theo từng provider.
Bạn cũng có một cơ chế dừng cục bộ. Đặt ENGINE_WORKER_COUNT=0 sẽ tạm dừng nhận job mới. Bạn cũng có thể thay đổi các giá trị worker trong màn hình Settings sau khi stack chạy.
Hướng dẫn này không nêu giá cho mỗi lần scan vì chi phí phụ thuộc vào kích thước repository, workflow bạn xây dựng và model đứng phía sau. Hãy chạy một lần scan trên một repository nhỏ, sau đó kiểm tra trang usage của provider trước khi chạy trên bất kỳ repository lớn nào.
Khởi động stack và kiểm tra trạng thái hoạt động
./kritt startLệnh này kiểm tra .env và ít nhất một credential, rồi chạy docker compose up --build. Lần build đầu tiên sẽ chậm vì nó build image cho frontend, backend, engine, executor view và database. Lệnh cũng chạy ở foreground, nên khi đóng phiên SSH, stack sẽ dừng. Hãy chạy lệnh trong tmux hoặc khởi động ở chế độ detached sau khi lần build đầu tiên thành công. Cả hai cách này đều không tự khởi động lại sau reboot. Nếu muốn stack tự chạy lại sau khi máy chủ khởi động lại, mẫu systemd unit trong duy trì agent tự host hoạt động qua các lần reboot có thể áp dụng trực tiếp.
docker compose up -d --build
docker compose psdocker compose ps phải liệt kê open-kritt-frontend, open-kritt-backend, open-kritt-engine, open-kritt-executor-view và open-kritt-db. Sau đó kiểm tra backend có trả lời ngay trên máy chủ hay không.
curl -s http://127.0.0.1:3002/api/healthPhản hồi JSON cho biết backend đang hoạt động. Failed to connect to 127.0.0.1 port 3002: Connection refused cho biết backend chưa hoạt động, còn docker compose logs backend sẽ cho biết nguyên nhân. Dừng toàn bộ bằng docker compose down từ thư mục repository.
Một tùy chọn bổ sung: docker compose exec backend npm run seed tải dữ liệu demo. Đây là cách nhanh để xem giao diện trước khi tốn chi phí cho một lần scan thực tế.
Truy cập giao diện trên cổng 5173 qua SSH tunnel
Mỗi service trong file compose bind vào 127.0.0.1 theo mặc định: frontend trên cổng 5173, backend trên cổng 3002, giao diện executor trên cổng 8090 và Postgres trên cổng 5432. Giữ nguyên các binding này và forward cổng qua SSH từ máy của bạn.
ssh -N -L 5173:127.0.0.1:5173 you@your-server-ipMở http://localhost:5173 trong trình duyệt trên máy local khi lệnh đó đang chạy. -N nghĩa là kết nối sẽ giữ forward và không mở shell. Thêm -L 8090:127.0.0.1:8090 thứ hai vào cùng lệnh khi bạn muốn truy cập cả giao diện executor.
Bạn có thể muốn đặt FRONTEND_BIND_ADDRESS=0.0.0.0 để bỏ qua tunnel. Không nên làm vậy. Backend không có màn hình đăng nhập, nên bất kỳ ai truy cập được trang đó đều có thể khởi chạy scan và tiêu tốn credit của nhà cung cấp. Để so sánh, Vaultwarden được thiết kế để public trên Internet, còn việc harden nó vẫn phụ thuộc vào admin token và file backup, là những cơ chế mà open-kritt không có. Còn một bẫy khác: port của container được publish trước khi policy mặc định của ufw được áp dụng, nên rule ufw deny 5173 có vẻ đúng nhưng không chặn được gì. Các port Docker bypass ufw giải thích chain của các rule gây ra hiện tượng này.
Cấu hình VPS
ENGINE_MIN_FREE_STORAGE_GB mặc định là 20, và engine sẽ không khởi động container scan mới cho từng job khi dung lượng trống thấp hơn mức này. Các image đã build, checkout cache, dữ liệu Postgres và workspace của job đều nằm trên cùng một disk, nên VPS 20 GB sẽ không bao giờ chạy được scan. Hãy xem 40 GB là mức tối thiểu, và cấp thêm dung lượng nếu bạn scan các repository lớn. Đừng cố tận dụng lại máy bằng cách đặt thêm một service ngốn storage bên cạnh, vì mức RAM và dung lượng disk tối thiểu được đo trong bảng so sánh PhotoPrism và Immich này cho thấy media library có thể nhanh chóng chiếm hết phần headroom mà scan cần. Điều tương tự cũng áp dụng cho những thành phần bổ sung trông có vẻ không đáng kể khi đặt cạnh scanner: một frontend trình duyệt biến giao diện thư viện Jellyfin thành cửa hàng cho thuê băng đĩa kiểu thập niên 90 vẫn kéo theo cả media server và các bản transcode của nó lên disk, nên hãy đặt nó trên host khác.
RAM tuân theo phép tính đơn giản. ENGINE_MEMORY_RESERVE_GB=2 giữ lại một phần RAM cho engine, database, API và overhead ngắn hạn; mỗi scan runner có một mức reservation và hard cap là ENGINE_SCAN_RUNNER_MEMORY_MB=1536. Vì vậy, 2 worker cần khoảng 5 GB trước khi các thành phần khác chạy. Engine chỉ cho phép chạy số runner phù hợp với ngân sách còn lại. Do đó, trên máy cấu hình nhỏ, các scan sẽ xếp hàng thay vì fail. Đây là cách xử lý tốt hơn nhiều so với để out-of-memory killer kết thúc tiến trình. Phép tính tương tự cũng xác định mức tối thiểu cho mọi công cụ cấp một container riêng cho mỗi đơn vị công việc. Vì vậy, mỗi AI coworker của OpenBot có một container và browser riêng sẽ chạm giới hạn RAM từ lâu trước khi chạm giới hạn CPU.
Hai tùy chọn prune mặc định là true: ENGINE_AUTO_PRUNE_DOCKER_BUILD_CACHE và ENGINE_AUTO_PRUNE_UNUSED_DOCKER_IMAGES. Sau khi task hoàn tất, engine xóa build cache không còn dùng, image không còn dùng và các scan container đã dừng. Các image được container đang chạy tham chiếu, bind mount, dữ liệu database, credential và volume được giữ nguyên. Đây cũng là một lý do nữa để không dùng chung host: một pruner mà bạn không cấu hình đang chạy trên Docker daemon đó.
Các thiết lập engine mà hầu hết mọi người đều phải thay đổi
ENGINE_WORKER_COUNT: tổng số worker slot dùng chung cho các bước scan và post-processing. Đặt thành 0 để tạm dừng nhận job mới.ENGINE_MAX_CONCURRENT_SCANS: số scan được phép chạy đồng thời. Scan đang xếp hàng sẽ chờ đến khi pool đang hoạt động trống.ENGINE_MAX_WORKERS_PER_SCAN: 0 sẽ chia đều tổng số slot cho các scan.ENGINE_HARNESS_TIMEOUT_SECONDS: mặc định là 7200. Đây là thời gian tối đa một job bị treo có thể chạy.ENGINE_MIN_FREE_STORAGE_GB: mức storage tối thiểu.ENGINE_IGNORE_LOW_STORAGE=truesẽ tắt cơ chế bảo vệ, và file cảnh báo rằng điều này có thể làm đầy disk của host.ENGINE_SCAN_RUNNER_MEMORY_MB: hard cap RAM cho mỗi runner. 0 sẽ bỏ giới hạn.
Quét một repository cục bộ mà không làm lộ dữ liệu
LOCAL_REPOS_PATH mặc định là ./local_repos và được bind-mount vào các container backend và engine tại /local_repos. Vì vậy, repository bạn đặt vào thư mục đó trên host sẽ xuất hiện ngay bên trong các container. Hãy dùng một bản clone mới, không dùng working tree đang làm việc. Container chạy job có một bản sao có quyền ghi, có root bên trong container và có quyền truy cập Internet outbound. Điều này có nghĩa là mọi dữ liệu nằm trong bản sao đó đều có thể bị thay đổi hoặc gửi ra ngoài máy chủ. Hãy xóa các file .env và private key trước khi copy project vào.
Những gì bạn nhận được và những gì bạn không nhận được
Bạn nhận được danh sách các phát hiện ứng viên đã được xếp hạng. Bạn không nhận được các lỗ hổng đã được xác minh. Việc xếp hạng và loại bỏ bản trùng quyết định thứ tự trong hàng đợi triage của bạn. Chúng không chứng minh rằng một mục là phát hiện thật. Post-script có thể thử xác thực và tạo proof of concept. Đây là tín hiệu mạnh nhất mà tool cung cấp. Tuy nhiên, post-script bị lỗi không phải là bằng chứng cho thấy phát hiện đó là giả. Vẫn cần có người đọc từng ứng viên. Khoảng cách giữa một ứng viên và một proof là lý do yêu cầu agent cung cấp bằng chứng mà bạn có thể tự chạy lại rất hữu ích ở đây: một phát hiện có thể tái hiện theo yêu cầu có giá trị hơn một phát hiện chỉ được xếp hạng mà bạn phải tin tưởng.
Guide này không khẳng định open-kritt tìm được bao nhiêu bug thật, vì chúng tôi chưa đo lường điều đó. Bất kỳ ai đưa ra detection rate cho codebase của bạn đều chưa chạy tool trên codebase đó. Trước tiên, hãy scan một repository mà bạn đã hiểu rõ: những phát hiện bạn có thể tự đánh giá là cách calibration ít tốn kém nhất.
Authorization ở đây quan trọng hơn so với hầu hết các tool tự host. Các agent compile và execute code, đồng thời truy cập network, nên bước proof-of-concept có thể tác động đến các system đang chạy. Chỉ trỏ tool vào code do bạn sở hữu hoặc được thuê để test, và ghi rõ target scope trước khi chạy bất cứ thứ gì. Nếu bạn cấu hình ANTHROPIC_API_KEY và dùng Claude Code engine, các thói quen sandbox khi chạy Claude Code an toàn trên VPS cũng áp dụng cho những agent này.
FAQ
Vì sao open-kritt cần VPS riêng?
Vì các analysis agent chạy dưới quyền root bên trong những job container có thể hủy bỏ, sử dụng bản sao code có quyền ghi và truy cập Internet trực tiếp. Ngoài ra, engine service mount Docker socket của host để có thể khởi chạy một container cho mỗi job. Bất kỳ process nào truy cập được socket đó đều có thể khởi chạy container mount filesystem của host. Vì vậy, bạn nên xem toàn bộ stack như đang chạy dưới quyền root trên host của nó. Trên VPS riêng, đây là một đánh đổi chấp nhận được và việc dựng lại máy không gây mất mát gì. Trên workstation bạn dùng hằng ngày, cách này đặt SSH key và browser profile của bạn trong cùng trust boundary với code đang được quét.
Có thể expose port 5173 thay vì dùng SSH tunnel không?
Không nên. Backend được phát hành mà không có application authentication, nên port này là lớp bảo vệ duy nhất giữa Internet với kết quả phân tích và credit của provider. Vì lý do đó, compose file bind mọi service vào 127.0.0.1. Thay vào đó, hãy chạy ssh -N -L 5173:127.0.0.1:5173 you@your-server-ip rồi truy cập http://localhost:5173 trên máy local. Rule của ufw không thay thế được cách này, vì published Docker port được xử lý trước khi default policy của ufw áp dụng.
Làm thế nào để ngăn open-kritt tiêu tốn nhiều hơn mức dự kiến?
Hãy đặt hard limit trong console của model provider trước lần scan đầu tiên, vì open-kritt không có budget setting riêng. Giữ nguyên concurrency default được phát hành cho vài lần chạy đầu, ENGINE_WORKER_COUNT=2 và ENGINE_MAX_CONCURRENT_SCANS=1. Lưu ý rằng một provider account mặc định cho phép tối đa 15 root model call chạy đồng thời, trong khi một Codex session có thể chạy tối đa 5 child agent. ENGINE_WORKER_COUNT=0 sẽ tạm dừng việc nhận job mới và là cách dừng local nhanh nhất.
Nên checkout version nào?
Một tag, không bao giờ là main. git fetch --tags theo sau bởi git tag --list sẽ hiển thị các bản có sẵn. v1.3.0, được phát hành vào ngày 4 August 2026, là bản mới nhất tại thời điểm viết tài liệu này. Việc pin version bảo đảm rằng một lần rebuild sau vài tháng vẫn tạo ra cùng một stack. Nó cũng biến việc nâng cấp thành quyết định được đưa ra sau khi đọc release notes, thay vì là hệ quả ngẫu nhiên của việc clone vào một ngày khác.
Scan không bao giờ bắt đầu. Cần kiểm tra gì?
Trước tiên, hãy kiểm tra dung lượng disk còn trống, vì engine sẽ không khởi chạy scan container riêng cho từng job khi dung lượng còn trống thấp hơn ENGINE_MIN_FREE_STORAGE_GB; giá trị mặc định là 20 GB. Tiếp theo, kiểm tra ENGINE_WORKER_COUNT không có giá trị 0, vì giá trị đó sẽ tạm dừng việc nhận job mới. Sau đó, xác nhận model credential đã được cấu hình thực sự bằng cách chạy ./kritt setup, vì chỉ có GITHUB_TOKEN thì không thể chạy scan. docker compose logs engine cho biết lý do job bị bỏ qua.