1. Giới thiệu chung
Trong quá trình phát triển các ứng dụng web hiện đại, React là một trong những công nghệ được sử dụng phổ biến nhờ khả năng xây dựng giao diện linh hoạt, tái sử dụng thành phần và hỗ trợ phát triển ứng dụng có quy mô lớn.
Tuy nhiên, bên cạnh những lợi ích về hiệu quả phát triển, việc sử dụng React cũng đặt ra các yêu cầu về bảo đảm an toàn thông tin, đặc biệt trong hoạt động quản lý cấu hình, thông tin xác thực và đóng gói sản phẩm trước khi phát hành.
Một trong những vấn đề đáng quan tâm là tình trạng API Secret, Access Token, JWT Signing Key hoặc Private Key bị đưa vào các tệp JavaScript Bundle và phân phối công khai tới trình duyệt người dùng.
Vấn đề này càng trở nên đáng chú ý trong bối cảnh các công cụ kiểm thử xâm nhập tự động ứng dụng trí tuệ nhân tạo, điển hình như ARTEX, ngày càng có khả năng tự động thu thập thông tin, phân tích bề mặt tấn công và thực hiện các bước kiểm tra bảo mật theo kết quả phát hiện được.
Trước đây, việc phân tích hàng loạt JavaScript Bundle thường đòi hỏi sự tham gia trực tiếp của chuyên gia kiểm thử hoặc các công cụ chuyên dụng. Hiện nay, quá trình này có thể được tích hợp vào chuỗi tác vụ tự động, qua đó giảm công sức thực hiện và mở rộng phạm vi phát hiện.
Do đó, việc kiểm soát thông tin bí mật trong sản phẩm frontend không chỉ là yêu cầu về chất lượng mã nguồn mà còn là một biện pháp quan trọng nhằm hạn chế nguy cơ bị khai thác từ các hệ thống tự động.
2. Tổng quan về React
React là thư viện JavaScript mã nguồn mở, ban đầu được phát triển bởi Facebook (nay là Meta), phục vụ xây dựng giao diện người dùng theo mô hình thành phần (Component-based Architecture).
Khác với các framework cung cấp tương đối đầy đủ thành phần phát triển ứng dụng, React tập trung chủ yếu vào việc xây dựng và cập nhật giao diện. Các chức năng như điều hướng, quản lý dữ liệu hoặc giao tiếp với hệ thống backend có thể được triển khai thông qua thư viện và framework bổ sung.
React có một số đặc điểm chính:
- Component-based Architecture: Cho phép phân chia giao diện thành các thành phần độc lập, có khả năng tái sử dụng.
- Declarative UI: Developer mô tả giao diện theo trạng thái dữ liệu; React thực hiện cập nhật khi trạng thái thay đổi.
- State Management: Hỗ trợ quản lý dữ liệu và trạng thái trong quá trình người dùng tương tác.
- Client-side Rendering (CSR): Cho phép phần lớn hoạt động dựng giao diện diễn ra tại trình duyệt.
- Server-side Rendering (SSR): Có thể triển khai thông qua các framework phù hợp để thực hiện dựng giao diện tại máy chủ.
Tùy theo nhu cầu, ứng dụng React có thể được xây dựng theo mô hình Single Page Application (SPA), Server-side Rendering, Static Site Generation hoặc kết hợp các phương thức.
Trong phạm vi bài viết này, nội dung tập trung vào ứng dụng React có JavaScript được đóng gói và phân phối tới trình duyệt, đây là mô hình thường gặp trong các hệ thống web.
3. Kiến trúc ứng dụng React
3.1. Kiến trúc tổng thể
Đối với ứng dụng React theo mô hình SPA, kiến trúc có thể được phân chia thành ba lớp chính.
Lớp giao diện (Presentation Layer) chịu trách nhiệm hiển thị dữ liệu, tiếp nhận tương tác và cập nhật giao diện. Lớp này được tổ chức thông qua các React Component.
Lớp xử lý phía frontend (Client Application Layer) thực hiện quản lý trạng thái, điều hướng, xử lý dữ liệu hiển thị và gửi yêu cầu đến các dịch vụ API.
Lớp dịch vụ phía backend (Backend Service Layer) đảm nhiệm xử lý nghiệp vụ, xác thực, phân quyền, truy cập cơ sở dữ liệu, quản lý thông tin bí mật và tích hợp với các hệ thống bên ngoài.
Mô hình kiến trúc cơ bản:

Trong kiến trúc này, frontend và backend có trách nhiệm bảo mật khác nhau.
Frontend được thực thi trong môi trường trình duyệt do người sử dụng kiểm soát. Vì vậy, không thể bảo đảm tính bí mật của các giá trị được phân phối và xử lý hoàn toàn tại phía client.
Ngược lại, backend được triển khai trong môi trường máy chủ có kiểm soát, phù hợp để thực hiện những chức năng yêu cầu sử dụng credential của hệ thống, xử lý thông tin nhạy cảm và áp dụng các chính sách phân quyền.
Việc không xác định rõ ranh giới này là một trong những nguyên nhân cơ bản dẫn đến tình trạng đưa thông tin xác thực bí mật vào mã nguồn frontend.
3.2. Cấu trúc mã nguồn React
Một dự án React sử dụng Vite có thể được tổ chức như sau:
react-application/├── public/│ └── assets/├── src/│ ├── components/│ ├── pages/│ ├── hooks/│ ├── services/│ ├── context/│ ├── App.jsx│ └── main.jsx├── index.html├── package.json├── vite.config.js└── .env
Trong đó, main.jsx thường là điểm khởi tạo ứng dụng, App.jsx là thành phần giao diện cấp cao, services/ có thể chứa logic gọi API và .env được sử dụng để cung cấp biến cấu hình cho môi trường phát triển hoặc build.
Cấu trúc trên mang tính tham khảo. React không bắt buộc áp dụng một cấu trúc thư mục cố định.
3.3. Cơ chế hoạt động trên trình duyệt
Khi người dùng truy cập ứng dụng React, trình duyệt tải tài liệu HTML và các tài nguyên JavaScript, CSS, hình ảnh cùng các thành phần liên quan.
JavaScript được thực thi để khởi tạo ứng dụng và xử lý tương tác. Các yêu cầu nghiệp vụ tiếp theo có thể được thực hiện qua API.
Cần lưu ý rằng các tài nguyên đã được phân phối tới trình duyệt có thể được truy cập, lưu trữ và phân tích bằng những công cụ thông dụng như Developer Tools hoặc các công cụ phân tích JavaScript.
Do đó, việc một thông tin không được hiển thị trực tiếp trên giao diện không đồng nghĩa với việc thông tin đó được bảo vệ.
4. JavaScript Bundle và cơ chế đóng gói ứng dụng
4.1. JavaScript Bundle là gì?
Trong quá trình phát triển, ứng dụng React thường bao gồm nhiều tệp JavaScript, JSX hoặc TypeScript, được tổ chức thành các module và sử dụng các thư viện phụ thuộc.
Trước khi phát hành, công cụ build thực hiện xử lý các thành phần này để tạo ra sản phẩm phù hợp cho môi trường production.
Quá trình build có thể bao gồm chuyển đổi cú pháp, phân tích dependency, đóng gói module, loại bỏ mã nguồn không cần thiết, rút gọn nội dung và phân chia JavaScript thành các chunk.
Các công cụ được sử dụng phổ biến gồm Vite, Webpack, Rollup và esbuild.
Ví dụ, ứng dụng React sau build có thể tạo ra:
dist/├── index.html└── assets/ ├── index-a1b2c3.js ├── index-d4e5f6.css ├── vendor-g7h8i9.js └── dashboard-j1k2l3.js
Tên file và cấu trúc thực tế phụ thuộc vào công cụ cũng như cấu hình build.
Các JavaScript Bundle có thể bao gồm mã nguồn ứng dụng, thư viện phụ thuộc, logic giao tiếp API và các giá trị cấu hình đã được nhúng trong quá trình xây dựng sản phẩm.
| Thành phần | Vai trò | Ai tạo ra? |
|---|---|---|
main.js | Chứa logic ứng dụng | Bundler |
runtime.js | Chứa logic nạp và quản lý module/chunk | Webpack khi cấu hình tách runtime |
vendor.js | Chứa thư viện bên thứ ba | Bundler khi cấu hình tách vendor |
chunk-*.js | Chứa module được tách để tải khi cần | Bundler |
polyfills.js | Bổ sung API cho môi trường chưa hỗ trợ | Build pipeline |
*.js.map | Ánh xạ bundle về source code | Build pipeline |
4.2. Vì sao thông tin bí mật có thể xuất hiện trong Bundle?
Xét ví dụ developer khai báo:
const config = { apiUrl: "https://api.example.com", apiSecret: "sk_example_sensitive_value"};
Nếu phần mã này được đưa vào sản phẩm build, giá trị apiSecret có thể tiếp tục tồn tại trong JavaScript Bundle sau khi đã được rút gọn.
Việc sử dụng các kỹ thuật minification hoặc obfuscation không phải là biện pháp bảo vệ thông tin bí mật. Những kỹ thuật này chủ yếu thay đổi cách biểu diễn mã nguồn, không tạo ra ranh giới bảo mật giữa trình duyệt và máy chủ.
Đối với những giá trị được ứng dụng phía client sử dụng, công cụ build có thể phải giữ lại giá trị hoặc logic tương ứng để ứng dụng vận hành.
Vì vậy, JavaScript Bundle phải được xem là tài nguyên có khả năng bị truy cập công khai.
5. Nguyên nhân lộ thông tin xác thực qua JavaScript Bundle
5.1. Hardcode thông tin bí mật trong mã nguồn
Một nguyên nhân thường gặp là developer khai báo trực tiếp API Secret, Service Token hoặc JWT Signing Key trong tệp JavaScript, JSX hoặc TypeScript.
Việc đặt những giá trị này trong file cấu hình riêng, module tiện ích hoặc mã nguồn đã được rút gọn không làm thay đổi khả năng truy cập từ phía client nếu giá trị được phân phối tới trình duyệt.
Nguyên nhân gốc rễ là chưa phân loại rõ thông tin công khai và thông tin cần bảo vệ tại máy chủ.
5.2. Sử dụng sai biến môi trường
Đối với ứng dụng React sử dụng Vite, các biến môi trường có tiền tố VITE_ mặc định được thiết kế để cung cấp giá trị cho mã nguồn phía client.
Ví dụ:
VITE_API_URL=https://api.example.comVITE_API_SECRET=sk_example_sensitive_value
Developer sử dụng:
const secret = import.meta.env.VITE_API_SECRET;
Trong quá trình build, tham chiếu trên có thể được thay thế bằng giá trị thực tế. Khi đó, secret xuất hiện trong JavaScript được phát hành.
Tình huống tương tự có thể phát sinh trong các dự án Create React App sử dụng biến REACT_APP_*.
Cần nhấn mạnh rằng việc quản lý giá trị trong .env, GitLab CI/CD Variables hoặc một công cụ quản lý cấu hình không bảo đảm thông tin được giữ bí mật nếu giá trị cuối cùng bị đưa vào sản phẩm frontend.
5.3. Thiết kế frontend gọi trực tiếp dịch vụ bằng credential của hệ thống
Một sai sót khác là sử dụng frontend để gọi trực tiếp các dịch vụ bên thứ ba bằng API Secret hoặc Service Token có quyền truy cập độc lập.
Ví dụ:
fetch("https://third-party.example.com/data", { headers: { Authorization: "Bearer <SERVICE_TOKEN>" }});
Trong trường hợp token là credential dùng chung của hệ thống, người dùng có thể quan sát token thông qua JavaScript hoặc yêu cầu mạng.
Thiết kế phù hợp là thực hiện các yêu cầu cần credential bí mật tại backend hoặc BFF. Frontend gửi yêu cầu theo phiên người dùng; backend kiểm tra xác thực, phân quyền và sử dụng credential của hệ thống để giao tiếp với dịch vụ bên ngoài.
5.4. Secret được đưa vào từ quy trình CI/CD
Một số trường hợp secret không tồn tại trong repository nhưng được cung cấp từ biến môi trường tại thời điểm build.
Nếu pipeline sử dụng secret để tạo nội dung JavaScript phía client, thông tin vẫn có thể xuất hiện trong sản phẩm sau build.
Đây là nguyên nhân khiến hoạt động Secret Scanning chỉ trên mã nguồn không thể phát hiện đầy đủ mọi trường hợp rò rỉ.
5.5. Công khai tài nguyên không phù hợp
Bên cạnh JavaScript Bundle, rủi ro còn có thể phát sinh từ việc công khai file cấu hình, JSON chứa credential, tài liệu debug hoặc source map có nội dung nhạy cảm.
Source map có thể hỗ trợ quá trình phân tích mã nguồn gốc, nhưng không phải mọi source map đều chứa secret và bản thân việc công khai source map không mặc nhiên đồng nghĩa với một lỗ hổng nghiêm trọng.
Việc đánh giá cần căn cứ vào nội dung thực tế được phân phối.
6. Tác động và mức độ nghiêm trọng của thông tin bị lộ
Không phải mọi chuỗi chứa từ khóa token, key hoặc secret trong JavaScript Bundle đều cấu thành lỗ hổng.
Việc đánh giá cần căn cứ vào loại thông tin, hiệu lực, phạm vi quyền và khả năng bị lợi dụng.
| Loại thông tin | Nguy cơ chính |
|---|---|
| JWT Signing Secret / Private Key | Có thể bị sử dụng để tạo JWT có chữ ký hợp lệ nếu khóa đang được tin cậy |
| Service Account Token | Truy cập các dịch vụ và tài nguyên theo phạm vi quyền được cấp |
| API Secret có quyền quản trị | Có thể thực hiện chức năng quản trị hoặc truy cập dữ liệu nhạy cảm |
| OAuth Client Secret | Làm suy giảm tính bí mật của client, tùy phương thức xác thực |
| OAuth Client ID | Thông thường là thông tin định danh được phép công khai |
| Publishable API Key | Có thể công khai theo thiết kế và điều kiện sử dụng của nhà cung cấp |
Đối với các credential có quyền hạn cao và còn hiệu lực, việc lộ lọt có thể dẫn đến rủi ro nghiêm trọng.
Tuy nhiên, không nên mặc định mọi trường hợp phát hiện token đều thuộc mức Critical. Mức độ nghiêm trọng cần được xác định thông qua phân tích tác động, quyền hạn và điều kiện khai thác thực tế.
7. Yêu cầu kiểm soát đối với hoạt động phát triển và phát hành
7.1. Kiểm soát tại giai đoạn thiết kế
Các hệ thống phải phân định rõ trách nhiệm giữa frontend và backend.
Các chức năng sử dụng credential bí mật của hệ thống phải được triển khai tại backend hoặc BFF, bảo đảm frontend không cần lưu trữ hoặc sử dụng trực tiếp thông tin xác thực thuộc phạm vi quản lý của máy chủ.
Các dịch vụ backend phải thực hiện xác thực, phân quyền và giới hạn truy cập độc lập, không phụ thuộc vào các cơ chế kiểm tra được thực hiện hoàn toàn trên trình duyệt.
7.2. Kiểm soát tại giai đoạn phát triển
Developer không được hardcode API Secret, Private Key, JWT Signing Key hoặc Service Account Token trong mã nguồn frontend.
Các biến cấu hình cần được phân loại rõ giữa thông tin công khai và thông tin bí mật. Những biến được công cụ build cung cấp cho phía client chỉ được sử dụng để chứa thông tin được phép công khai.
Thông tin bí mật phải được quản lý tại các hệ thống Secret Management phù hợp, với cơ chế phân quyền, ghi nhận truy cập và luân chuyển khóa theo yêu cầu bảo mật.
7.3. Kiểm soát tại giai đoạn CI/CD
Cần triển khai đồng thời các lớp kiểm tra bảo mật sau:
Source Secret Scanning: Kiểm tra mã nguồn, file cấu hình và các thay đổi trước khi merge.
Build Artifact Secret Scanning: Kiểm tra JavaScript Bundle, HTML, JSON, source map và các tài nguyên sau build nhằm phát hiện thông tin bí mật được nhúng từ mã nguồn hoặc biến môi trường CI/CD.
Release Validation: Kiểm tra những tài nguyên thực tế được công khai qua web server hoặc CDN, bảo đảm không phát hành thông tin xác thực ngoài phạm vi cho phép.
Các phát hiện được xác nhận có nguy cơ lộ credential phải được xử lý trước khi phát hành theo quy định quản lý rủi ro của tổ chức.
7.4. Kiểm thử liên tục đối với tài nguyên công khai
Bên cạnh các lớp kiểm tra trong CI/CD, cần xem xét triển khai hoạt động rà soát định kỳ đối với các tài nguyên JavaScript đang được phân phối trên môi trường production.
Hoạt động này giúp phát hiện những trường hợp tài nguyên cũ còn tồn tại trên CDN, cấu hình công khai không phù hợp hoặc sai lệch giữa sản phẩm đã kiểm tra trong pipeline và sản phẩm thực tế được triển khai.
Có thể sử dụng các công cụ phân tích JavaScript, Secret Scanning, Attack Surface Management và những nền tảng kiểm thử tự động có khả năng tích hợp phù hợp.
Việc sử dụng ARTEX hoặc công cụ tương tự trong hoạt động kiểm thử nội bộ cần được thực hiện trên các tài sản thuộc phạm vi được ủy quyền, có kiểm soát tác động, quản lý bằng chứng và xác minh kết quả trước khi kết luận.
7.5. Xử lý khi phát hiện secret bị lộ
Khi phát hiện thông tin xác thực bị lộ, đơn vị phát triển phải phối hợp với bộ phận an ninh thông tin để xác định hiệu lực, phạm vi ảnh hưởng và thực hiện biện pháp xử lý tương ứng.
Đối với credential đã bị công khai và có khả năng sử dụng trái phép, cần ưu tiên thu hồi hoặc thay thế, kiểm tra log truy cập và rà soát những tài nguyên còn lưu trữ thông tin cũ.
Không được xem việc xóa secret khỏi mã nguồn hoặc build lại frontend là biện pháp xử lý đầy đủ nếu credential cũ vẫn còn hiệu lực.
8. So sánh JavaScript Bundle giữa React và Angular
React và Angular có những khác biệt về kiến trúc, phương thức tổ chức ứng dụng và cơ chế xử lý giao diện. Tuy nhiên, cả hai đều có thể sử dụng các công cụ build để chuyển đổi và đóng gói mã nguồn thành những tài nguyên JavaScript được phân phối tới trình duyệt.
Đối với React, hoạt động build phụ thuộc vào công cụ hoặc framework được lựa chọn, chẳng hạn Vite hoặc Webpack.
Đối với Angular, hoạt động build được quản lý thông qua Angular CLI và các builder tương ứng. Những phiên bản Angular hiện đại sử dụng pipeline build dựa trên esbuild và Vite theo cấu hình hỗ trợ.
| Nội dung | React | Angular |
|---|---|---|
| Bản chất | Thư viện UI | Frontend framework |
| Cấu trúc giao diện | Components và JSX | Components và Templates |
| Công cụ build | Vite, Webpack và các giải pháp khác | Angular CLI và các builder |
| Sản phẩm sau build | JavaScript Bundle và Chunks | JavaScript Bundle và Chunks |
| Cấu hình frontend | Phụ thuộc công cụ, ví dụ VITE_* | Environment files và cấu hình build |
| Nguy cơ lộ secret | Có khi secret được đưa vào client bundle | Có khi secret được đưa vào client bundle |
| Khả năng bị công cụ tự động phân tích | Có | Có |
| Biện pháp kiểm soát | Phân tách secret về backend, quét source và build artifact | Tương tự React |
Ví dụ, Angular có thể khai báo thông tin cấu hình trong environment.ts. Khi được sử dụng trong mã nguồn client, các giá trị này có thể được đưa vào JavaScript Bundle.
Cơ chế này có bản chất tương tự việc React sử dụng Vite để nhúng các biến cấu hình frontend trong quá trình build.
Do đó, dù React và Angular có kiến trúc nội bộ khác nhau, nguy cơ lộ thông tin xác thực qua sản phẩm JavaScript là vấn đề chung đối với các ứng dụng frontend.
| Tiêu chí | Angular | React |
|---|---|---|
| Nhà phát triển | Meta | |
| Loại | Frontend framework | UI library |
| Package chính | @angular/core | react, react-dom |
| Build output | main.js, chunk-*.js | main.js, index-*.js, chunks |
| Rendering | Angular runtime | React DOM |
Các công cụ kiểm thử tự động không nhất thiết phải phân biệt chính xác framework đang sử dụng để phát hiện những chuỗi credential được công khai trong tài nguyên JavaScript.
Điều này cho thấy biện pháp phòng ngừa cần tập trung vào quản lý thông tin bí mật, thiết kế ranh giới bảo mật và kiểm soát sản phẩm trước khi phát hành, thay vì chỉ dựa trên đặc điểm bảo mật riêng của từng framework.