ساعت ۱۶:۳۰ است و Pull Request مربوط به «تسویه کیف پول» باید امشب منتشر شود. اسکنر ۱۸۷ هشدار نشان می‌دهد؛ یکی مسیر ورودی کاربر تا کوئری SQL را رسم کرده، چند مورد فقط درباره نام‌گذاری‌اند و بقیه از قبل در شاخه اصلی وجود داشته‌اند. آیا انتشار باید متوقف شود؟ پاسخ حرفه‌ای نه «همه را ببندید» است و نه «ابزار همیشه خطا می‌کند». باید بدانیم هر هشدار چه چیزی را اثبات می‌کند، چه چیزی را اثبات نمی‌کند و تصمیم بعدی چیست.

تحلیل استاتیک کد (Static Code Analysis) کد یا نمایش میانی آن را بدون اجرای برنامه بررسی می‌کند. برای یک تستر یا کارشناس QA، ارزش اصلی این فناوری در شمردن هشدارها نیست؛ در تبدیل سیگنال فنی به ریسک محصول، سناریوی تست، گزارش قابل‌اقدام و یک Quality Gate منصفانه است. این راهنما همین مسیر عملی را، از Rule تا Triage و CI/CD، با مثال توضیح می‌دهد.

خلاصه اجرایی:

  • هشدار ابزار مساوی باگ قطعی یا آسیب‌پذیری قابل‌بهره‌برداری نیست؛ نقطه شروع بررسی است.
  • Linter، کامپایلر/Type Checker، تحلیل کیفیت و SAST هم‌پوشانی دارند، اما هدف و عمقشان یکسان نیست.
  • در کد قدیمی، Baseline بسازید و Gate را ابتدا روی کد جدید یا تغییرکرده بگذارید.
  • برای هر Suppression دلیل، مالک، تاریخ انقضا و دامنه ثبت کنید؛ خاموش‌کردن بی‌سروصدای Rule بدهی پنهان می‌سازد.
  • یافته معتبر باید به اصلاح کد و تست رگرسیون منتهی شود؛ تحلیل استاتیک جای تست پویا را نمی‌گیرد.

تحلیل استاتیک کد چیست و با چه چیزهایی فرق دارد؟

تحلیل استاتیک، خانواده‌ای از روش‌هاست که متن کد، Bytecode، AST، نمودار جریان کنترل یا نمایش میانی دیگری را بدون اجرای خود برنامه تحلیل می‌کند. خروجی ممکن است یک خطای قطعی کامپایل، الگوی مشکوک، بوی بد کد، مسیر احتمالی ورود داده آلوده یا نقض سیاست سازمان باشد. بنابراین «بدون اجرا» ویژگی مشترک است، نه ضمانت اینکه همه ابزارها یک مسئله را می‌بینند.

اگر درباره بازبینی نیازمندی، طراحی و کد در معنای گسترده‌تر سؤال دارید، ابتدا راهنمای تست استاتیک و Static Testing را بخوانید. مقاله حاضر عمداً محدودتر است: خروجی خودکار تحلیل کد و تصمیمی که QA بر اساس آن می‌گیرد.

مرز Linter، SAST، SCA و Static Testing

روش ورودی و هدف غالب نمونه خروجی چیزی که نباید از آن انتظار داشت
Compiler / Type Checker قواعد زبان و سازگاری نوع‌ها نوع ناسازگار، Symbol ناشناخته پوشش کامل منطق کسب‌وکار یا امنیت
Linter قواعد نحوی، سبک و برخی خطاهای محتمل متغیر استفاده‌نشده، شرط مشکوک ردیابی عمیق همه مسیرهای داده
تحلیل کیفیت قابلیت نگهداری، پیچیدگی، تکرار و Reliability تابع پیچیده، کد تکراری، Bug pattern اثبات تجربه کاربری یا عملکرد زمان اجرا
SAST ضعف امنیتی در کد اختصاصی جریان ورودی کنترل‌نشده تا SQL/HTML/Command فهرست CVE کتابخانه‌ها یا مشاهده رفتار Production
SCA Dependency، مجوز و آسیب‌پذیری شناخته‌شده جزء ثالث نسخه کتابخانه مرتبط با CVE کشف تزریق در منطق اختصاصی برنامه
Secret Scan کلید، Token و الگوهای Secret در مخزن/تاریخچه API key احتمالی تأیید خودکار اینکه Secret هنوز معتبر است
IaC Scan Terraform، Kubernetes و پیکربندی زیرساخت Storage عمومی یا دسترسی بیش‌ازحد تحلیل رفتار کامل سرویس در Runtime
بازبینی انسانی قصد طراحی، زمینه و منطق دامنه دورزدن سقف برداشت یا مجوز ناقص تکرار سریع و یکنواخت روی میلیون‌ها خط

پس «SAST» زیرمجموعه امنیت‌محور تحلیل استاتیک است و «SCA» مترادف آن نیست. برای مقایسه مرحله‌ای این خانواده‌ها، راهنمای ابزارهای تست امنیت؛ SAST، DAST و SCA مکمل این مقاله است.

آیا تحلیل استاتیک همان تست جعبه سفید است؟

تحلیل‌گر به ساختار داخلی کد دسترسی دارد و از این نظر به رویکرد White-box نزدیک است؛ اما تست جعبه سفید دامنه وسیع‌تری دارد و می‌تواند شامل طراحی تست واحد، پوشش Statement/Branch و اجرای کد هم باشد. برای فهم معیارهای پوشش، به راهنمای تست جعبه سفید و Code Coverage مراجعه کنید. پوشش خط بالا نیز به‌تنهایی ثابت نمی‌کند Rule امنیتی یا مسیر خطرناک پوشش داده شده است.

تحلیل‌گر چگونه از کد به هشدار می‌رسد؟

۱. تطبیق متن و Token

ساده‌ترین Ruleها دنبال رشته، Token یا فراخوانی مشخص می‌گردند؛ مثلاً استفاده از eval یا الگوریتم رمزنگاری ممنوع. سریع و قابل‌فهم‌اند، اما ممکن است زمینه امن را هم علامت بزنند یا Wrapper اختصاصی تیم را نشناسند.

۲. درخت نحو انتزاعی یا AST

AST شکل نحوی کد را مستقل از فاصله و قالب‌بندی نشان می‌دهد. Rule می‌تواند بگوید «فراخوانی تابع X با آرگومان Y» و نسخه‌های نحوی مشابه را بیابد. بسیاری از Linterها و تحلیل‌گرهای الگو از این سطح استفاده می‌کنند.

۳. گراف جریان کنترل یا CFG

CFG شاخه‌ها، حلقه‌ها، Return و مسیرهای ممکن اجرای کد را مدل می‌کند. با آن می‌توان کد دست‌نیافتنی، شرط همواره درست، آزاد نشدن Resource در یک شاخه یا Null Dereference محتمل را بررسی کرد. «ممکن» بودن مسیر هنوز به معنای قابل‌وقوع بودن آن در محیط واقعی نیست.

۴. جریان داده و Taint Tracking

در تحلیل Taint سه واژه کلیدی داریم:

  • Source: جایی که داده غیرقابل‌اعتماد وارد می‌شود؛ مثل پارامتر HTTP، پیام صف یا فایل آپلودی.
  • Sanitizer / Validator: کنترلی که داده را برای کاربرد مشخص معتبر یا بی‌خطر می‌کند.
  • Sink: عملیات حساس؛ مثل اجرای کوئری، Command، Template HTML یا Redirect.

ابزار بررسی می‌کند آیا از Source تا Sink مسیری بدون کنترل قابل‌شناسایی وجود دارد. مستندات رسمی Semgrep درباره Taint Analysis و راهنمای Path Query در CodeQL این مدل Source-to-Sink را تشریح می‌کنند. تفاوت موتور، زبان، Framework model و تحلیل درون‌فایلی یا بین‌فایلی مستقیماً روی نتیجه اثر دارد.

۵. تحلیل بین‌رویه‌ای و حساس به زمینه

اگر داده از چند تابع، کلاس یا فایل عبور کند، تحلیل Interprocedural لازم است. موتورهای دقیق‌تر ممکن است Call Graph، نوع، Alias و زمینه فراخوانی را هم مدل کنند. این دقت رایگان نیست: زمان، حافظه، تنظیم Build و مدل‌سازی Framework بیشتر لازم می‌شود. QA باید بداند اسکن سریع PR و اسکن عمیق شبانه الزاماً خروجی یکسان ندارند.

آناتومی یک یافته قابل‌بررسی

قبل از بحث درباره Severity، هویت یافته را کامل کنید. حداقل داده‌های لازم عبارت‌اند از:

  • نام ابزار، نسخه Engine، نسخه Rule pack و شناسه Rule؛
  • Commit SHA، Branch، زمان اسکن و حالت اسکن کامل یا Diff؛
  • Repository، مسیر فایل، خط و در صورت امکان Component/مالک؛
  • پیام Rule، دسته کیفیت یا امنیت و نگاشت دقیق به CWE در صورت وجود؛
  • Source، Sink، مسیر داده و Sanitizerهایی که ابزار دیده یا ندیده است؛
  • Severity ابزار، Confidence، Reachability و وضعیت Triage؛
  • Baseline و اینکه یافته جدید، قدیمی، برگشتی یا Duplicate است؛
  • پیوند به کد، اجرای CI و شواهد اصلاح.

استاندارد SARIF ۲.۱.۰ قالب JSON مشترکی برای تبادل خروجی تحلیل‌گرها تعریف می‌کند. SARIF می‌تواند Rule، Location و مسیر جریان را میان Scanner، CI و مخزن منتقل کند؛ اما خودِ استاندارد درباره معتبر بودن یافته داوری نمی‌کند.

Severity با Risk یکی نیست

Severity پیش‌فرض ابزار معمولاً از ماهیت Rule می‌آید. ریسک محصول به زمینه وابسته است: آیا مسیر از اینترنت قابل‌دسترسی است؟ داده حساس چیست؟ کنترل جبرانی داریم؟ اثر مالی و دامنه کاربران چقدر است؟ برای مثال، یک Sink خطرناک در ابزار Migration داخلی و آفلاین ممکن است Exposure متفاوتی با همان Sink در API عمومی پرداخت داشته باشد. با این حال، «داخلی بودن» به‌تنهایی دلیل بستن یافته نیست.

CWE، CVE و Rule ID را قاطی نکنید

  • CWE نوع ضعف عمومی، مانند Neutralization نامناسب ورودی است.
  • CVE شناسه یک آسیب‌پذیری افشاشده در محصول/نسخه مشخص است.
  • Rule ID پیاده‌سازی یک تشخیص در ابزار مشخص است.

فهرست Common Weakness Enumeration زبان مشترکی برای نام‌گذاری ضعف‌هاست، نه رتبه قطعی ریسک هر یافته و نه اثبات Exploitability.

نقش QA در تحلیل استاتیک کد چیست؟

مالکیت ابزار ممکن است با AppSec یا تیم Platform باشد و اصلاح کد با توسعه‌دهنده؛ نقش QA اتصال یافته به رفتار و ریسک قابل‌آزمون است. QA لازم نیست برای همه زبان‌ها متخصص Compiler باشد، اما باید بتواند سؤال درست بپرسد و شواهد ناقص را تشخیص دهد.

قبل از اسکن

  • دارایی‌های حیاتی، داده‌های حساس و Trust Boundaryها را از نیازمندی و Threat Model استخراج کند.
  • با تیم مشخص کند کدام Repository، زبان، Generated code، Test code و IaC داخل Scope است.
  • Acceptance Criteria امنیتی/کیفی را به Rule یا تست قابل‌بررسی متصل کند.
  • نمونه‌های True Positive و False Positive گذشته را برای کالیبراسیون Rule نگه دارد.

هنگام Triage

  • مسیر Source-to-Sink را با جریان واقعی محصول مقایسه کند.
  • قابل‌دسترسی بودن Feature، نقش کاربر، Flag و Configuration را بررسی کند.
  • برای ضعف‌های مهم، تست پویا یا واحد منفی طراحی کند.
  • بین Duplicate، False Positive، Accepted Risk و Out of Scope فرق بگذارد.

پس از اصلاح

  • تأیید کند هشدار در Commit اصلاحی واقعاً حذف شده، نه اینکه Rule یا فایل از Scope خارج شده باشد.
  • تست رگرسیون را اجرا و شواهد قبل/بعد را ثبت کند.
  • از رخداد واقعی، Rule سفارشی یا تست تازه بسازد تا منفی کاذب تکرار نشود.

این کار بخشی از برنامه بزرگ‌تر تست امنیت نرم‌افزار است؛ نه جایگزین Threat Modeling، تست API، DAST، Pen Test یا بازبینی انسانی منطق کسب‌وکار.

راه‌اندازی درست: Scope، Rule و Baseline

گام ۱: مسئله را قبل از ابزار تعریف کنید

به‌جای «SonarQube می‌خواهیم»، بنویسید: «می‌خواهیم ورود SQL Injection جدید به سرویس پرداخت را پیش از Merge متوقف کنیم و زمان Triage زیر یک روز باشد.» سپس زبان‌ها، Framework، اندازه مخزن، محرمانگی کد، Runner، زمان مجاز CI و بودجه را ثبت کنید. این صورت‌مسئله انتخاب ابزار و Gate را قابل‌اندازه‌گیری می‌کند.

گام ۲: Scope را قابل‌ممیزی کنید

مسیرهای Vendor، Fixture، Minified، Generated و Migration را کورکورانه حذف نکنید. برای هر Exclusion یک دلیل و مالک داشته باشید. خروجی اسکن باید نشان دهد چند Repository/Component و چه Commitی بررسی شده، آیا Build موفق بوده و چه فایل‌هایی تحلیل نشده‌اند. اسکن سبزی که نیمی از کد را نخوانده، سیگنال اعتمادپذیری نیست.

گام ۳: Rule profile کوچک اما معتبر بسازید

از Rule pack نگهداری‌شده و متناسب با زبان/Framework شروع کنید. Ruleهای بسیار نویزی را ابتدا در حالت Report-only ارزیابی کنید. هر Rule سفارشی باید Test fixture مثبت و منفی، پیام اصلاح، مالک و نگاشت به سیاست داخلی داشته باشد. در ESLint، Rule واحد بنیادی پیکربندی است و شدت/گزینه‌ها قابل‌تنظیم‌اند؛ مستندات رسمی Configure Rules همچنین بر مستندسازی دلیل Inline disable تأکید می‌کند.

گام ۴: برای کد قدیمی Baseline بسازید

اگر نخستین اسکن ۵۰۰۰ هشدار قدیمی می‌دهد، Gate صفرهشدار معمولاً تیم را به خاموش‌کردن ابزار سوق می‌دهد. Snapshot معتبر از شاخه اصلی و Commit مشخص بسازید؛ هشدارهای موجود را بدهی قابل‌برنامه‌ریزی بدانید و از ورود یافته جدید جلوگیری کنید. Baseline باید با Rule/Engine جدید بازبینی شود، چون تغییر Rule ممکن است شناسه یا تعداد یافته‌ها را عوض کند.

گام ۵: سه حلقه بازخورد تعریف کنید

  1. IDE / Local: Ruleهای سریع برای بازخورد چندثانیه‌ای.
  2. Pull Request: تحلیل Diff، Gate و Annotation برای کد جدید.
  3. Main / Scheduled: تحلیل کامل و عمیق‌تر برای مسیرهای بین‌فایلی، Trend و Drift.

این لایه‌بندی سرعت توسعه را حفظ می‌کند و هم‌زمان اسکن عمیق را حذف نمی‌کند. آن را با معماری Continuous Testing در CI/CD هماهنگ کنید.

گردش‌کار Triage هشدارهای تحلیل استاتیک

مرحله ۱: سلامت خود اسکن را تأیید کنید

اول Status ابزار، Log، Commit، Rule pack، تعداد فایل‌های تحلیل‌شده و خطاهای Parser/Build را ببینید. «صفر یافته» بعد از Timeout یا نخواندن زبان هدف یک Pass نیست. اسکن ناقص باید وضعیت جداگانه Scan Failed/Incomplete داشته باشد.

مرحله ۲: تازگی و هویت یافته را تعیین کنید

آیا این هشدار در Diff ایجاد شده، از Baseline آمده، بعد از اصلاح برگشته یا Duplicate همان مسیر است؟ Fingerprint ابزار کمک می‌کند، اما Rename، تغییر Rule یا جابه‌جایی خط ممکن است هویت را بشکند. Commit و مسیر داده را کنار شناسه ذخیره کنید.

مرحله ۳: Rule را بخوانید، نه فقط عنوان را

توضیح Rule، الگوی نامطمئن، پیش‌شرط‌ها، نمونه امن و محدودیت تحلیل را بخوانید. یک Rule «SQL Injection» ممکن است فقط String concatenation را ببیند؛ دیگری Taint path را دنبال کند. این دو Confidence یکسان ندارند.

مرحله ۴: Source، مسیر و Sink را بازسازی کنید

از ورودی شروع کنید و هر Transformation، Validation، Decode، Cast و Sanitizer را تا Sink دنبال کنید. بپرسید:

  • Source واقعاً تحت کنترل مهاجم است یا از داده قابل‌اعتماد می‌آید؟
  • این مسیر در Build و Configuration منتشرشده قابل‌دسترسی است؟
  • Validator برای همین Sink و Context مناسب است؟ HTML escaping از SQL Injection جلوگیری نمی‌کند.
  • Wrapper یا ORM واقعاً Parameter binding انجام می‌دهد یا فقط نام امنی دارد؟
  • آیا مسیر جایگزینی هست که ابزار ندیده باشد؟

مرحله ۵: اثر کسب‌وکار را بسنجید

دارایی، محرمانگی/یکپارچگی/دسترس‌پذیری، سطح دسترسی لازم، دامنه کاربر، قابلیت تکرار و کنترل‌های جبرانی را ثبت کنید. سپس Priority اصلاح را تعیین کنید. Severity ابزار ورودی تصمیم است، نه تصمیم نهایی.

مرحله ۶: با تست هدفمند فرضیه را بررسی کنید

در محیط مجاز، یک تست واحد، Integration یا درخواست منفی بسازید. هدف «Exploit نمایشی» بی‌ضابطه نیست؛ هدف تأیید کنترل و ساخت رگرسیون است. برای ورودی‌های دیتابیس، راهنمای تست پایگاه داده، SQL و Integrity به طراحی Oracleهای مکمل کمک می‌کند.

مرحله ۷: وضعیت دقیق انتخاب کنید

وضعیت معنا شاهد لازم اقدام
True Positive / To Fix الگوی گزارش‌شده در زمینه محصول معتبر است مسیر کد، اثر و در صورت نیاز تست مالک، SLA، اصلاح و رگرسیون
False Positive Rule با واقعیت کد تطبیق ندارد مثلاً Sanitizer معتبر و مدل‌نشده ثبت دلیل؛ بهبود Rule/model
Accepted Risk یافته معتبر است اما فعلاً پذیرفته می‌شود تصمیم Risk owner، کنترل جبرانی تاریخ انقضا و بازبینی
Duplicate همان علت و مسیر قبلاً ثبت شده پیوند به یافته مرجع یک مالک و یک اصلاح
Out of Scope دارایی طبق Scope مصوب خارج است سند Scope، نه حدس فردی بازبینی دوره‌ای Scope
Removed فایل/Rule/Scope تغییر کرده، نه الزاماً Fix Diff تنظیم یا حذف فایل با Fixed اشتباه نشود

مرحله ۸: اصلاح و بسته‌شدن را دو مرحله‌ای کنید

«کد تغییر کرد» مساوی «یافته بسته شد» نیست. اسکن مجدد باید Rule را روی Commit جدید اجرا کند؛ تست رگرسیون باید رفتار امن را بسنجد؛ QA باید بررسی کند حذف هشدار نتیجه خاموش‌شدن Rule، افزودن Exclusion یا شکستن Build نباشد.

مثال عملی: هشدار SQL Injection در تسویه فروشنده

فرض کنید API داخلی پنل مالی، تراکنش‌های یک فروشنده را بر اساس merchantId می‌خواند. نسخه آسیب‌پذیر:

app.get("/settlements", async (req, res) => {
  const merchantId = req.query.merchantId;
  const sql =
    "SELECT id, amount, status FROM settlements WHERE merchant_id = '" +
    merchantId +
    "'";
  const rows = await db.query(sql);
  res.json(rows);
});

تحلیل‌گر ممکن است req.query.merchantId را Source، ساخت رشته را Propagation و db.query(sql) را Sink بداند. اما QA باید بیش از دیدن این سه نقطه کار کند: آیا Route احراز هویت دارد؟ آیا مجوز دسترسی فروشنده بررسی می‌شود؟ Driver چه APIای دارد؟ آیا Wrapper داخلی Parameterization را پنهان کرده؟ پاسخ این سؤال‌ها هم اعتبار یافته و هم دامنه اثر را مشخص می‌کند.

اصلاح امن‌تر با Query پارامتری و کنترل مجوز

app.get("/settlements", requireFinanceRole, async (req, res) => {
  const merchantId = String(req.query.merchantId ?? "");

  if (!/^[0-9]{1,18}$/.test(merchantId)) {
    return res.status(400).json({ code: "INVALID_MERCHANT_ID" });
  }

  const rows = await db.query(
    "SELECT id, amount, status FROM settlements WHERE merchant_id = ?",
    [merchantId]
  );

  return res.json(rows);
});

اعتبارسنجی قالب ورودی مفید است، ولی کنترل اصلی در برابر تزریق، API پارامتری Driver است. requireFinanceRole نیز مسئله مجوز را جداگانه پوشش می‌دهد. نام Placeholder در Driverهای مختلف فرق دارد؛ تیم باید مستندات همان Driver را ملاک قرار دهد.

سناریوهای تستی که از یافته ساخته می‌شوند

  • شناسه معتبر متعلق به فروشنده مجاز، فقط رکوردهای همان دامنه را برگرداند.
  • ورودی شامل Quote، Comment و عملگر منطقی با پاسخ کنترل‌شده رد شود و Query اضافی اجرا نشود.
  • رشته Unicode/Persian، رقم فارسی، فاصله نامرئی و طول بیشتر از حد، رفتار تعریف‌شده داشته باشد.
  • کاربر بدون نقش مالی حتی با Merchant ID معتبر، پاسخ ۴۰۳ بگیرد.
  • خطای Driver، متن SQL، Credential یا Stack trace را در پاسخ افشا نکند.
  • پس از اصلاح، Rule قبلی روی Commit جدید هشدار ندهد و Log نشان دهد اسکن کامل بوده است.

نمونه گزارش یافته برای توسعه‌دهنده

عنوان: عبور merchantId کنترل‌نشده به SQL در API تسویه
Rule/CWE: شناسه Rule ابزار + CWE دقیق گزارش‌شده
Build: Repository، Branch و Commit SHA
مسیر: req.query.merchantId → الحاق رشته → db.query
اثر: خواندن یا تغییر غیرمجاز داده مالی، مشروط به Reachability مسیر
شواهد: لینک SARIF/CI، فایل و خطوط؛ نتیجه تست کنترل‌شده
انتظار: Parameter binding، اعتبارسنجی دامنه و تست رگرسیون
بسته‌شدن: اسکن مجدد موفق + تست منفی + نبود Exclusion جدید

این قالب را با اصول گزارش باگ حرفه‌ای ترکیب کنید تا یافته برای توسعه‌دهنده قابل‌بازتولید و برای ممیزی قابل‌ردگیری باشد.

مثبت کاذب و منفی کاذب را مهندسی کنید

چرا False Positive رخ می‌دهد؟

  • ابزار Sanitizer یا Wrapper اختصاصی را مدل نکرده است.
  • مسیر از نظر نحوی ممکن است اما با Feature flag یا مجوز واقعاً Reachable نیست.
  • Rule برای نسخه دیگری از Framework نوشته شده است.
  • داده برچسب‌خورده در Context مشخص قابل‌اعتماد است، ولی ابزار زمینه را نمی‌داند.
  • Generated/Test code بدون سیاست روشن داخل Scope آمده است.

چرا False Negative خطرناک‌تر و پنهان‌تر است؟

  • زبان، Template یا Framework کامل پشتیبانی نمی‌شود.
  • Build ناقص، فایل‌ها را از تحلیل خارج کرده است.
  • جریان داده از Reflection، Dynamic import، Serialization یا سرویس دیگر عبور می‌کند.
  • Source، Sink یا Sanitizer سفارشی مدل نشده است.
  • ضعف در منطق کسب‌وکار است و Rule نحوی مناسبی ندارد.
  • Rule pack قدیمی یا عمداً خاموش شده است.

Precision تقریبی را می‌توان از نمونه یافته‌های بررسی‌شده سنجید، اما Recall واقعی بدون مجموعه مرجع، Seeded defect یا دانستن باگ‌های ازدست‌رفته قابل‌محاسبه نیست. عبارت «ابزار ۹۹٪ باگ‌ها را می‌گیرد» بدون روش آزمایش و دامنه، معیار خرید یا اعتماد نیست.

سیاست Suppression سالم

Suppress کردن باید آخرین مرحله Triage باشد، نه میان‌بُر سبزکردن Pipeline. هر استثنا حداقل این فیلدها را داشته باشد: Rule ID، Scope کوچک، وضعیت دقیق، دلیل فنی، Risk owner، Ticket، ایجادکننده، تاریخ ثبت و تاریخ انقضا. Inline comment بدون توضیح یا Disable سراسری Rule باید توسط Policy رد شود.

دلیل استثنا را از هم تفکیک کنید: False Positive یعنی Rule اشتباه تطبیق داده؛ Accepted Risk یعنی مشکل واقعی است ولی تصمیم کسب‌وکار با کنترل جبرانی آن را موقتاً می‌پذیرد. این دو از نظر ممیزی و بهبود Rule یکسان نیستند.

طراحی Quality Gate منصفانه در CI/CD

Quality Gate قراردادی است که نتیجه تحلیل را به تصمیم Merge/Release تبدیل می‌کند. Gate خوب شفاف، سریع، قابل‌بازتولید، متناسب با ریسک و دارای مسیر استثنای ممیزی‌پذیر است. مستندات جاری SonarQube Quality Gates نیز شرایط Gate را از Quality Profile (مجموعه Ruleها) جدا می‌کند و در Pull Request بر شرایط کد جدید تمرکز دارد.

چه چیزی باید Merge را متوقف کند؟

یک Policy نمونه—که باید با ریسک سازمان کالیبره شود—می‌تواند چنین باشد:

  • اسکن ناقص، شکست Parser یا ناشناخته بودن Commit: Block؛
  • یافته جدید Critical/High با Confidence کافی و وضعیت Open: Block؛
  • Security Hotspot جدید بدون Review: Block یا Review اجباری؛
  • Suppression بدون دلیل/مالک/انقضا: Block؛
  • یافته قدیمی داخل Baseline: Report و ورود به برنامه کاهش بدهی؛
  • Rule تازه و کالیبره‌نشده: ابتدا Warn/Report-only، سپس Enforce؛
  • اختلاف کم‌ریسک سبک کد: Auto-fix یا Warn، نه لزوماً توقف انتشار.

Gate فقط شمارنده هشدار نیست

«تعداد هشدار > صفر» بدون تفکیک جدید/قدیمی، Severity، Confidence و وضعیت اسکن، تیم را به بازی‌دادن عددها تشویق می‌کند. Gate باید دست‌کم چهار لایه داشته باشد:

  1. Scan health: ابزار واقعاً موفق و Scope کامل باشد.
  2. New-risk policy: یافته‌های جدید پرریسک Triage نشده نباشند.
  3. Exception policy: استثنا معتبر و منقضی‌نشده باشد.
  4. Evidence: Result، Commit و Rule version آرشیو شود.

شبه‌پایپ‌لاین مستقل از Vendor

static-analysis:
  checkout: exact_commit
  steps:
    - verify_build_and_generated_sources
    - run_fast_lint_rules
    - run_sast_with_versioned_policy
    - export_results_as_sarif
    - verify_scan_health_and_scope
    - compare_findings_with_main_baseline
    - require_triage_for_new_high_risk_findings
    - validate_suppressions_have_owner_reason_expiry
  artifacts:
    - scan-log
    - policy-version
    - sarif
    - triage-summary
  decision:
    pass_when:
      - scan_is_complete
      - no_blocking_new_finding_is_open
      - all_exceptions_are_valid

نام Job و Syntax در GitHub Actions، GitLab CI، Jenkins یا Runner داخلی متفاوت است؛ قرارداد تصمیم باید ثابت بماند. هنگام تدوین این قرارداد، از روش PoC و معیارهای راهنمای استراتژی اتوماسیون تست استفاده کنید.

انتخاب ابزار: خانواده را مقایسه کنید، نه لوگو را

فهرست «بهترین ابزارها» سریع کهنه می‌شود. یک ماتریس PoC با کد واقعی خودتان قابل‌اتکاتر است:

معیار سؤال آزمون در PoC
زبان و Framework نسخه‌های واقعی پروژه و Template/ORM اختصاصی را می‌فهمد؟
عمق تحلیل Cross-file، Data flow، Taint و Build mode موردنیاز را دارد؟
کیفیت Rule روی ۲۰–۵۰ یافته نمونه، Precision و پیام اصلاح چگونه است؟
قابلیت سفارشی‌سازی Source/Sink/Sanitizer و Rule دامنه‌ای قابل‌مدل‌سازی است؟
سرعت و مقیاس PR scan و Full scan در بودجه زمانی Pipeline جا می‌شوند؟
تجربه Triage Fingerprint، مسیر داده، مالکیت، Comment، API و Audit log دارد؟
یکپارچگی SCM، IDE، Ticketing، SARIF و CI داخلی را پشتیبانی می‌کند؟
امنیت داده کد کجا پردازش/ذخیره می‌شود؟ Telemetry و Retention قابل‌کنترل است؟
عملیات Update آفلاین، Backup، HA، Runner و مصرف منابع چگونه است؟
هزینه کل مجوز، Runner، Triage، نگهداری Rule و آموزش چقدر است؟

خانواده‌های رایج

  • Linter/Type tools: مثل ESLint یا ابزارهای بومی زبان؛ سریع و نزدیک IDE.
  • Pattern/Taint scanners: مانند Semgrep؛ مناسب Ruleهای سفارشی و جریان داده با دامنه پشتیبانی مشخص.
  • Code query engines: مانند CodeQL؛ Query و Path analysis روی پایگاه کد.
  • Continuous quality platforms: مانند SonarQube؛ Profile، Trend و Gate یکپارچه.
  • Commercial AppSec suites: برای Policy، Portfolio، Compliance و پشتیبانی سازمانی؛ باید با PoC سنجیده شوند.
  • Language-specific analyzers: ابزارهای تخصصی Java، C/C++، Python، .NET یا Mobile که گاهی از ابزار عمومی دقیق‌ترند.

صفحه رسمی OWASP Source Code Analysis Tools برای شناخت گزینه‌ها و قوت/ضعف SAST نقطه شروع مناسبی است، اما هیچ فهرستی جای آزمون روی Repository و ریسک واقعی شما را نمی‌گیرد.

متریک‌هایی که رفتار درست ایجاد می‌کنند

متریک‌های عملیاتی

  • درصد Repository/Componentهای داخل Scope که اسکن سالم و به‌روز دارند؛
  • نرخ موفقیت اسکن و علت شکست/Timeout/Parser error؛
  • میانه زمان از ایجاد یافته جدید تا اولین Triage؛
  • میانه زمان تا اصلاح بر اساس سطح ریسک؛
  • سن یافته‌های Open و تعداد موارد خارج از SLA؛
  • نرخ بازگشت یافته پس از Fix.

متریک‌های کیفیت سیگنال

  • Precision نمونه‌برداری‌شده به تفکیک Rule، زبان و تیم؛
  • نسبت False Positive، Duplicate، Accepted Risk و Removed؛
  • Ruleهای پرنویز و Ruleهای مؤثر در کشف Defect واقعی؛
  • تعداد Suppressionهای فعال، منقضی و بدون مالک؛
  • رخدادهایی که تحلیل استاتیک ندیده و به Rule/Test تازه تبدیل شده‌اند.

متریک‌های نتیجه‌ای

  • کاهش تکرار یک CWE یا Root cause در کد جدید؛
  • درصد یافته‌های معتبر که تست رگرسیون دارند؛
  • کاهش زمان بازخورد پیش از Merge؛
  • کاهش Escaped defect مرتبط—با احتیاط در نسبت‌دادن علت.

تعداد خام هشدار به‌عنوان KPI فردی مضر است: توسعه‌دهنده را به خاموش‌کردن Rule و QA را به ثبت موارد کم‌ارزش سوق می‌دهد. Trend را همراه با Scope، نسخه Rule و تغییر اندازه کد تفسیر کنید.

ملاحظات تحلیل استاتیک برای تیم‌های ایرانی

محرمانگی کد و محدودیت سرویس خارجی

پیش از ارسال Source، SARIF یا Snippet به SaaS خارجی، طبقه‌بندی داده، قرارداد مشتری و سیاست سازمان را بررسی کنید. برای کد حساس، Self-hosted یا Runner داخل شبکه، Encryption، Retention محدود و Telemetry کنترل‌شده ممکن است الزام باشد. Secret واقعی را برای آزمایش Scanner در مخزن قرار ندهید؛ Fixture بی‌خطر و مشخص بسازید.

تحریم، پرداخت و پایداری دسترسی

فقط قیمت License را نبینید. امکان خرید/تمدید، دسترسی به Container و Rule update، محدودیت IP، Support، Export داده و مسیر مهاجرت را در Risk register بیاورید. Rule pack و Binary تأییدشده را در Artifact repository داخلی Cache کنید و Hash/امضای آن را کنترل کنید؛ این کار نباید مجوز نرم‌افزار را نقض کند.

Runner و به‌روزرسانی آفلاین

اگر شبکه Build محدود است، فرآیند واردکردن کنترل‌شده Engine/Rule، SBOM ابزار، Scan برای خود Artifact و Rollback نسخه را تعریف کنید. نسخه Rule در هر نتیجه ثبت شود تا جهش ناگهانی هشدارها قابل‌توضیح باشد.

ورودی فارسی، Unicode و منطق بومی

تحلیل‌گر ممکن است الگوی Injection را ببیند اما خطای تبدیل رقم فارسی/لاتین، نیم‌فاصله، Normalization نام، تقویم شمسی، مبلغ ریالی/تومانی یا راست‌به‌چپ را نفهمد. از یافته استاتیک، تست‌های دامنه‌ای برای این موارد بسازید. منطق سقف تراکنش، تسویه و نقش‌ها نیز غالباً به بازبینی انسانی و تست پویا نیاز دارد.

برنامه ۳۰روزه استقرار تحلیل استاتیک

روزهای ۱ تا ۵: صورت‌مسئله و نمونه مرجع

  • دو Repository نماینده و سه ریسک اولویت‌دار انتخاب کنید.
  • Scope، مالک، SLA، محدودیت کد و بودجه زمانی CI را بنویسید.
  • ۱۰ نمونه مثبت و ۱۰ نمونه امن از کد واقعی/Fixture گردآوری کنید.

روزهای ۶ تا ۱۲: PoC و کالیبراسیون

  • دو یا سه گزینه را با Commit یکسان و Rule profile ثبت‌شده اجرا کنید.
  • حداقل ۲۰–۵۰ یافته را دو نفره Triage و Precision را به تفکیک Rule بسنجید.
  • پوشش زبان، زمان، منابع، امنیت داده و تجربه توسعه‌دهنده را ثبت کنید.

روزهای ۱۳ تا ۲۰: Baseline و Report-only

  • اسکن کامل شاخه اصلی و Baseline نسخه‌دار ایجاد کنید.
  • Annotation در PR را بدون Block فعال کنید.
  • Taxonomy وضعیت، قالب Suppression و Dashboard حداقلی بسازید.

روزهای ۲۱ تا ۲۶: Gate محدود و قابل‌بازگشت

  • فقط اسکن ناقص و یافته جدید پرریسک Triage‌نشده را Block کنید.
  • مسیر Break-glass با تأیید Risk owner، دلیل و انقضا تعریف کنید.
  • زمان Pipeline و خطای Gate را روزانه پایش کنید.

روزهای ۲۷ تا ۳۰: بازنگری و Scale

  • Ruleهای نویزی را اصلاح یا موقتاً Report-only کنید.
  • یافته معتبر را به تست رگرسیون و درس آموخته وصل کنید.
  • تصمیم Scale، هزینه نگهداری و برنامه سه‌ماهه بدهی قدیمی را ثبت کنید.

چک‌لیست QA برای هر Pull Request

  • اسکن روی Commit دقیق PR انجام شده و Log سالم است.
  • زبان، Build و فایل‌های تغییرکرده واقعاً تحلیل شده‌اند.
  • نسخه Engine، Rule profile و Baseline ثبت شده است.
  • یافته جدید از بدهی قدیمی و Duplicate تفکیک شده است.
  • Rule، Source، Sink، Path و Context محصول بررسی شده‌اند.
  • Severity ابزار با Reachability و اثر کسب‌وکار تعدیل شده است.
  • برای یافته معتبر، مالک، SLA و تست رگرسیون وجود دارد.
  • Suppression دلیل، Scope کوچک، Risk owner و انقضا دارد.
  • بسته‌شدن با اسکن مجدد و رفتار پویا تأیید شده است.
  • نتیجه و شواهد برای Audit قابل‌بازیابی‌اند.

سوالات متداول تحلیل استاتیک کد

آیا تحلیل استاتیک کد همان SAST است؟

خیر. تحلیل استاتیک اصطلاح گسترده‌تری است و بررسی نوع، Lint، کیفیت و امنیت را دربرمی‌گیرد. SAST بخش امنیت‌محور آن است که برای یافتن ضعف‌های امنیتی در کد اختصاصی به کار می‌رود. SCA نیز Dependency و آسیب‌پذیری شناخته‌شده اجزای ثالث را بررسی می‌کند و مترادف SAST نیست.

آیا هر هشدار SAST یک آسیب‌پذیری واقعی است؟

خیر. هشدار تطبیق یک Rule با شواهد کد است. برای تصمیم، باید مسیر داده، Reachability، کنترل‌های موجود و اثر محصول بررسی شود. نتیجه می‌تواند True Positive، False Positive، Accepted Risk، Duplicate یا Out of Scope باشد و دلیل باید ثبت شود.

تستر بدون مهارت برنامه‌نویسی عمیق چگونه گزارش را بخواند؟

از چهار سؤال شروع کند: ورودی غیرقابل‌اعتماد کجاست؟ عملیات حساس کدام است؟ چه کنترل یا Sanitizerی میان آن‌هاست؟ اثر شکست برای کاربر و کسب‌وکار چیست؟ سپس با توسعه‌دهنده یا AppSec مسیر کد را مرور و یک تست منفی کوچک طراحی کند. خواندن تدریجی AST و Data flow مفید است، اما لازم نیست QA از روز اول Rule engine بنویسد.

بهترین Quality Gate برای پروژه قدیمی چیست؟

یک نسخه واحد برای همه وجود ندارد. معمولاً Baseline معتبر از کد موجود می‌سازیم، سلامت اسکن را اجباری می‌کنیم و ابتدا فقط یافته‌های جدید پرریسک یا Triage‌نشده را Block می‌کنیم. بدهی قدیمی با SLA و برنامه جدا کاهش می‌یابد. آستانه باید پس از PoC و با داده واقعی تیم تنظیم شود.

آیا تحلیل استاتیک جای تست پویا و Code Review را می‌گیرد؟

خیر. تحلیل استاتیک در تکرار سریع Ruleهای شناخته‌شده عالی است، اما رفتار Runtime، Configuration، تجربه کاربری، Race واقعی و بسیاری از خطاهای منطق کسب‌وکار را کامل نمی‌بیند. یافته مهم باید با بازبینی زمینه و در صورت مناسب با تست واحد، Integration، API یا امنیتی تکمیل شود.

جمع‌بندی: از هشدار به شواهد و تصمیم برسید

بلوغ تحلیل استاتیک با تعداد ابزار یا حجم Dashboard سنجیده نمی‌شود. تیم بالغ می‌داند چه کدی اسکن شده، هر Rule چه فرضی دارد، چگونه هشدار را Triage کند، چه زمانی Merge را متوقف سازد و چگونه از یک یافته معتبر، اصلاح و تست رگرسیون بسازد. Baseline برای کد قدیمی، Gate روی ریسک جدید، Suppression ممیزی‌پذیر و ترکیب تحلیل استاتیک با تست پویا چهار ستون این سیستم‌اند.

در سطح فرایندی، NIST SSDF 1.1 توصیه‌های امن‌سازی توسعه را به‌صورت نتیجه‌محور در SDLC سازمان‌دهی می‌کند. استفاده از آن به‌عنوان زبان مشترک میان توسعه، QA، AppSec و مدیریت ریسک، از تبدیل Scanner به یک جزیره ابزار جلوگیری می‌کند.

روش تدوین و منابع این راهنما

این مقاله با تفکیک مفاهیم، تحلیل گردش‌کار واقعی QA و تطبیق جزئیات فنی با مستندات رسمی تهیه شده است. قابلیت ابزار، زبان‌های پشتیبانی‌شده، مجوز و Syntax پایپ‌لاین ممکن است تغییر کند؛ پیش از خرید یا پیاده‌سازی، مستندات نسخه نصب‌شده و یک PoC روی کد خودتان را ملاک قرار دهید.

  • OWASP: Source Code Analysis Tools و محدودیت‌های SAST
  • OASIS: استاندارد SARIF ۲.۱.۰
  • SonarSource: Quality Profile، Quality Gate و New Code
  • Semgrep: مفاهیم Rule و Taint analysis
  • CodeQL: Path query و جریان Source-to-Sink
  • ESLint: پیکربندی Rule و Inline suppression
  • MITRE: طبقه‌بندی CWE
  • NIST: Secure Software Development Framework 1.1

بازبینی محتوایی: مرداد ۱۴۰۵. این متن راهنمای فنی و فرایندی است و جایگزین ارزیابی امنیتی متناسب با سامانه، قرارداد یا مقررات سازمان شما نیست.

دیدگاهتان را بنویسید