Skip to content

Сведения о безопасности

Чему вы научитесь

  • Как плагин защищает вашу систему от угроз безопасности
  • Какие правила безопасности должны соблюдать файлы навыков
  • Лучшие практики безопасности при использовании плагина

Основная концепция

Плагин OpenCode Agent Skills работает в вашей локальной среде, выполняя скрипты, читая файлы и разбирая конфигурации. Несмотря на его мощь, если файлы навыков поступают из ненадёжных источников, это может создать риски безопасности.

При проектировании плагина встроены многоуровневые механизмы безопасности, словно несколько защитных ворот: от доступа к путям и разбора файлов до выполнения скриптов — каждый слой строго контролируется. Понимание этих механизмов поможет вам использовать плагин безопаснее.

Подробное описание механизмов безопасности

1. Проверка безопасности путей: предотвращение выхода за пределы каталога

Проблема: Если файл навыков содержит злонамеренный путь (например, ../../etc/passwd), это может позволить доступ к чувствительным системным файлам.

Защитные меры:

Плагин использует функцию isPathSafe() (строки 130-133 в src/utils.ts), чтобы гарантировать, что все обращения к файлам ограничены каталогом навыка:

typescript
export function isPathSafe(basePath: string, requestedPath: string): boolean {
  const resolved = path.resolve(basePath, requestedPath);
  return resolved.startsWith(basePath + path.sep) || resolved === basePath;
}

Как это работает:

  1. Запрошенный путь преобразуется в абсолютный
  2. Проверяется, начинается ли преобразованный путь с каталога навыка
  3. Если путь пытается выйти за пределы каталога навыка (содержит ..), доступ отклоняется

Практические примеры:

Когда инструмент read_skill_file читает файл (строки 101-103 в src/tools.ts), он сначала вызывает isPathSafe:

typescript
// Security: ensure path doesn't escape skill directory
if (!isPathSafe(skill.path, args.filename)) {
  return `Invalid path: cannot access files outside skill directory.`;
}

Это означает:

  • docs/guide.md → Разрешено (внутри каталога навыка)
  • ../../../etc/passwd → Отклонено (попытка доступа к системному файлу)
  • /etc/passwd → Отклонено (абсолютный путь)

Почему это важно

Атаки выхода за пределы каталога (path traversal) — распространённая уязвимость веб-приложений. Даже если плагин работает локально, ненадёжные навыки могут попытаться получить доступ к вашим SSH-ключам, конфигурациям проектов и другим чувствительным файлам.

2. Безопасный парсинг YAML: предотвращение выполнения кода

Проблема: YAML поддерживает пользовательские теги и сложные объекты. Злонамеренный YAML может выполнить код через теги (например, !!js/function).

Защитные меры:

Плагин использует функцию parseYamlFrontmatter() (строки 41-49 в src/utils.ts), применяя строгую стратегию парсинга YAML:

typescript
export function parseYamlFrontmatter(text: string): Record<string, unknown> {
  try {
    const result = YAML.parse(text, {
      // Use core schema which only supports basic JSON-compatible types
      // This prevents custom tags that could execute code
      schema: "core",
      // Limit recursion depth to prevent DoS attacks
      maxAliasCount: 100,
    });
    return typeof result === "object" && result !== null
      ? (result as Record<string, unknown>)
      : {};
  } catch {
    return {};
  }
}

Ключевые параметры безопасности:

ПараметрНазначение
schema: "core"Поддерживает только базовые JSON-типы (строки, числа, логические значения, массивы, объекты), отключает пользовательские теги
maxAliasCount: 100Ограничивает глубину рекурсии алиасов YAML для предотвращения DoS-атак

Практические примеры:

yaml
# Злонамеренный пример YAML (будет отклонён схемой core)
---
!!js/function >
function () { return "malicious code" }
---

# Правильный безопасный формат
---
name: my-skill
description: A safe skill description
---

Если парсинг YAML не удаётся, плагин молча игнорирует навык и продолжает обнаруживать другие навыки (строки 142-145 в src/skills.ts):

typescript
let frontmatterObj: unknown;
try {
  frontmatterObj = parseYamlFrontmatter(frontmatterText);
} catch {
  return null;  // Ошибка парсинга, пропуск этого навыка
}

3. Валидация ввода: строгая проверка Zod Schema

Проблема: Поля frontmatter навыка могут не соответствовать спецификации, что приведёт к аномальному поведению плагина.

Защитные меры:

Плагин использует Zod Schema (строки 105-114 в src/skills.ts) для строгой валидации frontmatter:

typescript
const SkillFrontmatterSchema = z.object({
  name: z.string()
    .regex(/^[\p{Ll}\p{N}-]+$/u, { message: "Name must be lowercase alphanumeric with hyphens" })
    .min(1, { message: "Name cannot be empty" }),
  description: z.string()
    .min(1, { message: "Description cannot be empty" }),
  license: z.string().optional(),
  "allowed-tools": z.array(z.string()).optional(),
  metadata: z.record(z.string(), z.string()).optional()
});

Правила валидации:

ПолеПравилоОтклоняемые примеры
nameСтрочные буквы, цифры, дефисы, не может быть пустымMySkill (заглавные), my skill (пробел)
descriptionНе может быть пустым"" (пустая строка)
licenseНеобязательная строка-
allowed-toolsНеобязательный массив строк[123] (не строки)
metadataНеобязательный объект key-value (значения — строки){key: 123} (значение не строка)

Практические примеры:

yaml
# ❌ Ошибка: name содержит заглавные буквы
---
name: GitHelper
description: Git operations helper
---

# ✅ Правильно: соответствует спецификации
---
name: git-helper
description: Git operations helper
---

Если валидация не проходит, плагин пропускает навык (строки 147-152 в src/skills.ts):

typescript
let frontmatter: SkillFrontmatter;
try {
  frontmatter = SkillFrontmatterSchema.parse(frontmatterObj);
} catch (error) {
  return null;  // Ошибка валидации, пропуск этого навыка
}

4. Безопасность выполнения скриптов: выполняются только исполняемые файлы

Проблема: Если плагин выполняет произвольные файлы (например, файлы конфигурации, документацию), это может привести к неожиданным последствиям.

Защитные меры:

При обнаружении скриптов (строки 59-99 в src/skills.ts) плагин собирает только файлы с правами на выполнение:

typescript
async function findScripts(skillPath: string, maxDepth: number = 10): Promise<Script[]> {
  const scripts: Script[] = [];
  const skipDirs = new Set(['node_modules', '__pycache__', '.git', '.venv', 'venv', '.tox', '.nox']);

  // ... логика рекурсивного обхода ...

  if (stats.isFile()) {
    // Ключевой момент: только файлы с битом исполнения
    if (stats.mode & 0o111) {
      scripts.push({
        relativePath: newRelPath,
        absolutePath: fullPath
      });
    }
  }
  // ...
}

Характеристики безопасности:

Механизм проверкиНазначение
Проверка бита исполнения (stats.mode & 0o111)Выполняются только файлы, явно помеченные пользователем как исполняемые, предотвращая случайное выполнение документации или конфигураций
Пропуск скрытых каталогов (entry.name.startsWith('.'))Не сканируются скрытые каталоги типа .git, .vscode, чтобы избежать сканирования слишком большого количества файлов
Пропуск каталогов зависимостей (skipDirs.has(entry.name))Пропускаются node_modules, __pycache__ и т.д., чтобы избежать сканирования сторонних зависимостей
Ограничение глубины рекурсии (maxDepth: 10)Глубина рекурсии ограничена 10 уровнями, предотвращая проблемы с производительностью из-за глубоких каталогов в злонамеренных навыках

Практические примеры:

В каталоге навыка:

bash
my-skill/
├── SKILL.md
├── deploy.sh          # ✓ Исполняемый (распознаётся как скрипт)
├── build.sh           # ✓ Исполняемый (распознаётся как скрипт)
├── README.md          # ✗ Неисполняемый (не распознаётся как скрипт)
├── config.json        # ✗ Неисполняемый (не распознаётся как скрипт)
└── node_modules/      # ✗ Пропускается (каталог зависимостей)
    └── ...           # ✗ Пропускается

Если вызвать run_skill_script("my-skill", "README.md"), то из-за отсутствия прав на выполнение у README.md файл не будет распознан как скрипт (строка 86 в src/skills.ts), и функция вернёт ошибку "not found" (строки 165-177 в src/tools.ts).

Лучшие практики безопасности

1. Получайте навыки из надёжных источников

  • ✓ Используйте официальные репозитории навыков или доверенных разработчиков
  • ✓ Проверяйте количество GitHub Star и активность контрибьюторов навыка
  • ✗ Не скачивайте и не запускайте навыки из неизвестных источников

2. Рецензируйте содержимое навыков

Перед загрузкой нового навыка быстро просмотрите SKILL.md и файлы скриптов:

bash
# Посмотреть описание и метаданные навыка
cat .opencode/skills/skill-name/SKILL.md

# Проверить содержимое скриптов
cat .opencode/skills/skill-name/scripts/*.sh

Обратите особое внимание на:

  • Доступны ли скрипты к чувствительным системным путям (/etc, ~/.ssh)
  • Устанавливают ли скрипты внешние зависимости
  • Модифицируют ли скрипты системную конфигурацию

3. Правильно устанавливайте права на скрипты

Добавляйте права на выполнение только для тех файлов, которые явно должны выполняться:

bash
# Правильно: добавить права на выполнение для скрипта
chmod +x .opencode/skills/my-skill/tools/deploy.sh

# Правильно: документация остаётся с правами по умолчанию (неисполняемая)
# README.md, config.json и т.д. не требуют выполнения

4. Скрывайте чувствительные файлы

В каталоге навыка не должно быть чувствительной информации:

  • ✗ Файлы .env (API-ключи)
  • ✗ Файлы .pem (закрытые ключи)
  • credentials.json (учётные данные)
  • ✓ Используйте переменные окружения или внешние конфигурации для управления чувствительными данными

5. Навыки уровня проекта перекрывают навыки уровня пользователя

Приоритет обнаружения навыков (строки 241-246 в src/skills.ts):

  1. .opencode/skills/ (уровень проекта)
  2. .claude/skills/ (уровень проекта, Claude)
  3. ~/.config/opencode/skills/ (уровень пользователя)
  4. ~/.claude/skills/ (уровень пользователя, Claude)
  5. ~/.claude/plugins/cache/ (кэш плагинов)
  6. ~/.claude/plugins/marketplaces/ (маркетплейс плагинов)

Лучшие практики:

  • Специфичные для проекта навыки помещайте в .opencode/skills/ — они автоматически перекроют одноимённые навыки уровня пользователя
  • Общие навыки помещайте в ~/.config/opencode/skills/ — будут доступны во всех проектах
  • Не рекомендуется глобально устанавливать навыки из ненадёжных источников

Итог урока

Плагин OpenCode Agent Skills имеет встроенную многоуровневую защиту:

Механизм безопасностиЗащищаемая цельРасположение в коде
Проверка безопасности путейПредотвращает выход за пределы каталога, ограничивает область доступа к файламutils.ts:130-133
Безопасный парсинг YAMLПредотвращает выполнение кода в злонамеренном YAMLutils.ts:41-49
Валидация Zod SchemaГарантирует соответствие frontmatter навыка спецификацииskills.ts:105-114
Проверка исполняемости скриптовВыполняются только файлы, явно помеченные пользователем как исполняемыеskills.ts:86
Логика пропуска каталоговИзбегает сканирования скрытых каталогов и каталогов зависимостейskills.ts:61, 70

Помните: безопасность — это общая ответственность. Плагин предоставляет механизмы защиты, но окончательное решение за вами — используйте только навыки из надёжных источников и выработайте привычку рецензировать код.

Предварительный обзор следующего урока

На следующем уроке мы изучим Лучшие практики разработки навыков.

Вы узнаете:

  • Правила именования и советы по составлению описаний
  • Организацию каталогов и использование скриптов
  • Лучшие практики для Frontmatter
  • Как избежать распространённых ошибок

Приложение: Справочник исходного кода

Нажмите, чтобы раскрыть расположение исходного кода

Обновлено: 2026-01-24

Механизм безопасностиПуть к файлуНомера строк
Проверка безопасности путейsrc/utils.ts130-133
Безопасный парсинг YAMLsrc/utils.ts41-56
Валидация Zod Schemasrc/skills.ts105-114
Проверка исполняемости скриптовsrc/skills.ts86
Логика пропуска каталоговsrc/skills.ts61, 70
Безопасность путей в инструментахsrc/tools.ts101-103

Ключевые функции:

  • isPathSafe(basePath, requestedPath) — проверяет безопасность пути, предотвращая выход за пределы каталога
  • parseYamlFrontmatter(text) — безопасно разбирает YAML, используя core schema и ограничение рекурсии
  • SkillFrontmatterSchema — Zod schema, валидирующая поля frontmatter навыка
  • findScripts(skillPath, maxDepth) — рекурсивно находит исполняемые скрипты, пропуская скрытые и зависимые каталоги

Ключевые константы:

  • maxAliasCount: 100 — максимальное количество алиасов при парсинге YAML для предотвращения DoS-атак
  • maxDepth: 10 — максимальная глубина рекурсии при обнаружении скриптов
  • 0o111 — маска бита исполнения (проверка, является ли файл исполняемым)