feat(secrets): add secrets.env loader with op:// support#35
Merged
Conversation
Notifier configs like `bot_token = "${TELEGRAM_BOT_TOKEN}"` (PR #33)
only worked when the var was wired into the launchd plist or systemd
unit, which leaks via `launchctl print` and is invisible from the
manifest.
New `dotagent-secrets` crate loads `~/.config/dotagent/secrets.env`
at startup, enforces mode 0600, parses dotenv conventions (`export`
prefix, double/single quotes with escapes), and resolves
`op://vault/item/field` via `op read` so 1Password rotation works
without a config edit. Telegram `expand_env` consults the store
first with `std::env` fallback. SIGHUP reload drops the previous
store on load failure so a rotated token never gets shadowed.
`dotagent doctor` reports presence, mode, and unresolved references.
Closes #34
Signed-off-by: Avelino <31996+avelino@users.noreply.github.com>
Signed-off-by: Avelino <31996+avelino@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
Adds a daemon-level secrets loading mechanism so notifier configs can safely reference ${VAR} without requiring secrets to be injected into launchd/systemd unit environments, including support for resolving op://... references via the 1Password CLI.
Changes:
- Introduces new
dotagent-secretscrate to load and cache asecrets.envfile (0600 posture, dotenv-ish parsing, optionalop readresolution). - Wires secrets loading into daemon startup + SIGHUP reload, adds audit events (
secrets_loaded/secrets_refused), and extendsdotagent doctorto report secrets file status. - Updates Telegram notifier
${VAR}interpolation to consult the secrets store first (withstd::envfallback) and adds/updates related documentation.
Reviewed changes
Copilot reviewed 18 out of 19 changed files in this pull request and generated 12 comments.
Show a summary per file
| File | Description |
|---|---|
| docs/SUMMARY.md | Adds the new Secrets concept page to the docs sidebar. |
| docs/reference/paths.md | Documents secrets.env path and new audit events. |
| docs/reference/agent-spec.md | Documents ${VAR} resolution behavior for notifier config strings. |
| docs/guides/config-reference.md | Adds [secrets] file config override documentation. |
| docs/concepts/secrets.md | New end-user documentation for secrets file format, permissions, reload, and auditing. |
| docs/concepts/notifications.md | Updates Telegram docs to reference secrets store resolution. |
| crates/dotagent/src/commands/mod.rs | Extends doctor to report secrets file status. |
| crates/dotagent/src/commands/daemon.rs | Loads secrets at startup and on SIGHUP; emits audit events; resolves secrets path. |
| crates/dotagent/Cargo.toml | Adds dotagent-secrets dependency. |
| crates/dotagent-state/src/paths.rs | Adds secrets_file() path resolver with env override. |
| crates/dotagent-secrets/src/lib.rs | Implements secrets file parsing, permission checks, op:// resolution, and global store. |
| crates/dotagent-secrets/Cargo.toml | New crate manifest. |
| crates/dotagent-notify/src/telegram.rs | Switches ${VAR} lookup to dotagent_secrets::lookup. |
| crates/dotagent-notify/Cargo.toml | Adds dotagent-secrets dependency. |
| crates/dotagent-core/src/lib.rs | Re-exports SecretsConfig. |
| crates/dotagent-core/src/config.rs | Adds [secrets] config struct and parsing tests. |
| crates/dotagent-core/src/audit.rs | Adds SecretsLoaded / SecretsRefused audit events and severities. |
| Cargo.toml | Adds dotagent-secrets workspace member and dependency. |
| Cargo.lock | Locks new crate dependencies. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Copilot review on PR #35 flagged inconsistent quote handling (`KEY="v" trailing` accepted silently while `KEY='v' ` rejected trailing whitespace), relative paths in `DOTAGENT_SECRETS_FILE` and `[secrets].file` resolving against unpredictable working directories under launchd/systemd, and doc claims that "keys are never logged" while runtime warnings include them. New `ensure_trailing_whitespace` helper makes both quote styles require whitespace-only after the close quote. Path resolver and `[secrets].file` ignore non-absolute values with a `tracing` warning and fall back to the default. Mode error now says "owner-only" instead of literal 0600, matching the actual rule (`mode & 0o077 == 0`). Docs separate "values never leak" from "key names may appear in warns" and the audit payload table picks up `unresolved_references`. Signed-off-by: Avelino <31996+avelino@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Notifier configs like
bot_token = "${TELEGRAM_BOT_TOKEN}"(PR #33) only worked when the var was wired into the launchd plist or systemd unit, which leaks vialaunchctl printand is invisible from the manifest.New
dotagent-secretscrate loads~/.config/dotagent/secrets.envat startup, enforces mode 0600, parses dotenv conventions (exportprefix, double/single quotes with escapes), and resolvesop://vault/item/fieldviaop readso 1Password rotation works without a config edit. Telegramexpand_envconsults the store first withstd::envfallback. SIGHUP reload drops the previous store on load failure so a rotated token never gets shadowed.dotagent doctorreports presence, mode, and unresolved references.Closes #34