Next.js Server Actions একটি use-server ফাইলের প্রতিটি এক্সপোর্ট করা ফাংশনকে স্বয়ংক্রিয়ভাবে একটি পাবলিক HTTP এন্ডপয়েন্ট হিসেবে প্রকাশ করে, যা একটি অনন্য আইডেন্টিফায়ার (unique identifier) প্রদান করে যা ক্লায়েন্ট ফাংশনটি কল করার জন্য ব্যবহার করে। বাটন, লিঙ্ক বা অন্য কোনো UI এলিমেন্ট রেন্ডার হোক বা না হোক, এই এন্ডপয়েন্টটি বিদ্যমান থাকে, যার অর্থ হলো যে কেউ যদি এই আইডেন্টিফায়ারটি খুঁজে পায়, তবে সে সরাসরি ফাংশনটি কল করতে পারে।

ফ্রেমওয়ার্কটির ডিজাইন Server Actions-কে সাধারণ হেল্পার ফাংশনের মতো বিবেচনা করে, কিন্তু রানটাইমে এগুলো ব্যবহারযোগ্য URL-এ পরিণত হয়। যেসব ডেভেলপার মনে করেন যে UI-ই একমাত্র গেটকিপার (gatekeeper), তাদের জন্য এটি একটি অদৃশ্য অ্যাটাক সারফেস (attack surface) তৈরি করে যা একটি মাত্র সুনিপুণ রিকোয়েস্টের মাধ্যমে কাজে লাগানো সম্ভব।

কেন Server Actions নিরাপদ মনে হয়েছিল

ডেভেলপাররা যাতে তাদের কম্পোনেন্টের পাশেই সার্ভার-সাইড কোড লিখতে পারেন এবং আলাদা API রাউটের ঝামেলা এড়াতে পারেন, সেজন্য Server Actions প্রবর্তন করা হয়েছিল। একটি সাধারণ ব্যবহার দেখতে এমন হতে পারে:

<form action={deleteInvoice}>
  <button type="submit">Delete</button>
</form>

যেহেতু ফর্মটিই deleteInvoice ট্রিগার করার একমাত্র দৃশ্যমান উপায়, তাই অনেক ডেভেলপার অননুমোদিত ব্যবহারকারীদের জন্য বাটনটি লুকিয়ে রাখেন, মনে করেন যে TypeScript সিগনেচার ভুল ডেটা আটকাবে, এবং এই বিশ্বাসের ওপর নির্ভর করেন যে ফাংশনটি একটি সার্ভার-অনলি মডিউলে রয়েছে। এই ধারণাগুলোর কোনোটিই প্রকৃত সুরক্ষা প্রদান করে না।

লুকানো এক্সপোজার

যখন একটি ফাইলে “use server” থাকে, Next.js প্রতিটি এক্সপোর্ট করা ফাংশনকে একটি এন্ডপয়েন্টে কম্পাইল করে, যেমন:

POST /_next/data/<build-id>/<page>.json?__rsc=<action-id>

<action-id> হলো একটি স্ট্যাবল হ্যাশ (stable hash) যা ক্লায়েন্ট বান্ডলে এমবেড করা থাকে। একজন আক্রমণকারী এটি পেতে পারেন:

  • পেজের নেটওয়ার্ক ট্রাফিক পরিদর্শন করে।
  • বান্ডেল করা JavaScript পড়ে (ID-টি একটি সাধারণ স্ট্রিং)।
  • প্রজেক্টটি যদি অনুমানযোগ্য প্যাটার্ন অনুসরণ করে, তবে নামকরণের রীতির (naming conventions) ওপর ভিত্তি করে অনুমান করে।

একবার ID জানা হয়ে গেলে, যেকোনো টুল—cURL, Postman, বা কোনো ম্যালিশিয়াস স্ক্রিপ্ট—থেকে রিকোয়েস্ট পাঠানো সম্ভব, যা যেকোনো UI-লেভেল চেক বাইপাস করে ফেলে।

তিনটি সুনির্দিষ্ট ঝুঁকি

ঝুঁকি কেন এটি গুরুত্বপূর্ণ
UI চেক অকার্যকর বাটন বা লিঙ্ক লুকিয়ে রাখলে অন্তর্নিহিত এন্ডপয়েন্টটি মুছে যায় না। এন্ডপয়েন্টটি ব্যবহারযোগ্য থাকে, ঠিক যেমন একটি লুকানো অ্যাডমিন পেজ যা সার্ভারে এখনও বিদ্যমান।
TypeScript রানটাইমে কোনো সুরক্ষা দেয় না কোড চলার সময় টাইপগুলো মুছে ফেলা হয়। deleteInvoice(id: number) হিসেবে ঘোষিত একটি ফাংশন একটি বিশাল স্ট্রিং, একটি অ্যারে, এমনকি ম্যালিশিয়াস JSON গ্রহণ করতে পারে, যা লজিক এরর বা ইনজেকশন অ্যাটাকের দিকে পরিচালিত করতে পারে।
ডিফল্ট অথেন্টিকেশন বা অথরাইজেশন নেই Server Actions দেখতে লোকাল হেল্পারের মতো, তাই ডেভেলপাররা প্রায়ই সেশন চেক, CSRF সুরক্ষা বা রো-লেভেল পারমিশন চেক যোগ করতে ভুলে যান যা প্রথাগত API রাউটে স্ট্যান্ডার্ড।

একটি Server Action কীভাবে সুরক্ষিত করবেন

  1. কলারকে (caller) অথেন্টিকেট করুন – কোনো বিজনেস লজিক চলার আগে একটি বৈধ সেশন বা টোকেন আছে কিনা তা যাচাই করুন।
  2. পেলোড ভ্যালিডেট করুন – রানটাইমে ডেটা টাইপ এবং ভ্যালু কনস্ট্রেইন্ট প্রয়োগ করতে একটি স্কিমা লাইব্রেরি (যেমন, Zod, Yup) ব্যবহার করুন।
  3. অপারেশনটি অথরাইজ করুন – “ব্যবহারকারী লগ-ইন করা আছে কি না?” এর বাইরেও, ব্যবহারকারী যে নির্দিষ্ট রেকর্ডটি পরিবর্তন বা মুছে ফেলার চেষ্টা করছেন সেটি তার মালিকানা আছে কিনা তা নিশ্চিত করুন।

একটি ন্যূনতম উদাহরণ:

'use server';
import { getSession } from '@/auth';
import { z } from 'zod';
import { db } from '@/db';

const DeleteInvoiceSchema = z.object({
  id: z.number().int().positive(),
});

export async function deleteInvoice(formData: FormData) {
  const session = await getSession();
  if (!session) throw new Error('Unauthenticated');

  const parsed = DeleteInvoiceSchema.safeParse({
    id: Number(formData.get('id')),
  });
  if (!parsed.success) throw new Error('Invalid input');

  const invoice = await db.invoice.findUnique({ where: { id: parsed.data.id } });
  if (!invoice || invoice.ownerId !== session.userId) {
    throw new Error('Unauthorized');
  }

  await db.invoice.delete({ where: { id: invoice.id } });
}

কোডটি স্পষ্টভাবে অথেন্টিকেশন চেক করে, ইনকামিং id ভ্যালিডেট করে এবং ডিলিট করার আগে লগ-ইন করা ব্যবহারকারীর ইনভয়েসটির মালিকানা আছে কিনা তা নিশ্চিত করে।

অন্যান্য সূক্ষ্ম Next.js পিটফল (pitfalls)

সমস্যা লক্ষণ সমাধান
NEXT_PUBLIC_ এনভায়রনমেন্ট ভেরিয়েবল NEXT_PUBLIC_ দিয়ে শুরু হওয়া যেকোনো কিছু ক্লায়েন্টে বান্ডেল করা হয়, যা সিক্রেট বা গোপন তথ্য প্রকাশ করে দেয়। সিক্রেটগুলো সাধারণ এনভায়রনমেন্ট ভেরিয়েবলে রাখুন, কখনোই সেগুলোর আগে NEXT_PUBLIC_ ব্যবহার করবেন না।
ওপেন রিডাইরেক্ট (Open redirects) একটি redirect কুয়েরি প্যারামিটার গ্রহণ করা এবং সেটিকে সরাসরি যুক্ত (concatenate) করা ব্যবহারকারীদের //evil.com-এ পাঠিয়ে দিতে পারে। একটি হোয়াইটলিস্টের বিপরীতে টার্গেটটি যাচাই করুন অথবা সেম-অরিজিন (same-origin) চেক প্রয়োগ করুন।
সার্ভার-সাইড রিকোয়েস্ট ফরজারী (SSRF) ব্যবহারকারীর দেওয়া একটি URL ফেচ করলে আক্রমণকারীরা অভ্যন্তরীণ পরিষেবা বা ক্লাউড মেটাডেটা এন্ডপয়েন্টে পৌঁছাতে পারে। হোস্টনেমগুলোর জন্য অ্যালাউ-লিস্ট (allow-list) ব্যবহার করুন, প্রাইভেট IP রেঞ্জ ব্লক করুন এবং টাইমআউট সেট করুন।
dangerouslySetInnerHTML স্যানিটাইজেশন ছাড়া ব্যবহারকারীর দেওয়া HTML রেন্ডার করলে XSS ঝুঁকি তৈরি হয়। DOMPurify-এর মতো লাইব্রেরি ব্যবহার করুন অথবা সরাসরি HTML ব্যবহার করা এড়িয়ে চলুন।

স্বয়ংক্রিয়ভাবে এই প্যাটার্নগুলো স্ক্যান করা

স্ট্যাটিক-অ্যানালাইসিস টুলগুলো উপরে তালিকাভুক্ত ঝুঁকিপূর্ণ কনস্ট্রাক্টগুলোকে চিহ্নিত করতে পারে। একটি হালকা ওজনের বিকল্প হলো Semgrep, যা দ্রুত চলে এবং CI পাইপলাইনে ইন্টিগ্রেট করা যেতে পারে:

npx --yes semgrep --config https://raw.githubusercontent.com/catidegla/stacksec/main/rules .

এই রুল সেটে এক্সপোর্ট করা সার্ভার ফাংশন, NEXT_PUBLIC_-এর অপব্যবহার, ওপেন রিডাইরেক্ট, SSRF প্যাটার্ন এবং অনিরাপদ HTML ইনসারশনের জন্য চেক অন্তর্ভুক্ত রয়েছে।

পরবর্তীতে যা খেয়াল রাখতে হবে

  • Framework updates – Next.js-এর রিলিজগুলোর দিকে নজর রাখুন; টিম বিল্ট-ইন authentication hooks চালু করতে পারে অথবা জেনারেট করা endpoints গুলোকে sandbox করতে পারে।
  • Community tooling – এখানে বর্ণিত সুরক্ষা ব্যবস্থাগুলোকে স্বয়ংক্রিয় করতে নতুন ESLint plugins এবং Next.js-এর জন্য নির্দিষ্ট Semgrep rules তৈরি হচ্ছে।
  • Real-world incidents – যেহেতু আরও বেশি প্রজেক্ট Server Actions গ্রহণ করছে, তাই প্রকাশ করা exploits গুলোর দিকে নজর রাখুন যা বাস্তবে ঝুঁকির বিষয়টি তুলে ধরে। কোনো breach ঘটার আগেই দ্রুত শনাক্তকরণ অভ্যন্তরীণ security reviews এ সাহায্য করতে পারে।

Takeaway: একটি Next.js Server Action কোনো private helper নয়; আপনি এটি export করার সাথে সাথেই এটি একটি public HTTP endpoint হয়ে যায়। এটিকে অন্য যেকোনো API route-এর মতো বিবেচনা করুন—authenticate, validate, এবং authorize করুন—অন্যথায়, UI-এর পাশে server code লেখার সুবিধাটি দ্রুত একটি security liability হয়ে দাঁড়াতে পারে।