Loading...
Loading...
Compare original and translation side by side
$import { $ } from "bun";
const userInput = "hello; rm -rf /";
// ✓ SAFE: Tagged template - automatically escapes
await $`echo ${userInput}`;
// Executes: echo 'hello; rm -rf /'
// ❌ CRITICAL: Spawning a new shell (bypasses Bun escaping)
await $`bash -c "echo ${userInput}"`;
// The nested shell interprets user input as code$import { $ } from "bun";
const userInput = "hello; rm -rf /";
// ✓ 安全:Tagged template - 自动转义
await $`echo ${userInput}`;
// 执行:echo 'hello; rm -rf /'
// ❌ 严重:生成新Shell(绕过Bun转义)
await $`bash -c "echo ${userInput}"`;
// 嵌套Shell会将用户输入解析为代码import { $ } from "bun";
// ❌ HIGH: Argument injection via -- prefix
const userRepo = "--upload-pack=id>/tmp/pwned";
await $`git ls-remote ${userRepo} main`;
// The -- prefix makes it a command-line argument, not a value
// ✓ Validate input format before use
const userRepo = getUserInput();
if (!userRepo.match(/^https?:\/\//)) {
throw new Error("Invalid repository URL");
}
await $`git ls-remote ${userRepo} main`;
// ✓ Or use -- to end argument parsing
await $`git ls-remote -- ${userRepo} main`;import { $ } from "bun";
// ❌ 高危:通过--前缀进行参数注入
const userRepo = "--upload-pack=id>/tmp/pwned";
await $`git ls-remote ${userRepo} main`;
// --前缀会将其视为命令行参数,而非值
// ✓ 使用前验证输入格式
const userRepo = getUserInput();
if (!userRepo.match(/^https?:\/\//)) {
throw new Error("无效的仓库URL");
}
await $`git ls-remote ${userRepo} main`;
// ✓ 或者使用--结束参数解析
await $`git ls-remote -- ${userRepo} main`;sqlimport { sql } from "bun";
const userId = "1 OR 1=1";
// ❌ CRITICAL: Function call - SQL injection!
await sql(`SELECT * FROM users WHERE id = ${userId}`);
// Executes: SELECT * FROM users WHERE id = 1 OR 1=1
// ✓ SAFE: Tagged template - parameterized query
await sql`SELECT * FROM users WHERE id = ${userId}`;
// Executes: SELECT * FROM users WHERE id = $1 with params ['1 OR 1=1']sqlimport { sql } from "bun";
const userId = "1 OR 1=1";
// ❌ 严重:函数调用 - SQL注入!
await sql(`SELECT * FROM users WHERE id = ${userId}`);
// 执行:SELECT * FROM users WHERE id = 1 OR 1=1
// ✓ 安全:Tagged template - 参数化查询
await sql`SELECT * FROM users WHERE id = ${userId}`;
// 执行:SELECT * FROM users WHERE id = $1,参数为 ['1 OR 1=1']import { Database } from "bun:sqlite";
const db = new Database("mydb.sqlite");
const userInput = "'; DROP TABLE users; --";
// ❌ CRITICAL: String interpolation
db.run(`INSERT INTO logs VALUES ('${userInput}')`);
// ✓ SAFE: Parameterized with .run()
db.run("INSERT INTO logs VALUES (?)", [userInput]);
// ✓ SAFE: Prepared statements
const stmt = db.prepare("SELECT * FROM users WHERE id = ?");
stmt.get(userInput);
// ✓ SAFE: Query with parameters
db.query("SELECT * FROM users WHERE email = ?").get(userInput);import { Database } from "bun:sqlite";
const db = new Database("mydb.sqlite");
const userInput = "'; DROP TABLE users; --";
// ❌ 严重:字符串插值
db.run(`INSERT INTO logs VALUES ('${userInput}')`);
// ✓ 安全:使用.run()进行参数化
db.run("INSERT INTO logs VALUES (?)", [userInput]);
// ✓ 安全:预编译语句
const stmt = db.prepare("SELECT * FROM users WHERE id = ?");
stmt.get(userInput);
// ✓ 安全:带参数的查询
db.query("SELECT * FROM users WHERE email = ?").get(userInput);// ❌ No input validation
Bun.serve({
fetch(req) {
const url = new URL(req.url);
const file = url.searchParams.get("file");
return new Response(Bun.file(`./uploads/${file}`)); // Path traversal!
},
});
// ✓ Validate and sanitize
import { join, basename, resolve } from "path";
Bun.serve({
fetch(req) {
const url = new URL(req.url);
const file = url.searchParams.get("file");
// Sanitize filename
const safeName = basename(file ?? "");
const uploadsDir = resolve("./uploads");
const filePath = resolve(join(uploadsDir, safeName));
// Verify path is within uploads directory
if (!filePath.startsWith(uploadsDir)) {
return new Response("Forbidden", { status: 403 });
}
return new Response(Bun.file(filePath));
},
});// ❌ 无输入验证
Bun.serve({
fetch(req) {
const url = new URL(req.url);
const file = url.searchParams.get("file");
return new Response(Bun.file(`./uploads/${file}`)); // 路径遍历!
},
});
// ✓ 验证并清理
import { join, basename, resolve } from "path";
Bun.serve({
fetch(req) {
const url = new URL(req.url);
const file = url.searchParams.get("file");
// 清理文件名
const safeName = basename(file ?? "");
const uploadsDir = resolve("./uploads");
const filePath = resolve(join(uploadsDir, safeName));
// 验证路径是否在uploads目录内
if (!filePath.startsWith(uploadsDir)) {
return new Response("禁止访问", { status: 403 });
}
return new Response(Bun.file(filePath));
},
});// ❌ No body size limit (large uploads can exhaust memory)
Bun.serve({
fetch(req) {
return new Response("ok");
},
});
// ✓ Set a max request body size
Bun.serve({
maxRequestBodySize: 1_000_000, // 1 MB
fetch(req) {
return new Response("ok");
},
});// ❌ 无请求体大小限制(大上传会耗尽内存)
Bun.serve({
fetch(req) {
return new Response("ok");
},
});
// ✓ 设置最大请求体大小
Bun.serve({
maxRequestBodySize: 1_000_000, // 1 MB
fetch(req) {
return new Response("ok");
},
});// ❌ Wide open CORS
Bun.serve({
fetch(req) {
return new Response("data", {
headers: {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Credentials": "true", // Dangerous combo!
},
});
},
});
// ✓ Explicit origin allowlist
const ALLOWED_ORIGINS = ["https://app.example.com"];
Bun.serve({
fetch(req) {
const origin = req.headers.get("Origin");
const corsHeaders: Record<string, string> = {};
if (origin && ALLOWED_ORIGINS.includes(origin)) {
corsHeaders["Access-Control-Allow-Origin"] = origin;
corsHeaders["Access-Control-Allow-Credentials"] = "true";
}
return new Response("data", { headers: corsHeaders });
},
});// ❌ 完全开放的CORS
Bun.serve({
fetch(req) {
return new Response("data", {
headers: {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Credentials": "true", // 危险组合!
},
});
},
});
// ✓ 明确的源白名单
const ALLOWED_ORIGINS = ["https://app.example.com"];
Bun.serve({
fetch(req) {
const origin = req.headers.get("Origin");
const corsHeaders: Record<string, string> = {};
if (origin && ALLOWED_ORIGINS.includes(origin)) {
corsHeaders["Access-Control-Allow-Origin"] = origin;
corsHeaders["Access-Control-Allow-Credentials"] = "true";
}
return new Response("data", { headers: corsHeaders });
},
});// ❌ Exposed to network (sometimes unintentional)
Bun.serve({
hostname: "0.0.0.0", // Accessible from any network interface
port: 3000,
fetch(req) { /* ... */ },
});
// ✓ Localhost only for development
Bun.serve({
hostname: "127.0.0.1", // Only local access
port: 3000,
fetch(req) { /* ... */ },
});// ❌ 暴露到网络(有时是无意的)
Bun.serve({
hostname: "0.0.0.0", // 可从任何网络接口访问
port: 3000,
fetch(req) { /* ... */ },
});
// ✓ 开发环境仅本地访问
Bun.serve({
hostname: "127.0.0.1", // 仅本地可访问
port: 3000,
fetch(req) { /* ... */ },
});// ❌ CRITICAL: User input in command array (can still be dangerous)
const filename = userInput; // Could be "--version" or other flags
Bun.spawn(["convert", filename, "output.png"]);
// ❌ CRITICAL: Shell execution with user input
Bun.spawn(["sh", "-c", `convert ${userInput} output.png`]);
// ✓ Validate input first
const filename = userInput;
if (!filename.match(/^[a-zA-Z0-9_-]+\.(jpg|png|gif)$/)) {
throw new Error("Invalid filename");
}
Bun.spawn(["convert", filename, "output.png"]);
// ✓ Use -- to prevent flag injection
Bun.spawn(["convert", "--", filename, "output.png"]);// ❌ 严重:命令数组中包含用户输入(仍可能危险)
const filename = userInput; // 可能是"--version"或其他标志
Bun.spawn(["convert", filename, "output.png"]);
// ❌ 严重:带用户输入的Shell执行
Bun.spawn(["sh", "-c", `convert ${userInput} output.png`]);
// ✓ 先验证输入
const filename = userInput;
if (!filename.match(/^[a-zA-Z0-9_-]+\.(jpg|png|gif)$/)) {
throw new Error("无效文件名");
}
Bun.spawn(["convert", filename, "output.png"]);
// ✓ 使用--防止标志注入
Bun.spawn(["convert", "--", filename, "output.png"]);// ❌ HIGH: Path traversal
const userFile = req.query.file; // "../../etc/passwd"
const content = await Bun.file(`./uploads/${userFile}`).text();
// ❌ HIGH: Writing to arbitrary paths
await Bun.write(`./data/${userFile}`, content);
// ✓ Sanitize paths
import { join, basename, resolve } from "path";
const UPLOADS_DIR = resolve("./uploads");
function getSafePath(userInput: string): string {
const safeName = basename(userInput);
const fullPath = resolve(join(UPLOADS_DIR, safeName));
if (!fullPath.startsWith(UPLOADS_DIR)) {
throw new Error("Invalid path");
}
return fullPath;
}
const content = await Bun.file(getSafePath(userFile)).text();// ❌ 高危:路径遍历
const userFile = req.query.file; // "../../etc/passwd"
const content = await Bun.file(`./uploads/${userFile}`).text();
// ❌ 高危:写入任意路径
await Bun.write(`./data/${userFile}`, content);
// ✓ 清理路径
import { join, basename, resolve } from "path";
const UPLOADS_DIR = resolve("./uploads");
function getSafePath(userInput: string): string {
const safeName = basename(userInput);
const fullPath = resolve(join(UPLOADS_DIR, safeName));
if (!fullPath.startsWith(UPLOADS_DIR)) {
throw new Error("无效路径");
}
return fullPath;
}
const content = await Bun.file(getSafePath(userFile)).text();// ✓ Bun.password.hash is secure by default (uses argon2)
const hash = await Bun.password.hash(password);
// ✓ Verify passwords
const isValid = await Bun.password.verify(password, hash);
// ⚠️ But check: is it actually being used?
// Common vibecoding mistake: storing plaintext anyway
// ❌ Storing plaintext
db.run("INSERT INTO users (password) VALUES (?)", [password]);
// ✓ Storing hash
const hash = await Bun.password.hash(password);
db.run("INSERT INTO users (password_hash) VALUES (?)", [hash]);// ✓ Bun.password.hash默认是安全的(使用argon2)
const hash = await Bun.password.hash(password);
// ✓ 验证密码
const isValid = await Bun.password.verify(password, hash);
// ⚠️ 但需检查:是否真的正确使用?
// 常见的编码错误:仍然存储明文
// ❌ 存储明文
db.run("INSERT INTO users (password) VALUES (?)", [password]);
// ✓ 存储哈希值
const hash = await Bun.password.hash(password);
db.run("INSERT INTO users (password_hash) VALUES (?)", [hash]);// Bun.env is the same as process.env
// ❌ Secrets in client-facing code
// If using Bun with a bundler, check what gets bundled
// ✓ Server-only access
const apiKey = Bun.env.API_KEY;
if (!apiKey) {
throw new Error("API_KEY not configured");
}
// Check bunfig.toml for any exposed variables// Bun.env与process.env相同
// ❌ 客户端代码中包含密钥
// 如果使用Bun进行打包,检查哪些内容会被打包
// ✓ 仅服务器端访问
const apiKey = Bun.env.API_KEY;
if (!apiKey) {
throw new Error("未配置API_KEY");
}
// 检查bunfig.toml中的暴露变量undefinedundefinedundefinedundefinedBun.serve({
fetch(req, server) {
if (req.headers.get("upgrade") === "websocket") {
// ❌ No auth check before upgrade
server.upgrade(req);
return;
}
},
websocket: {
message(ws, message) {
// ❌ Broadcasting without auth
ws.publish("chat", message);
},
},
});
// ✓ Authenticate before upgrade
Bun.serve({
fetch(req, server) {
if (req.headers.get("upgrade") === "websocket") {
const token = req.headers.get("Authorization");
const user = await verifyToken(token);
if (!user) {
return new Response("Unauthorized", { status: 401 });
}
server.upgrade(req, { data: { user } });
return;
}
},
websocket: {
message(ws, message) {
// Access authenticated user
const user = ws.data.user;
// Now safe to process message
},
},
});Bun.serve({
fetch(req, server) {
if (req.headers.get("upgrade") === "websocket") {
// ❌ 升级前无身份验证检查
server.upgrade(req);
return;
}
},
websocket: {
message(ws, message) {
// ❌ 无身份验证的广播
ws.publish("chat", message);
},
},
});
// ✓ 升级前进行身份验证
Bun.serve({
fetch(req, server) {
if (req.headers.get("upgrade") === "websocket") {
const token = req.headers.get("Authorization");
const user = await verifyToken(token);
if (!user) {
return new Response("未授权", { status: 401 });
}
server.upgrade(req, { data: { user } });
return;
}
},
websocket: {
message(ws, message) {
// 访问已认证用户
const user = ws.data.user;
// 现在可以安全处理消息
},
},
});| Issue | Pattern to Find | Severity |
|---|---|---|
| Shell injection (function call) | | CRITICAL |
| SQL injection (function call) | | CRITICAL |
| SQL string interpolation | | CRITICAL |
| Argument injection | User input starting with | HIGH |
| Path traversal | | HIGH |
| Command injection | | HIGH |
| Open CORS | | MEDIUM |
| Network exposure | | MEDIUM |
| Missing WebSocket auth | | HIGH |
| 问题 | 查找模式 | 严重程度 |
|---|---|---|
| Shell注入(函数调用) | | 严重 |
| SQL注入(函数调用) | | 严重 |
| SQL字符串插值 | SQL中的 | 严重 |
| 参数注入 | 以 | 高危 |
| 路径遍历 | | 高危 |
| 命令注入 | 带用户输入的 | 高危 |
| 开放CORS | | 中危 |
| 网络暴露 | | 中危 |
| 缺少WebSocket认证 | 无认证检查的 | 高危 |
undefinedundefined
</commands>
<checklist>
</commands>
<checklist>$sql--$sql