Skip to content

feat(secrets): add secrets.env loader with op:// support#35

Merged
avelino merged 3 commits into
mainfrom
avelino/issue-34
May 21, 2026
Merged

feat(secrets): add secrets.env loader with op:// support#35
avelino merged 3 commits into
mainfrom
avelino/issue-34

Conversation

@avelino
Copy link
Copy Markdown
Owner

@avelino avelino commented May 21, 2026

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

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>
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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-secrets crate to load and cache a secrets.env file (0600 posture, dotenv-ish parsing, optional op read resolution).
  • Wires secrets loading into daemon startup + SIGHUP reload, adds audit events (secrets_loaded / secrets_refused), and extends dotagent doctor to report secrets file status.
  • Updates Telegram notifier ${VAR} interpolation to consult the secrets store first (with std::env fallback) 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.

Comment thread crates/dotagent-secrets/src/lib.rs
Comment thread crates/dotagent-secrets/src/lib.rs
Comment thread crates/dotagent-secrets/src/lib.rs Outdated
Comment thread crates/dotagent-secrets/src/lib.rs
Comment thread crates/dotagent-secrets/src/lib.rs
Comment thread crates/dotagent/src/commands/mod.rs Outdated
Comment thread docs/concepts/secrets.md Outdated
Comment thread docs/concepts/secrets.md Outdated
Comment thread crates/dotagent/src/commands/mod.rs Outdated
Comment thread crates/dotagent/src/commands/daemon.rs Outdated
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>
@avelino avelino merged commit 3e9f374 into main May 21, 2026
5 checks passed
@avelino avelino deleted the avelino/issue-34 branch May 21, 2026 11:18
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.

Add daemon-level secrets loader (~/.config/dotagent/secrets.env)

2 participants