[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill:01M6MX2AJC5MDBMHYN82K2PK36":3,"skill-same:01M6MX2AJC5MDBMHYN82K2PK36":51},{"skillNo":4,"name":5,"nameZh":6,"description":7,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":15,"categories":16,"keywords":17,"publishedAt":25,"linkOnly":26,"markdownContent":27,"markdownBody":28,"frontMatter":29,"refName":30,"commitSha":31,"skillDirectory":32,"forksCount":33,"repositoryHtmlUrl":34,"repositoryHomepageUrl":6,"ownerHtmlUrl":35,"ownerBlogUrl":6,"ownerDescription":6,"updatedAt":36,"install":37,"resources":50},"01M6MX2AJC5MDBMHYN82K2PK36","security",null,"Security — чеклист безопасности Go-сервисов: валидация входа, секреты, auth, CVE-зависимости. Используй при правке auth-middleware, обработке user input, внешних вызовах, или как чеклист в \u002Fgo-review.","vmkteam\u002Fclaude-plugins","vmkteam","https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F55537877?v=4","01M6MWYAFDCD63N1M8MBJA6T0S","01M6MWYAKHK0QCPR0Y7AW5SH0R",7,"MIT","https:\u002F\u002Fgithub.com\u002Fvmkteam\u002Fclaude-plugins\u002Fblob\u002Fmaster\u002Fplugins\u002Fdeveloper\u002Fskills\u002Fsecurity\u002FSKILL.md",[],[5,18,19,20,21,22,23,24],"review","go","auth","cve","user","input","middleware","2026-11-10 10:12:30",false,"---\nname: security\ndescription: \"Security — чеклист безопасности Go-сервисов: валидация входа, секреты, auth, CVE-зависимости. Используй при правке auth-middleware, обработке user input, внешних вызовах, или как чеклист в \u002Fgo-review.\"\n---\n\n# Security — безопасность vmkteam Go-сервисов\n\nСправочник по безопасности для vmkteam-стека: JSON-RPC API, go-pg, zenrpc, auth middleware. Используется автоматически при code review (\u002Fgo-review) и разработке (\u002Fsolve).\n\n## Инструменты\n\n| Инструмент | Что ищет | Команда |\n|-----------|----------|---------|\n| `gosec` | SQL injection, hardcoded credentials, weak crypto, command injection | `gosec .\u002F...` |\n| `govulncheck` | Уязвимости в зависимостях (только реально используемые функции) | `govulncheck .\u002F...` |\n| `gitleaks` | API keys, tokens, passwords в коде и git-истории | `gitleaks detect --source .` |\n| `trivy` | Уязвимости в Docker-образах | CI only |\n\n## SQL Injection\n\nГлавная точка риска — `_repo_ext.go` (кастомные запросы).\n\n**Плохо** — пользовательский ввод в raw SQL:\n```go\n\u002F\u002F УЯЗВИМО: searchText попадает в SQL напрямую\nfunc (r OrderRepo) SearchOrders(ctx context.Context, searchText string) ([]Order, error) {\n    var orders []Order\n    _, err := r.db.Query(&orders,\n        \"SELECT * FROM orders WHERE title LIKE '%\"+searchText+\"%'\", nil)\n    return orders, err\n}\n```\n\n**Хорошо** — параметризованный запрос через go-pg:\n```go\nfunc (r OrderRepo) SearchOrders(ctx context.Context, searchText string) ([]Order, error) {\n    var orders []Order\n    err := r.db.Model(&orders).\n        Where(\"title ILIKE ?\", \"%\"+searchText+\"%\").\n        Select()\n    return orders, err\n}\n```\n\n**Хорошо** — если нужен raw SQL, использовать `pg.SafeQuery`:\n```go\nq := r.db.Model(&orders)\nq = q.Where(\"? ILIKE ?\", pg.Ident(\"title\"), \"%\"+searchText+\"%\")\n```\n\n**Правило**: go-pg ORM экранирует автоматически. Опасность только в `Query()` с конкатенацией строк.\n\n## Аутентификация и авторизация\n\n### Auth middleware (типичная ошибка — IDOR)\n\nMiddleware проверяет **аутентификацию** (есть ли валидный токен), но **не авторизацию** (может ли этот пользователь делать это действие).\n\n**Плохо** — middleware пропустил, но метод не проверяет ownership:\n```go\nfunc (s OrderService) GetByID(ctx context.Context, id int) (*Order, error) {\n    \u002F\u002F Любой авторизованный пользователь может получить любой заказ\n    return s.repo.OrderByID(ctx, id)\n}\n```\n\n**Хорошо** — проверка ownership:\n```go\nfunc (s OrderService) GetByID(ctx context.Context, id int) (*Order, error) {\n    user := UserFromContext(ctx)\n    order, err := s.repo.OrderByID(ctx, id)\n    if err != nil {\n        return nil, newInternalError(err)\n    }\n    if order == nil {\n        return nil, ErrNotFound\n    }\n    if order.UserID != user.ID {\n        return nil, ErrForbidden\n    }\n    return NewOrder(order), nil\n}\n```\n\n### Auth whitelist\n\nВ `server.go` middleware пропускает определённые методы без авторизации. Whitelist должен быть **минимальным**:\n\n```go\n\u002F\u002F Только auth.Login не требует токен\nif ns == NSAuth && method == RPC.AuthService.Login {\n    return h(ctx, method, params)\n}\n```\n\n## Error Information Leakage\n\n`zenrpc.NewError(code, err)` кладёт `err.Error()` в `message` ответа. Для кода 500 (и отрицательных) это безопасно только благодаря middleware: `zm.WithErrorSLog` (legacy — `zm.WithErrorLogger`) логирует такую ошибку, отправляет её в Sentry и заменяет `message` на `\"Internal error\"`. Для остальных кодов маскировки нет.\n\n**Хорошо** — внутренняя ошибка: причина уходит в Sentry, клиент видит `\"Internal error\"`:\n```go\nfunc newInternalError(err error) *zenrpc.Error {\n    return zenrpc.NewError(http.StatusInternalServerError, err)\n}\n\nreturn nil, newInternalError(err)\n```\nРаботает, только если `zm.WithErrorSLog` есть в `rpc.Use(...)` в `server.go` — без него клиент получит текст ошибки.\n\n**Хорошо** — клиентская ошибка с фиксированным текстом:\n```go\nvar ErrNotFound = zenrpc.NewStringError(http.StatusNotFound, \"not found\")\n```\n\n**Плохо** — `NewError` с кодом 4xx: текст внутренней ошибки уходит клиенту как есть:\n```go\nreturn nil, zenrpc.NewError(http.StatusBadRequest, err)\n\u002F\u002F Клиент увидит: {\"error\":{\"code\":400,\"message\":\"pq: relation \\\"orders\\\" does not exist\"}}\n```\n\n**Плохо** — `zenrpc.NewStringError(500, \"internal error\")` вместо `newInternalError(err)`: клиенту безопасно, но в Sentry уходит строка без причины, и диагностировать нечем.\n\n## Sensitive Data in Logs\n\nzenrpc-middleware (`WithSLog`, `WithAPILogger`) логирует параметры запросов. Если метод принимает пароль — он попадёт в логи.\n\n**Решение**: не передавать секреты как RPC-параметры, или фильтровать в middleware.\n\n## JSON-RPC специфика\n\n- Все методы через один POST endpoint (`\u002Frpc\u002F`) — стандартные WAF-правила для REST не работают\n- Rate limiting на уровне reverse proxy (traefik\u002Fnginx) — по IP, не по методу\n- CORS: `AllowOrigins: []string{\"*\"}` допустим в dev, но в production ограничивать доменами\n\n## Чеклист для code review\n\n| Категория | Проверить |\n|-----------|----------|\n| **SQL** | Нет конкатенации строк в `_repo_ext.go` |\n| **Auth** | Whitelist методов минимальный |\n| **Authz** | Проверка ownership\u002Fправ внутри методов (не только middleware) |\n| **Validation** | Все входы через `go-playground\u002Fvalidator`, max длины строк |\n| **Errors** | 4xx — `NewStringError` с фиксированным текстом; 500 — `newInternalError(err)`, и в цепочке middleware есть `zm.WithErrorSLog` |\n| **Logging** | Пароли\u002Fтокены не в параметрах логируемых методов |\n| **Secrets** | `cfg\u002Flocal.toml` в `.gitignore`, нет hardcoded credentials |\n| **CORS** | Ограничен в production |\n","# Security — безопасность vmkteam Go-сервисов\n\nСправочник по безопасности для vmkteam-стека: JSON-RPC API, go-pg, zenrpc, auth middleware. Используется автоматически при code review (\u002Fgo-review) и разработке (\u002Fsolve).\n\n## Инструменты\n\n| Инструмент | Что ищет | Команда |\n|-----------|----------|---------|\n| `gosec` | SQL injection, hardcoded credentials, weak crypto, command injection | `gosec .\u002F...` |\n| `govulncheck` | Уязвимости в зависимостях (только реально используемые функции) | `govulncheck .\u002F...` |\n| `gitleaks` | API keys, tokens, passwords в коде и git-истории | `gitleaks detect --source .` |\n| `trivy` | Уязвимости в Docker-образах | CI only |\n\n## SQL Injection\n\nГлавная точка риска — `_repo_ext.go` (кастомные запросы).\n\n**Плохо** — пользовательский ввод в raw SQL:\n```go\n\u002F\u002F УЯЗВИМО: searchText попадает в SQL напрямую\nfunc (r OrderRepo) SearchOrders(ctx context.Context, searchText string) ([]Order, error) {\n    var orders []Order\n    _, err := r.db.Query(&orders,\n        \"SELECT * FROM orders WHERE title LIKE '%\"+searchText+\"%'\", nil)\n    return orders, err\n}\n```\n\n**Хорошо** — параметризованный запрос через go-pg:\n```go\nfunc (r OrderRepo) SearchOrders(ctx context.Context, searchText string) ([]Order, error) {\n    var orders []Order\n    err := r.db.Model(&orders).\n        Where(\"title ILIKE ?\", \"%\"+searchText+\"%\").\n        Select()\n    return orders, err\n}\n```\n\n**Хорошо** — если нужен raw SQL, использовать `pg.SafeQuery`:\n```go\nq := r.db.Model(&orders)\nq = q.Where(\"? ILIKE ?\", pg.Ident(\"title\"), \"%\"+searchText+\"%\")\n```\n\n**Правило**: go-pg ORM экранирует автоматически. Опасность только в `Query()` с конкатенацией строк.\n\n## Аутентификация и авторизация\n\n### Auth middleware (типичная ошибка — IDOR)\n\nMiddleware проверяет **аутентификацию** (есть ли валидный токен), но **не авторизацию** (может ли этот пользователь делать это действие).\n\n**Плохо** — middleware пропустил, но метод не проверяет ownership:\n```go\nfunc (s OrderService) GetByID(ctx context.Context, id int) (*Order, error) {\n    \u002F\u002F Любой авторизованный пользователь может получить любой заказ\n    return s.repo.OrderByID(ctx, id)\n}\n```\n\n**Хорошо** — проверка ownership:\n```go\nfunc (s OrderService) GetByID(ctx context.Context, id int) (*Order, error) {\n    user := UserFromContext(ctx)\n    order, err := s.repo.OrderByID(ctx, id)\n    if err != nil {\n        return nil, newInternalError(err)\n    }\n    if order == nil {\n        return nil, ErrNotFound\n    }\n    if order.UserID != user.ID {\n        return nil, ErrForbidden\n    }\n    return NewOrder(order), nil\n}\n```\n\n### Auth whitelist\n\nВ `server.go` middleware пропускает определённые методы без авторизации. Whitelist должен быть **минимальным**:\n\n```go\n\u002F\u002F Только auth.Login не требует токен\nif ns == NSAuth && method == RPC.AuthService.Login {\n    return h(ctx, method, params)\n}\n```\n\n## Error Information Leakage\n\n`zenrpc.NewError(code, err)` кладёт `err.Error()` в `message` ответа. Для кода 500 (и отрицательных) это безопасно только благодаря middleware: `zm.WithErrorSLog` (legacy — `zm.WithErrorLogger`) логирует такую ошибку, отправляет её в Sentry и заменяет `message` на `\"Internal error\"`. Для остальных кодов маскировки нет.\n\n**Хорошо** — внутренняя ошибка: причина уходит в Sentry, клиент видит `\"Internal error\"`:\n```go\nfunc newInternalError(err error) *zenrpc.Error {\n    return zenrpc.NewError(http.StatusInternalServerError, err)\n}\n\nreturn nil, newInternalError(err)\n```\nРаботает, только если `zm.WithErrorSLog` есть в `rpc.Use(...)` в `server.go` — без него клиент получит текст ошибки.\n\n**Хорошо** — клиентская ошибка с фиксированным текстом:\n```go\nvar ErrNotFound = zenrpc.NewStringError(http.StatusNotFound, \"not found\")\n```\n\n**Плохо** — `NewError` с кодом 4xx: текст внутренней ошибки уходит клиенту как есть:\n```go\nreturn nil, zenrpc.NewError(http.StatusBadRequest, err)\n\u002F\u002F Клиент увидит: {\"error\":{\"code\":400,\"message\":\"pq: relation \\\"orders\\\" does not exist\"}}\n```\n\n**Плохо** — `zenrpc.NewStringError(500, \"internal error\")` вместо `newInternalError(err)`: клиенту безопасно, но в Sentry уходит строка без причины, и диагностировать нечем.\n\n## Sensitive Data in Logs\n\nzenrpc-middleware (`WithSLog`, `WithAPILogger`) логирует параметры запросов. Если метод принимает пароль — он попадёт в логи.\n\n**Решение**: не передавать секреты как RPC-параметры, или фильтровать в middleware.\n\n## JSON-RPC специфика\n\n- Все методы через один POST endpoint (`\u002Frpc\u002F`) — стандартные WAF-правила для REST не работают\n- Rate limiting на уровне reverse proxy (traefik\u002Fnginx) — по IP, не по методу\n- CORS: `AllowOrigins: []string{\"*\"}` допустим в dev, но в production ограничивать доменами\n\n## Чеклист для code review\n\n| Категория | Проверить |\n|-----------|----------|\n| **SQL** | Нет конкатенации строк в `_repo_ext.go` |\n| **Auth** | Whitelist методов минимальный |\n| **Authz** | Проверка ownership\u002Fправ внутри методов (не только middleware) |\n| **Validation** | Все входы через `go-playground\u002Fvalidator`, max длины строк |\n| **Errors** | 4xx — `NewStringError` с фиксированным текстом; 500 — `newInternalError(err)`, и в цепочке middleware есть `zm.WithErrorSLog` |\n| **Logging** | Пароли\u002Fтокены не в параметрах логируемых методов |\n| **Secrets** | `cfg\u002Flocal.toml` в `.gitignore`, нет hardcoded credentials |\n| **CORS** | Ограничен в production |\n",{"name":5,"description":7},"master","8f3147ce24a1a470d9aaa02aa775d7797d621d95","plugins\u002Fdeveloper\u002Fskills\u002Fsecurity",1,"https:\u002F\u002Fgithub.com\u002Fvmkteam\u002Fclaude-plugins","https:\u002F\u002Fgithub.com\u002Fvmkteam","2026-11-05 00:26:25",{"source":8,"skillName":5,"commands":38,"prompt":48,"zipAvailable":49},[39,42,45],{"tool":40,"command":41},"npx","npx skills add vmkteam\u002Fclaude-plugins --skill security",{"tool":43,"command":44},"bunx","bunx skills add vmkteam\u002Fclaude-plugins --skill security",{"tool":46,"command":47},"pnpm","pnpm dlx skills add vmkteam\u002Fclaude-plugins --skill security","安装技能，技能地址：https:\u002F\u002Fgithub.com\u002Fvmkteam\u002Fclaude-plugins\u002Fblob\u002Fmaster\u002Fplugins\u002Fdeveloper\u002Fskills\u002Fsecurity\u002FSKILL.md",true,[],[52,62,74,89,106,118],{"skillNo":53,"name":54,"nameZh":6,"description":55,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":56,"categories":57,"keywords":58,"publishedAt":25},"01M6MX3A8MZJBTMMK6WMY35FYH","zenrpc","Zenrpc — справочник по JSON-RPC 2.0 серверу для Go. Используй при создании и редактировании RPC-сервисов.","https:\u002F\u002Fgithub.com\u002Fvmkteam\u002Fclaude-plugins\u002Fblob\u002Fmaster\u002Fplugins\u002Fdeveloper\u002Fskills\u002Fzenrpc\u002FSKILL.md",[],[54,19,59,60,61],"json-rpc","json","rpc",{"skillNo":63,"name":64,"nameZh":6,"description":65,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":66,"categories":67,"keywords":68,"publishedAt":25},"01M6MX36PEA0NSHCCRF5GGVVTZ","youtrack","YouTrack — задачи через REST API и pcurl. Используй для поиска issues, создания, обновления описаний, смены assignee\u002Fstage\u002Fприоритета, логирования времени и работы с комментариями.","https:\u002F\u002Fgithub.com\u002Fvmkteam\u002Fclaude-plugins\u002Fblob\u002Fmaster\u002Fplugins\u002Fdeveloper\u002Fskills\u002Fyoutrack\u002FSKILL.md",[],[64,69,70,71,72,73],"issues","REST","api","stage","rest api",{"skillNo":75,"name":76,"nameZh":6,"description":77,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":78,"categories":79,"keywords":80,"publishedAt":25},"01M6MX322TD9HQPCYSNTFB4HBA","topsrv","topsrv — метрики, алерты, CVE и HTTP-логи серверов проекта через публичный JSON-RPC API app.topsrv.io. Используй, когда нужна загрузка CPU, памяти или диска хоста, метрики PostgreSQL\u002Fnginx\u002Fangie агента topsrv (`topsrv_*`), какие алерты горят, уязвимые пакеты на серверах или кто (боты, сети, IP) нагружает сайт.","https:\u002F\u002Fgithub.com\u002Fvmkteam\u002Fclaude-plugins\u002Fblob\u002Fmaster\u002Fplugins\u002Fdeveloper\u002Fskills\u002Ftopsrv\u002FSKILL.md",[],[76,81,82,61,21,83,71,84,60,85,86,87,59,88],"CPU","postgresql","nginx","Bash","ip","app","io","http",{"skillNo":90,"name":91,"nameZh":6,"description":92,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":93,"categories":94,"keywords":100,"publishedAt":25},"01M6MX2XFXCHNRPPW6WYZ15BXN","testing","Testing — используй всегда, когда пишешь или правишь Go-тесты (*_test.go), в том числе по сценариям Given\u002FWhen\u002FThen из spec: фреймворк как в соседних файлах, префикс TestDB, фабрики pkg\u002Fdb\u002Ftest, реальная БД без моков. Также при настройке TestMain\u002Ftest-short и когда тесты падают в gate, но проходят локально.","https:\u002F\u002Fgithub.com\u002Fvmkteam\u002Fclaude-plugins\u002Fblob\u002Fmaster\u002Fplugins\u002Fdeveloper\u002Fskills\u002Ftesting\u002FSKILL.md",[95],{"categoryNo":96,"categoryName":97,"taxonomyType":98,"taxonomyTypeDesc":99},"01JBSEEDCAT000000000000008","测试与质量","SCENARIO","使用场景",[91,101,102,19,103,104,105],"gate","spec","test","short","db",{"skillNo":107,"name":108,"nameZh":6,"description":109,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":110,"categories":111,"keywords":112,"publishedAt":25},"01M6MX2P8RFYPV4EXRMVW6KAN7","solve","Solve — полный цикл решения задачи из YouTrack: preflight → анализ → план → реализация → ревью → коммит. Используй при любой YouTrack-задаче (`\u002Fsolve PLF-819`); флаг `--fast` сокращает HITL для тривиальных багфиксов.","https:\u002F\u002Fgithub.com\u002Fvmkteam\u002Fclaude-plugins\u002Fblob\u002Fmaster\u002Fplugins\u002Fdeveloper\u002Fskills\u002Fsolve\u002FSKILL.md",[],[108,113,114,115,116,117,64],"fast","preflight","task","hitl","id",{"skillNo":119,"name":120,"nameZh":6,"description":121,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":122,"categories":123,"keywords":124,"publishedAt":25},"01M6MX2H6TF2VW75PCPGWR2SKZ","skills-check","Skills Check — smoke-тесты интеграций из project-index.md. Используй для проверки что pcurl-профили и URL всех data-source скиллов ещё живы.","https:\u002F\u002Fgithub.com\u002Fvmkteam\u002Fclaude-plugins\u002Fblob\u002Fmaster\u002Fplugins\u002Fdeveloper\u002Fskills\u002Fskills-check\u002FSKILL.md",[],[125,126,127,84,128,129,130,131,132,133,134,135,136,137],"skills check","check","data","smoke","probe","project","false","URL","data-source","true","source","fork","index"]