{
  "title": "سیاست امنیت",
  "slug": "team/platform/standards/security-policy",
  "url": "/docs/team/platform/standards/security-policy",
  "frontmatter": {
    "layout": "doc",
    "title": "سیاست امنیت",
    "description": "امنیت کد، متغیرهای محیط، دیتابیس، JWT، Docker و پاسخ به خطا",
    "version": "1.0.0",
    "status": "PRIVATE",
    "author": "xoxxel",
    "owner": "xoxxel",
    "created_at": "2026-06-07",
    "updated_at": "2026-06-07",
    "tags": "",
    "reviewers": ""
  },
  "sections": [
    {
      "level": 1,
      "heading": "سیاست امنیت",
      "content": "**Security Policy**\n\nنسخه 1.0 | الزامی برای همه سرویس‌ها و مشارکت‌کنندگان\n\n---"
    },
    {
      "level": 2,
      "heading": "1. اصل اول",
      "content": "**هیچ رازی در کد — هرگز.**\n\n---"
    },
    {
      "level": 2,
      "heading": "2. متغیرهای محیطی (Environment Variables)",
      "content": "| قانون | توضیح |\n|---|---|\n| `.env.example` | باید با همه کلیدها و مقادیر placeholder وجود داشته باشد |\n| `.env` در gitignore | `.env` و `.env.*` (به جز `.env.example` و `.env.test`) در `.gitignore` هستند |\n| هیچ مقدار واقعی | در `.env.example` هرگز مقدار واقعی نگذارید |\n\n```bash"
    },
    {
      "level": 1,
      "heading": "✅ درست — .env.example",
      "content": "AUTH_DB_URL=postgres://user:password@localhost:5432/auth_db\nAUTH_JWT_SECRET=your-secret-key-here\nAUTH_JWT_EXPIRY=15m"
    },
    {
      "level": 1,
      "heading": "❌ غلط — .env.example با کلید واقعی",
      "content": "AUTH_JWT_SECRET=my-real-production-secret-12345\n```\n\n---"
    },
    {
      "level": 2,
      "heading": "3. ممنوعیت‌های کد",
      "content": "| ممنوعیت | توضیح | مثال ❌ |\n|---|---|---|\n| ID سخت‌کد شده | هیچ شناسه، توکن یا رمز عبور در کد | `const API_KEY = \"sk-12345\"` |\n| آدرس داخلی سخت‌کد شده | آدرس‌های دیتابیس یا سرویس در کد | `const DB_URL = \"postgres://...\"` |\n| `console.log` از داده‌های حساس | لاگ کردن توکن، رمز، email | `console.log(\"Token:\", token)` |\n| رمز عبور در لاگ | هرگز رمز عبور یا توکن را لاگ نکنید | `logger.info(\"Login: \" + password)` |\n| comment شامل راز | کامنت‌های حاوی API Key | `// TODO: use key sk-xxxx` |\n\n---"
    },
    {
      "level": 2,
      "heading": "4. امنیت دیتابیس",
      "content": "| قانون | توضیح |\n|---|---|\n| **Parameterized Queries** | همه کوئری‌ها از binding parameter استفاده کنند — بدون الحاق رشته |\n| **حداقل دسترسی** | کاربر دیتابیس فقط دسترسی لازم را داشته باشد |\n| **بدون SQL خام** | از ORM یا query builder استفاده کنید |\n\n```typescript\n// ✅ درست — Parameterized Query\nconst result = await db.query(\n  'SELECT * FROM orders WHERE id = $1 AND seller_id = $2',\n  [orderId, sellerId]\n);\n\n// ❌ غلط — String Concatenation\nconst result = await db.query(\n  `SELECT * FROM orders WHERE id = '${orderId}'`\n);\n```\n\n---"
    },
    {
      "level": 2,
      "heading": "5. امنیت API",
      "content": "| قانون | توضیح |\n|---|---|\n| **Authentication** | همه endpoints (به جز Public) نیاز به توکن معتبر دارند |\n| **Authorization** | بررسی مجوز در IAM از طریق `POST /v1/iam/authorization/check` — هر سرویس Permissions خود را در IAM ثبت می‌کند |\n| **Rate Limiting** | محدودیت نرخ برای همه endpoints |\n| **Input Validation** | اعتبارسنجی همه ورودی‌ها در gateway یا middleware |\n| **CORS** | فقط دامنه‌های مجاز |\n\n> **قاعده Permission Contract:** هر سرویس Permissions مورد نیاز خود را در IAM ثبت می‌کند. IAM را مستقیماً لاگین/نقش ذخیره نمی‌کند. مطالعه کامل: [استاندارد قرارداد مجوز](permission-contract-standard.md)\n\n---"
    },
    {
      "level": 2,
      "heading": "6. امنیت JWT",
      "content": "| مورد | مقدار |\n|---|---|\n| الگوریتم | `RS256` یا `HS256` |\n| طول توکن دسترسی | حداکثر **۱۵ دقیقه** |\n| طول توکن Refresh | حداکثر **۷ روز** |\n| ذخیره سمت کلاینت | `HttpOnly` cookie (برای وب) |\n| چرخش | هر بار استفاده از Refresh Token، توکن جدید صادر شود |\n\n```typescript\n// ✅ درست\nconst accessToken = jwt.sign(payload, secret, { expiresIn: '15m' });\nconst refreshToken = jwt.sign(payload, refreshSecret, { expiresIn: '7d' });\n\n// ❌ غلط — توکن طولانی\nconst accessToken = jwt.sign(payload, secret, { expiresIn: '30d' });\n```\n\n---"
    },
    {
      "level": 2,
      "heading": "7. امنیت Cookie",
      "content": "| قانون | توضیح |\n|---|---|\n| `HttpOnly` | غیرقابل دسترسی از جاوااسکریپت |\n| `Secure` | فقط از طریق HTTPS ارسال شود |\n| `SameSite` | `Strict` یا `Lax` |\n| `Path` | محدود به مسیرهای ضروری |\n\n```typescript\n// ✅ درست\nres.cookie('refreshToken', token, {\n  httpOnly: true,\n  secure: true,\n  sameSite: 'strict',\n  path: '/v1/auth',\n  maxAge: 7 * 24 * 60 * 60 * 1000\n});\n```\n\n---"
    },
    {
      "level": 2,
      "heading": "8. امنیت Docker",
      "content": "| قانون | توضیح |\n|---|---|\n| **Non-root user** | کانتینر با کاربر `node` یا کاربر اختصاصی اجرا شود |\n| **Alpine/Slim** | از تصاویر پایه حداقلی استفاده کنید |\n| **اسکن امنیتی** | تصویر نهایی از نظر آسیب‌پذیری اسکن شود |\n| **بدون راز در تصویر** | هیچ `.env` یا فایل راز در تصویر نباشد |\n\n```dockerfile"
    },
    {
      "level": 1,
      "heading": "✅ درست",
      "content": "FROM node:20-alpine\nRUN addgroup -S appgroup && adduser -S appuser -G appgroup\nUSER appuser\nWORKDIR /app\nCOPY --from=builder /app/dist ./dist\nCMD [\"node\", \"dist/main.js\"]\n```\n\n---"
    },
    {
      "level": 2,
      "heading": "9. پاسخ به خطا",
      "content": "| قانون | توضیح |\n|---|---|\n| بدون stack trace | هرگز stack trace یا خطاهای دیتابیس را نشان ندهید |\n| پیام generic | `Internal server error` به جای جزئیات فنی |\n| خطاهای امنیتی generic | `Invalid credentials` به جای `User not found` (جلوگیری از تشخیص username) |\n\n```json\n// ✅ درست\n{ \"error\": { \"code\": \"AUTH_INVALID_CREDENTIALS\", \"message\": \"Invalid email or password\", \"details\": [] } }\n\n// ❌ غلط — بیش از حد اطلاعات\n{ \"error\": { \"code\": \"USER_NOT_FOUND\", \"message\": \"User with email x@y.com does not exist\", \"details\": [] } }\n```\n\n---"
    },
    {
      "level": 2,
      "heading": "خلاصه",
      "content": "| حوزه | قانون اصلی |\n|---|---|\n| کد | هیچ رازی در کد نباشد |\n| .env | `.env.example` با placeholder |\n| دیتابیس | Parameterized Queries |\n| JWT | Access Token: ۱۵m, Refresh Token: ۷d |\n| Cookie | HttpOnly + Secure + SameSite |\n| Docker | Non-root user |\n| خطا | هرگز stack trace یا جزئیات فنی |\n| Rate Limit | همه endpoints |\n\n---"
    },
    {
      "level": 2,
      "heading": "10. پیکربندی برون‌ریز (Externalized Configuration)",
      "content": "**Externalized Configuration**"
    },
    {
      "level": 3,
      "heading": "۱۰.۱ تفاوت Configuration و Secret",
      "content": "Secrets (رمزها، کلیدها) با Configurations (timeout، retry، delay) تفاوت دارند — هر دو باید خارج از کد باشند اما جدا از هم مدیریت می‌شوند:\n\n| نوع | ویژگی | مثال |\n|-----|--------|------|\n| **Secret** | محرمانه، دسترسی محدود، هرگز در git | `DB_PASSWORD`، `JWT_SECRET`، `API_KEY` |\n| **Configuration** | غیرمحرمانه، مقدار پیش‌فرض دارد، در `.env.example` | `APP_TIMEOUT_MS`، `APP_MAX_RETRY`، `LOG_LEVEL`، `APP_ESCROW_RELEASE_DELAY_MS` |\n\n> **فرمت env var:** مطابق [قراردادهای نام‌گذاری](../standards/naming-conventions#3-%D9%85%D8%AA%D8%BA%DB%8C%D8%B1%D9%87%D8%A7%DB%8C-%D9%85%D8%AD%DB%8C%D8%B7%DB%8C-environment-variables)، فرمت متغیرهای محیطی `{SERVICE}_{CATEGORY}_{NAME}` با `SCREAMING_SNAKE_CASE` است. در مثال‌های بالا `APP_` به عنوان پیشوند generic استفاده شده — در سرویس واقعی با نام سرویس جایگزین شود (مثلاً `ORDER_TIMEOUT_MS`)."
    },
    {
      "level": 3,
      "heading": "۱۰.۲ فایل‌های env",
      "content": "هر سرویس باید دو فایل داشته باشد:\n\n| فایل | در git | توضیح |\n|------|--------|-------|\n| `.env.example` | بله | مرجع توسعه‌دهنده — شامل همه کلیدها با مقادیر placeholder |\n| `.env` | خیر | مقادیر واقعی محیط — مخصوص هر محیط (توسعه، استیجینگ، تولید) |\n\n```bash"
    },
    {
      "level": 1,
      "heading": ".env.example — مرجع توسعه‌دهنده",
      "content": "APP_DB_HOST=localhost\nAPP_DB_PORT=5432\nAPP_DB_NAME=myapp_db\nAPP_DB_USER=user\nAPP_DB_PASSWORD=your-password-here\n\nAPP_TIMEOUT_MS=5000\nAPP_MAX_RETRY=3\nLOG_LEVEL=debug\n```"
    },
    {
      "level": 3,
      "heading": "۱۰.۳ قانون hardcode",
      "content": "**اگر تغییر یک مقدار نیاز به build مجدد دارد، باید env var باشد.**\n\n```typescript\n// ❌ غلط — hardcoded configuration\nconst TIMEOUT = 5000;\nconst MAX_RETRIES = 3;\nconst API_BASE_URL = 'https://api.example.com';\n\n// ✅ درست — externalized\nconst TIMEOUT = parseInt(process.env.APP_TIMEOUT_MS ?? '5000', 10);\nconst MAX_RETRIES = parseInt(process.env.APP_MAX_RETRY ?? '3', 10);\n```"
    }
  ]
}