Chuyển đến nội dung
Chandler Nguyen
AI11 phút đọc

Tôi đang nghĩ về vibe coder và chuyện bảo trì. Rồi tôi kiểm tra chính các app của mình.

Một cảnh báo bảo mật của Next.js chạm đến ba app của chính tôi. Một trong số đó có 175 commit trong tháng ấy mà 20 ngày sau vẫn chưa được vá, vì chẳng có gì hỏng cả. Những gì tôi tìm thấy, và vài bước kiểm tra nhỏ mà giờ tôi nghĩ ai xây sản phẩm cũng cần.

Ở chỗ làm, tôi làm việc với khá nhiều vibe coder, và điều đó khiến tôi nghĩ về chuyện bảo trì. Phần mềm cần được chăm sóc sau khi ra mắt, package thì cũ đi, lỗ hổng thì liên tục được phát hiện, và khi mới bắt đầu xây sản phẩm bằng AI thì rất dễ không nghĩ tới những chuyện này.

Rồi tôi nhận ra mình nên áp dụng cách nghĩ đó cho chính các dự án của mình, nên tôi lật lại lịch sử git của chúng. Tôi tìm thấy khá nhiều thứ, tuy không phải những gì tôi dự đoán, và một trong số đó vẫn còn mở vào đúng ngày tôi kiểm tra.

Vậy nên bài này không còn là về những gì người khác quên nữa. Nó là về tôi.

Một cảnh báo, ba app

Ngày 14 tháng 10, Next.js phát hành bản vá cho hai lỗ hổng nghiêm trọng (critical) và một lỗ hổng mức cao (high). "Critical" ở đây nghĩa là thực thi mã từ xa, tức là trong một số điều kiện, một người lạ trên internet có thể chạy code của họ trên server của bạn mà không cần đăng nhập. Bản vá có trong phiên bản 16.4.1.

Tôi chạy ba app trên Next.js, đều xây bằng các công cụ AI coding và đều do cùng một người chịu trách nhiệm, là tôi. Đây là những gì đã xảy ra:

AppKhi nào code chuyển sang bản đã váThời gian sau cảnh báoLên production
chandlernguyen.com18 tháng 104 ngày18 tháng 10
DIALØGUE29 tháng 1015 ngày29 tháng 10
Prova3 tháng 1120 ngày3 tháng 11

Khi tôi kiểm tra vào ngày 3 tháng 11, Prova vẫn bị khóa ở Next.js 16.2.6, phiên bản nó dùng từ tháng 5, và production đang chạy đúng bản đó. Chạy pnpm audit --prod cho ra 5 cảnh báo critical, tất cả đều nằm trong Next.js.

Con số đó nghe tệ hơn những gì tôi tìm thấy, nên tôi muốn công bằng. Khi cùng các công cụ AI đi qua từng cảnh báo, tôi thấy đa số cảnh báo critical không áp dụng cho Prova. Nó không có hàm use cache, không chạy trên Windows, và tôi không tìm thấy dữ liệu đầu vào nào do kẻ tấn công kiểm soát đi tới được code next/og. Các cảnh báo về tối ưu hình ảnh có lẽ cũng không áp dụng trên Vercel, tuy nhiên đó là suy luận từ cách nền tảng này hoạt động, vì tôi không tìm được tuyên bố nào của Vercel nói như vậy. Điều chắc chắn áp dụng là một lỗi từ chối dịch vụ mức cao trong Server Actions, nghĩa là ai đó có thể làm gián đoạn app, và lỗi này vẫn tồn tại cho tới khi được vá.

Tôi chuyển production sang Next.js 16.4.5 ngay trong ngày. Kết quả audit giảm từ 5 critical xuống 0, và phần lớn cảnh báo mức cao cũng biến mất. Phần còn lại nằm ngoài Next.js và tôi vẫn phải xử lý tiếp, nên tình hình đã tốt hơn nhưng chưa sạch.

Tôi không thể gọi bất kỳ điều gì ở trên là bảo trì tốt. Những cảnh báo tệ nhất phần lớn không chạm tới Prova là nhờ cách nó tình cờ được xây, chứ không phải vì có ai kiểm tra. Đó là may mắn, và tôi chỉ biết vì cuối cùng tôi cũng chịu nhìn.

Tôi cũng không thể nhận nhiều công cho dòng đầu tiên của bảng. Commit ngày 18 tháng 10 trên site của tôi là một lần nâng cấp toolchain chung, từ Next.js 16.1.6, và nội dung commit không hề nhắc tới cảnh báo. Có thể đơn giản chỉ là đúng lúc.

Vì sao bận rộn chẳng giúp được gì

Cách giải thích dễ nhất là Prova đã bị bỏ bê, và điều đó cũng có phần đúng. Nó có một khoảng cách 41 ngày giữa hai commit trong tháng 8 và tháng 9.

Tuy nhiên, cách giải thích đó sụp đổ khi nhìn vào tháng 10. Prova có 175 commit trong tháng đó. Tôi đã phát hành các bản sửa cho iOS, thanh toán, email và bản dịch, còn phiên bản Next.js thì không nhúc nhích. Ngày 30 tháng 10, tôi thậm chí còn viết một kế hoạch bảo trì dependency. Dòng trạng thái của nó ghi: "đã lên kế hoạch; chưa bắt đầu triển khai."

Vậy nên hoạt động không phải là bảo trì, và tôi nghĩ đây là phần quan trọng nhất với bất kỳ ai xây sản phẩm bằng AI. Khi làm một tính năng, bạn có phản hồi ngay lập tức. Bạn thấy cái nút, bấm vào và nhìn nó chạy. Khi một lỗ hổng được công bố trong một package bạn cài từ năm tháng trước, bạn chẳng nhận được gì cả. App vẫn phục vụ trang, test vẫn xanh và build vẫn qua. Không có gì hỏng, và chính vì thế mà chẳng ai nhìn.

Nó giống như một chiếc báo cháy chỉ kêu nếu bạn đi tới và hỏi nó. Công cụ AI giúp xây nhà rẻ đi rất nhiều. Chúng không lắp báo cháy cho bạn.

Chuyện này cũng từng xảy ra với tôi. DIALØGUE không có commit nào từ ngày 6 tháng 10 năm 2025 đến ngày 11 tháng 1 năm 2026. React2Shell, một lỗ hổng critical của Next.js và React, được công khai vào đầu tháng 12. Commit vá của tôi đề ngày 11 tháng 1, hơn năm tuần sau đó, và bản ghi deployment production cũ nhất tôi còn xem được là từ ngày 18 tháng 1, nên tôi không thể nói chính xác điều gì đang chạy vào tháng 12. Điều có lẽ giúp tôi nhiều nhất lại chẳng liên quan gì đến tôi: Vercel nói họ đã triển khai các quy tắc tường lửa cho mọi khách hàng, đồng thời cũng nói thẳng là đừng dựa vào chúng thay cho việc vá.

Tôi muốn nói rõ một điều, vì cụm "vibe coder" có thể nghe như một lời chê. Tôi học code qua các khóa học bài bản, trong đó có một chứng chỉ an ninh mạng, các repo này có test và các bước review, vậy mà chuyện này vẫn xảy ra. Nên tôi không nghĩ đây là vấn đề của người mới. Tôi nghĩ đây là điều mặc định sẽ xảy ra khi tạo ra phần mềm trở nên rẻ và không ai được giao việc trông chừng nó.

Chiếc báo cháy trông như thế nào

Tin tốt là một bước kiểm tra rất nhỏ thay đổi được khá nhiều.

Ngày 27 tháng 10, tôi thêm pnpm audit --prod vào CI của site, tức là bộ kiểm tra tự động chạy mỗi khi code được push. Ngày 28 tháng 10 nó báo lỗi, vì một cảnh báo trong thư viện render email mà site của tôi kéo theo nhưng chưa bao giờ dùng, và ngày 29 tháng 10 tôi gỡ thư viện đó. Mất hai ngày và tôi không phải nhớ gì cả.

Chẳng có gì thông minh ở đây. Chỉ là phải có ai đó, hoặc cái gì đó, được giao việc nhìn vào.

Cập nhật package cũng sẽ không cứu được tôi

Nếu bài này dừng ở đó, nó sẽ cho bạn một sự yên tâm giả, vì một số bản sửa khó chịu hơn mà tôi làm trong tháng 10 chẳng liên quan gì đến package lỗi thời. Đó là lỗi trong chính sản phẩm của tôi.

Ví dụ trong DIALØGUE:

  • Các hàm database có đặc quyền có thể được gọi chỉ với public key, và một người dùng đã đăng nhập có thể chạm tới những hàm vốn chỉ dành cho backend của tôi.
  • Một podcast đã hoàn tiền có thể sống lại và hoàn thành mà không bị tính phí lần nữa, và điều đó đã thật sự xảy ra với một số ít podcast.

Trong Prova, các role database công khai đã thừa hưởng những quyền không ai chọn, như TRUNCATE, vì chúng đến từ cấu hình mặc định của nền tảng. Không có đường nào trong app dùng tới, nhưng chúng vẫn ở đó.

Không có lần nâng cấp phiên bản nào sửa được những chuyện này. Các app này đã chạy cùng những vấn đề đó, và ở đây cũng chẳng có gì hỏng. Chúng chỉ lộ ra vì một đợt security review đã đi tìm, và trước đó tôi cũng từng làm các đợt security review trên những sản phẩm này mà không phát hiện được hết.

Có ai lợi dụng những lỗi này không? Tôi không biết, và tôi cũng không thể tìm ra đầy đủ. Log database của tôi chỉ lưu khoảng 90 ngày, và một số vấn đề này còn cũ hơn thế. Trong khoảng thời gian tôi xem được, tôi không tìm thấy lời gọi nào từ bên ngoài tới các hàm đó. Điều này chỉ bao phủ một khoảng thời gian có hạn và không chứng minh rằng chẳng có gì xảy ra. Tôi cũng không biết ai hay cái gì đã khởi động lại những podcast đã hoàn tiền đó, dù phỏng đoán tốt nhất của tôi là một lần retry trong chính code của mình.

Đây cũng không phải danh sách đầy đủ. Tôi vẫn đang xử lý những vấn đề khác, và tôi sẽ không mô tả chúng khi chúng còn đang mở.

Câu chuyện về model không như tôi tưởng

Tôi đã định viết rằng model AI cũ khiến bạn tốn tiền hơn. Tôi không chứng minh được điều đó. Tôi đã tìm, và trong các repo này không hề có số liệu chi phí trước và sau, nên nói vậy chỉ là đoán.

Điều tôi tìm thấy còn lạ hơn. Các model viết nháp kịch bản và review của DIALØGUE được đặt là gemini-3.5-flash cho tới ngày 12 tháng 10. Tài liệu của Google giờ nói rằng endpoint đó không bị tắt và tự động chuyển request sang gemini-3.6-flash. Tôi không biết việc chuyển hướng bắt đầu từ khi nào, nên không thể nói model nào đã trả lời và trong bao lâu. Một cái tên model trong config không đóng băng được model.

Giá cũng thay đổi. Gemini 3.8 Flash, model tôi chuyển Prova và DIALØGUE sang trong tháng 10, đang niêm yết ở mức $0.75 cho mỗi triệu token đầu vào và $3.75 cho mỗi triệu token đầu ra đến hết 31 tháng 12. Từ ngày 1 tháng 1 năm 2027, giá tăng gấp đôi lên $1.50 và $7.50. Tôi mới chuyển sang từ tháng trước, và vẫn phải tính xem điều đó ảnh hưởng đến chi phí của mình ra sao.

Nâng cấp cũng không phải là không có rủi ro, và tôi không muốn giả vờ như vậy:

  • Ngay sau khi tôi chuyển Ask Sydney sang DeepSeek Flash, ít nhất một câu hỏi đã thất bại trên production vì model muốn nhiều vòng tìm kiếm hơn mức code của tôi cho phép.
  • Khi tôi chuyển embedding của Prova, các lần chạy đầu tiên thất bại sau khi bảng knowledge base đã bị xóa sạch, nên chất lượng retrieval giảm cho tới khi tôi khôi phục được.
  • Chính đợt migration đó còn phát hiện một thiết lập production lẽ ra sẽ gửi tên model Claude tới API của Google.

Thứ khiến đợt migration của Prova đáng tin lại rất nhàm chán. Tôi có một bản đánh giá được ghi lại từ trước khi đổi, trả lời đúng 22 trên 24 quyết định sprint review, và sau đó tôi đạt cùng điểm số ấy. Không có nó thì tôi chỉ biết hy vọng.

Có nên bảo trì nó không?

Câu hỏi khó nhất là câu tôi suýt bỏ qua. Mỗi app bạn ra mắt là một gói thuê bao nhỏ mà bạn phải tiếp tục trả bằng sự chú ý của mình, và AI đã khiến việc đăng ký thêm trở nên rất dễ.

Với STRAŦUM, tôi đã chọn hướng khác. Tôi cho nó nghỉ hưu và công bố v1 như một bản triển khai tham khảo. Repository ghi rõ nó không được bảo trì như một dịch vụ production, và file bảo mật liệt kê các cảnh báo tôi biết, trong đó có những vấn đề dependency lúc build chưa có bản sửa, được đánh giá là rủi ro thực tế thấp. Tôi nghĩ đó là một kết cục hoàn toàn trung thực. Phiên bản không trung thực là một app vẫn nhận đăng ký trong khi chẳng ai trông chừng nó.

Tôi sẽ làm gì vào thứ Hai

Nếu bạn đã ra mắt thứ gì đó bằng AI và có người đang dùng, đây là danh sách tôi sẽ bắt đầu. Không có mục nào cần phải hoàn hảo.

  1. Chạy audit một lần, ngay hôm nay. npm audit --omit=dev hoặc pnpm audit --prod, trong mọi repo đang chạy thật. Rồi hỏi công cụ AI của bạn xem kết quả nào áp dụng cho cách app của bạn được xây, vì không phải tất cả đều áp dụng.
  2. Biến việc kiểm tra đó thành tự động. Thêm vào CI để nó có thể báo lỗi mà bạn không cần nhớ. Với app ít đụng tới, hãy dùng thêm lịch chạy định kỳ, vì một bước kiểm tra chỉ chạy khi push sẽ không giúp được gì trong một tháng yên ắng.
  3. Đọc phân quyền, không chỉ đọc package. Hỏi xem ai đó có thể làm gì chỉ với public key của bạn mà không cần tài khoản. Rồi thử trên bản sao local thay vì giả định.
  4. Tìm secret trong lịch sử git. Nếu một key thật từng bị commit, hãy thu hồi nó. Xóa dòng đó là chưa đủ.
  5. Tìm hiểu log của bạn lưu được bao xa trước khi cần đến nó. Điều đó quyết định bạn có bao giờ trả lời được câu hỏi liệu có gì đã bị lợi dụng hay không.
  6. Giữ một bài đánh giá nhỏ cho mỗi tính năng AI. Chỉ cần 20 ví dụ thật có câu trả lời đúng đã biết cũng cho bạn thấy việc đổi model có làm mọi thứ tệ đi không.
  7. Kiểm tra định kỳ các trang ngừng hỗ trợ và bảng giá của nhà cung cấp AI. Đưa vào lịch. Nếu bạn đang dùng Gemini 3.8 Flash, ngày 1 tháng 1 đã là một mốc tốt để bắt đầu.
  8. Quyết định giữ hay cho nghỉ từng app. Nếu bạn không định trông chừng nó, hãy gỡ xuống hoặc nói rõ điều đó.

Tôi vẫn đang tìm hiểu xem có thể giao bao nhiêu trong số này cho một agent chạy theo lịch. Điều tôi biết là việc viết kế hoạch vào ngày 30 tháng 10 chẳng vá được gì.

Nếu bạn đang xây sản phẩm bằng AI, lần cuối bạn kiểm tra thứ mình đã ra mắt là khi nào, và bạn đã tìm thấy gì? Tôi thật sự muốn biết, nhất là nếu bạn có một chiếc báo cháy tốt hơn của tôi.