From 5e0c77766b18e0be1126e603954d938408e2050c Mon Sep 17 00:00:00 2001 From: nexryai <61890205+nexryai@users.noreply.github.com> Date: Fri, 9 Jan 2026 12:53:17 +0000 Subject: [PATCH] Improve security --- src/index.ts | 84 +++++++++++++++++++++++++++++++++++++++++++++++-- test/s3.test.ts | 3 +- 2 files changed, 83 insertions(+), 4 deletions(-) diff --git a/src/index.ts b/src/index.ts index d40e1fc..8cc0961 100644 --- a/src/index.ts +++ b/src/index.ts @@ -6,9 +6,8 @@ export default { async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise { try { - const isValid = await verifySignature(request, env); - if (!isValid) { - return new Response("Invalid Signature", { status: 403 }); + if (request.method === "OPTIONS") { + return new Response(null, { status: 204 }); } const url = new URL(request.url); @@ -19,6 +18,19 @@ export default { const bucket = pathParts[0] || ""; const objectKey = pathParts.slice(1).join("/"); + if (!isValidPath(bucket, objectKey)) { + return new Response("Invalid path", { status: 400 }); + } + + if (!isAllowedBucket(bucket, env)) { + return new Response("Access denied to this bucket", { status: 403 }); + } + + const isValid = await verifySignature(request, env); + if (!isValid) { + return new Response("Invalid Signature", { status: 403 }); + } + const accessToken = await getAccessToken(env); if (method === "PUT" || method === "POST") { @@ -125,6 +137,7 @@ interface Env { GOOGLE_REFRESH_TOKEN: string; AUTH_KV: KVNamespace; FOLDER_CACHE: KVNamespace; + ALLOWED_BUCKETS?: string; } interface GoogleDriveFile { @@ -139,6 +152,71 @@ interface GoogleDriveSearchResponse { files?: GoogleDriveFile[]; } +// ======================================== +// Security Functions +// ======================================== + +function isValidPath(bucket: string, objectKey: string): boolean { + // バケット名の検証 + if (!bucket || bucket.includes("..") || bucket.includes("/") || bucket.includes("\\")) { + return false; + } + + // オブジェクトキーの検証 + if (objectKey) { + // ".." を含むパスを拒否 + if (objectKey.includes("..")) { + return false; + } + + // バックスラッシュを含むパスを拒否 (Windowsスタイルのパス) + if (objectKey.includes("\\")) { + return false; + } + + // 絶対パスを拒否 + if (objectKey.startsWith("/")) { + return false; + } + + // パスの各コンポーネントを検証 + const parts = objectKey.split("/"); + for (const part of parts) { + // 空のコンポーネントや "." を拒否 + if (!part || part === "." || part === "..") { + return false; + } + + // NULLバイトを拒否 + if (part.includes("\0")) { + return false; + } + } + } + + return true; +} + +function isAllowedBucket(bucket: string, env: Env): boolean { + console.log(bucket); + // 許可リストが設定されていない場合はすべて拒否 + if (!env.ALLOWED_BUCKETS) { + return false; + } + + const allowedBuckets = env.ALLOWED_BUCKETS.split(",") + .map((b) => b.trim()) + .filter((b) => b); + + // 空の許可リストの場合もすべて拒否 + if (allowedBuckets.length === 0) { + return false; + } + + // バケット名が許可リストに含まれているかチェック + return allowedBuckets.includes(bucket); +} + // ======================================== // Google Drive API Functions // ======================================== diff --git a/test/s3.test.ts b/test/s3.test.ts index 6a9599e..c7dd29f 100644 --- a/test/s3.test.ts +++ b/test/s3.test.ts @@ -14,6 +14,7 @@ const ENV = { GOOGLE_REFRESH_TOKEN: "test-refresh-token", AUTH_KV: createMockKV(), FOLDER_CACHE: createMockKV(), + ALLOWED_BUCKETS: "test-bucket,empty-bucket,my-bucket", }; const CTX = { @@ -222,7 +223,7 @@ describe("S3 API Server with Google Drive Backend", () => { const command = new GetObjectCommand({ Bucket: "my-bucket", - Key: "test.png", + Key: "my-bucket/test.png", }); const url = await getSignedUrl(s3, command, { expiresIn: 3600 });