Skip to content

feat(env): manage env variables without returning their values - #59

Merged
agentHits merged 2 commits into
AgentHits-Devfrom
feat/blind-env-api
Oct 12, 2026
Merged

agentHits merged 2 commits into
AgentHits-Devfrom
feat/blind-env-api

Conversation

@agentHits

Copy link
Copy Markdown
Collaborator

Что это

Способ управлять переменными окружения «вслепую»: ИИ-агент или скрипт с API-ключом видит имена переменных и меняет их, но ни один ответ не содержит значения.

Новые методы

  • GET /env.keys — список имён с флагами secret и empty и ревизией. Значений нет.
  • POST /env.apply (tRPC-процедура env.applyChanges: имя apply в tRPC зарезервировано) — одна операция, в которой можно:
    • set — задать значение;
    • remove — удалить переменную;
    • generate — создать случайный секрет на стороне панели (длина 16–128, форматы base64url, hex, alphanumeric); значение никуда не возвращается;
    • copy — скопировать значение из другой переменной или другого сервиса, не раскрывая его; так же делается переименование (copy, затем remove).
      В ответе только имя, действие (created, updated, unchanged, removed, missing) и источник. Есть dryRun и expectedRevision.

Области: application, compose, postgres, mysql, mariadb, mongo, redis, libsql, project, environment. Для приложения доступны ещё buildArgs и buildSecrets.

Как это устроено

  • Текст правится по грамматике dotenv: многострочное значение в кавычках заменяется и удаляется целиком; комментарии, порядок, export и завершающий перевод строки сохраняются. После правки текст разбирается заново и сверяется с ожидаемым набором — если не сошлось, ничего не сохраняется.
  • Чтение, проверка ревизии и запись идут в одной транзакции с блокировкой строки.
  • Права: для сервисов envVars read/write, для проекта projectEnvVars, для окружения environmentEnvVars, плюс проверка организации. Источник copy требует права чтения в своей области.
  • Супер-пароль: env.keys — чтение, env.applyChanges — обычная запись (по API-ключу работает, пока открыт доступ).
  • Существующие application.envUpsert и compose.env.upsert не тронуты; ревизии совместимы.
  • env.keys не показывает «имена», похожие на кусок закодированных данных (блок ключа, вставленный без кавычек).

Ограничения

  • Действие unchanged показывает, совпало ли переданное значение с текущим. Так же ведут себя существующие upsert-методы.
  • Изменение env вступает в силу после redeploy сервиса.

Проверки

  • bun run typecheck в apps/dokploy и packages/server — без ошибок.
  • bun run test -- --run __test__/security __test__/env — проходит; полный набор без *.real.test.ts — проходит.
  • Проверена совместимость с PR feat(security): keep secret values out of API key responses #58 (фильтр ответов для API-ключей): локальное слияние обеих веток — без конфликтов, 289 файлов, 2902 теста проходят. PR независимы, порядок слияния любой.
  • biome check по изменённым файлам — 0 ошибок.
  • На живой панели и настоящем PostgreSQL не проверялось: транзакция и FOR UPDATE проверены на тестовой базе в памяти.

🤖 Generated with Claude Code

agentHits and others added 2 commits October 12, 2026 13:31
Что:
- Новый tRPC-роутер env. env.keys (GET /env.keys) отдаёт только имена
  переменных с флагами secret/empty и ревизией. env.applyChanges
  (POST /env.apply, operationId env-apply) задаёт, удаляет, генерирует на
  сервере и копирует переменные и возвращает только имя, действие и
  источник.
- Области: application, compose, postgres, mysql, mariadb, mongo, redis,
  libsql, project, environment. Для application дополнительно доступны
  поля buildArgs и buildSecrets.
- applyEnvChanges правит текст по грамматике dotenv: многострочное
  значение в кавычках заменяется и удаляется целиком, комментарии, пустые
  строки, порядок, export и завершающий перевод строки сохраняются. После
  правки текст заново разбирается и сверяется с ожидаемым набором
  переменных.
- Значение записывается в форме, которую dotenv читает обратно символ в
  символ (JSON, обратные слэши, кавычки, многострочные блоки).
- Чтение, проверка ревизии и запись идут в одной транзакции с блокировкой
  строки. Источник copy читается в той же транзакции.
- Добавлен общий хелпер ревизии getEnvFieldRevision. Для application/env
  и compose используются существующие ревизии, поэтому они совместимы с
  application.envUpsert и compose.env.upsert.
- Регулярное выражение имени переменной вынесено в одно место, им
  пользуются и старые схемы upsert.
- Тестовый fake-drizzle-db теперь умеет select ... for update и вложенные
  with.
Зачем:
- ИИ-агенту с API-ключом нужно управлять переменными окружения, не
  получая их значений: увидеть имена, задать значение, сгенерировать его
  на сервере, скопировать между сервисами, переименовать и удалить.
Риски:
- tRPC резервирует имя apply, поэтому процедура называется
  env.applyChanges. REST-путь /env.apply закреплён через meta.openapi, в
  procedure-access процедура записана как env.applyChanges.
- Действие unchanged, в том числе на dryRun, показывает, совпало ли
  переданное или скопированное значение с текущим. Так же ведут себя
  существующие upsert-методы.
- Имена берутся из разбора dotenv. В повреждённом файле, где секрет
  занимает несколько строк без кавычек, часть значения может оказаться
  именем переменной.
- Значение, которое нельзя записать без искажения, и правка, меняющая
  разбор соседних строк, отклоняются с BAD_REQUEST и не сохраняются.
- Поле, которое не удалось расшифровать, не читается и не правится
  (PRECONDITION_FAILED).
- upsertEnvVariables и старые upsert-методы не менялись и остаются
  построчными.
Проверки:
- Команды и результаты: bun run typecheck в packages/server и в
  apps/dokploy — без ошибок; bun run test -- --run по пяти новым и
  изменённым файлам тестов — 5 файлов, 76 тестов прошли; bun run test --
  --run __test__/security __test__/env — 38 файлов, 413 тестов прошли;
  bun run test -- --run целиком — 285 файлов прошли, 1 пропущен, 1 упал;
  biome check по 15 затронутым файлам — без замечаний.
- Ограничения: упавший файл __test__/deploy/application.real.test.ts (4
  теста) требует запущенного Docker, в этой среде демон недоступен
  (ECONNREFUSED на сокете Docker). Транзакция и блокировка строки
  проверены только на тестовой базе в памяти, на настоящем PostgreSQL и
  на живой панели ничего не запускалось. openapi.json в корне не
  перегенерирован.

What:
- New tRPC router env. env.keys (GET /env.keys) returns only variable
  names with secret/empty flags and a revision. env.applyChanges
  (POST /env.apply, operationId env-apply) sets, removes, generates
  server-side and copies variables and returns only the name, action and
  source.
- Scopes: application, compose, postgres, mysql, mariadb, mongo, redis,
  libsql, project, environment. The application scope also takes the
  buildArgs and buildSecrets fields.
- applyEnvChanges edits the text by the dotenv grammar: a multi-line
  quoted value is replaced and removed as a whole, while comments, blank
  lines, ordering, export and the trailing newline are kept. After the
  edit the text is parsed again and compared with the expected set of
  variables.
- A value is written in a form that dotenv reads back character for
  character (JSON, backslashes, quotes, multi-line blocks).
- The read, the revision check and the write run in one transaction with
  a row lock. A copy source is read in the same transaction.
- Added the generic revision helper getEnvFieldRevision. application/env
  and compose reuse the existing revisions, so they interoperate with
  application.envUpsert and compose.env.upsert.
- The variable name pattern now lives in one place and the existing
  upsert schemas use it.
- The fake-drizzle-db test helper now supports select ... for update and
  nested with.
Why:
- An AI agent holding an API key has to manage environment variables
  without receiving their values: see the names, set a value, generate
  one on the server, copy between services, rename and remove.
Risks:
- tRPC reserves the name apply, so the procedure is env.applyChanges. The
  REST path /env.apply is pinned through meta.openapi, and
  procedure-access lists the procedure as env.applyChanges.
- The unchanged action, also on a dry run, tells whether a sent or copied
  value equals the current one. The existing upsert endpoints behave the
  same way.
- Names come from the dotenv parse. In a damaged file, where a secret
  spans several lines without quotes, a part of the value can show up as
  a variable name.
- A value that cannot be stored without distortion, and an edit that
  would change how neighbouring lines parse, are rejected with
  BAD_REQUEST and not saved.
- A field that could not be decrypted is neither listed nor edited
  (PRECONDITION_FAILED).
- upsertEnvVariables and the existing upsert endpoints are untouched and
  stay line-based.
Checks:
- Commands and results: bun run typecheck in packages/server and in
  apps/dokploy — no errors; bun run test -- --run on the five new and
  changed test files — 5 files, 76 tests passed; bun run test -- --run
  __test__/security __test__/env — 38 files, 413 tests passed; the full
  bun run test -- --run — 285 files passed, 1 skipped, 1 failed; biome
  check on the 15 touched files — clean.
- Limitations: the failing file __test__/deploy/application.real.test.ts
  (4 tests) needs a running Docker daemon, which is not available here
  (ECONNREFUSED on the Docker socket). The transaction and the row lock
  were only exercised against the in-memory test database; nothing ran
  against a real PostgreSQL or a live panel. The root openapi.json was
  not regenerated.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…e listing

Что:
- `env.keys` не возвращает «имена», похожие на кусок закодированных данных: от 20 символов, без подчёркивания, со строчными, заглавными буквами и цифрами.
- Тест супер-сессии для слепых env-процедур подменяет сервис известных секретов: ответы по API-ключу проходят фильтр, который читает секреты из таблиц, отсутствующих в фальшивой базе этого теста.
Зачем:
- Если блок ключа вставлен в env без кавычек, dotenv читает его base64-строки с `=` на конце как присваивания, и строка секрета попала бы в список имён переменных, единственный ответ, где имена отдаются открыто.
- Тест должен проходить и вместе с фильтром ответов для API-ключей, и без него.
Риски:
- Настоящая переменная с таким необычным именем (20+ символов, смешанный регистр, цифры, без подчёркивания) не попадёт в список; менять её по имени по-прежнему можно.
- Хвост блока короче 20 символов этим правилом не отсекается.
Проверки:
- Команды и результаты: в apps/dokploy `bun run typecheck` - без ошибок; `bun run test -- --run __test__/security/super-session-blind-env.test.ts __test__/env` - 250 passed; `biome check` по изменённым файлам - 0 ошибок.
- Ограничения: на живой панели не проверялось.

What:
- `env.keys` does not return "names" that look like a piece of encoded data: 20 characters or more, no underscore, with lowercase letters, uppercase letters and digits.
- The super session test of the blind env procedures stubs the known secrets service: API key responses go through a filter that reads secrets from tables this test's fake database does not have.
Why:
- When a key block is pasted into env without quotes, dotenv reads its base64 lines ending in `=` as assignments, and a line of the secret would land in the list of variable names, the one response where names are returned in clear.
- The test has to pass both with the API key response filter and without it.
Risks:
- A real variable with such an unusual name (20+ characters, mixed case, digits, no underscore) is left out of the listing; it can still be changed by name.
- A tail of the block shorter than 20 characters is not caught by this rule.
Checks:
- Commands and results: in apps/dokploy `bun run typecheck` - no errors; `bun run test -- --run __test__/security/super-session-blind-env.test.ts __test__/env` - 250 passed; `biome check` on the changed files - 0 errors.
- Limitations: not exercised against a live panel.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@agentHits
agentHits merged commit e91f009 into AgentHits-Dev Oct 12, 2026
7 checks passed
@agentHits
agentHits deleted the feat/blind-env-api branch October 12, 2026 19:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant