3 Commits
Author SHA1 Message Date
e48b4d41da fix(build): normalize dash → underscore in AGENTS / PLATFORMS_INCLUDE / EXCLUDE (#1033)
Build tags use underscores (`no_foo_bar`) but ALL_AGENTS / ALL_PLATFORMS
elements are bare names. When a plugin's registered name contains a
dash (e.g. an agent named `foo-bar` whose build tag is `no_foo_bar`),
the user-facing flag value `AGENTS=foo-bar` was passed through
unchanged. The filter-out then failed to match `foo_bar` in the ALL
list, so the agent was *added* to the exclusion set — i.e. the build
silently dropped the very plugin the user asked to include.

Introduce a tiny `_normalize` define that runs the comma-split and a
dash→underscore fold in one step, and use it everywhere user-supplied
plugin lists feed into filter-out / addprefix. Behaviour for existing
underscore-or-bare names is unchanged.

No new ALL_AGENTS / ALL_PLATFORMS entries are added by this commit;
the fix is purely about input normalization so downstream additions
with hyphenated names work as the help text suggests.

Co-authored-by: Shuchao Shao <shaoshch@yonyou.com>
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-09-27 20:16:28 +08:00
d484ae64fc feat(core): gate /commands addexec and /cron addexec behind admin_from (#1036)
Both /commands and /cron currently have a single coarse-grained
privilege check: the cmdID is or is not in privilegedCommands. Neither
is listed there, so all subcommands run for any user — including
`addexec`, which registers a custom command that shells out, or
schedules one that shells out on a cron. A non-admin user who can send
slash commands can effectively install arbitrary shell payloads for
anyone else to trigger.

(executeCustomCommand already gates Exec-bearing custom commands on
admin_from after they have been registered, so executing such a payload
also requires admin — but the registration itself shouldn't be open to
non-admins either. This PR closes the registration side.)

Introduce isPrivilegedCommandInvocation(cmdID, args) which:

  - falls through to the existing privilegedCommands map for the
    static cases (/shell, /show, /dir, /restart, /upgrade, /web, /diff)
  - additionally returns true for `/commands addexec ...` and
    `/cron addexec ...` (subcommand names matched via matchSubCommand
    so prefix forms like `/cron addex` and case variants are covered)
  - returns false for every other subcommand of /commands and /cron
    (list, add, del, etc. stay non-privileged)

handleCommand swaps the privilegedCommands[cmdID] check for the new
helper. No other privilege paths change.

Co-authored-by: Shuchao Shao <shaoshch@yonyou.com>
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-08-14 08:35:35 +08:00
c53f5450ee feat(daemon): harden service-file env capture + add EnvDiscoverer plugin hook (#1034)
* feat(daemon): harden service-file writes and capture config.toml ${ENV}

Three independent improvements to the service-file install path so
operators can store API keys / tokens in config.toml using ${ENV}
placeholders and have them work with the installed daemon, without
those values being world-readable on disk.

1. captureConfigEnvPlaceholders: during `daemon install`, scan the
   target config.toml for ${VAR_NAME} placeholders. For every match
   that is set in the current process environment, copy the value
   into the EnvExtra map so the rendered launchd plist / systemd unit
   / Windows task script carries it. Without this, the daemon
   process starts with empty strings for placeholder values and
   fails to authenticate to any platform.

2. Tighten service-file permissions to 0600. Both the WriteFile mode
   and an explicit os.Chmod after write — the Chmod is required for
   the reinstall path because WriteFile only applies perm on create,
   so a 0644 file left by a previous cc-connect version would keep
   its old permissions in place. Applies to launchd plist, systemd
   unit, and Windows .ps1 script (the latter as a defense-in-depth
   layer; real Windows access control lives in the ACL).

3. EnvExtra hardening in every renderer:
   - drop entries whose key fails POSIX-identifier validation
   - drop entries whose value is empty
   - launchd: XML-escape both keys and values; reserve PATH /
     CC_LOG_FILE / CC_LOG_MAX_SIZE so EnvExtra can't override the
     template-owned keys
   - systemd: backslash- and quote-escape values for the
     `Environment="K=V"` form per systemd.exec(5)

Add --no-capture-secrets / CC_DAEMON_NO_CAPTURE_SECRETS=1 opt-out for
operators who'd rather inject secrets via keychain, secret-tool, or
systemd EnvironmentFile= and keep the service file token-free.

runSystemctl becomes a var so per-platform tests can stub it without
needing a real systemctl on the host.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* feat(daemon): add EnvDiscoverer plugin hook for install-time env capture

Generalises captureDaemonEnv() with a small plugin registry:

    type EnvDiscoverer func() (map[string]string, error)
    daemon.RegisterEnvDiscoverer(d)
    daemon.ResetEnvDiscoverers()    // tests only

Resolve() invokes every registered discoverer (unless NoCaptureSecrets
is set) and merges the result into EnvExtra after the proxy-key capture
and config.toml ${ENV} placeholder scan. Discoverers run in
registration order; the returned map is filtered for valid POSIX env
names and non-empty values before reaching any renderer.

Discoverer errors are logged at WARN level and never fail install —
matching the rest of the install path's tolerance posture.

Use case: lets a downstream / plugin agent contribute extra env vars
the service file should carry, without daemon needing to know about
that agent. Without the hook, the only ways to extend capture are (a)
pre-populate EnvExtra in the caller — verbose at every install site —
or (b) edit daemon/manager.go to import the new package — which couples
daemon to that package and breaks the no-agent-deps boundary.

The isValidEnvName helper moves out of manager.go into the new
env_extension.go alongside the registry, since the validator is shared
between the renderers and the discoverer-merge code.

Comment tweaks in launchd.go / systemd.go acknowledge that discoverer
output is another source of captured secrets justifying the 0600 perms.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix(daemon): add non-linux linger check stub

---------

Co-authored-by: Shuchao Shao <shaoshch@yonyou.com>
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-11 00:14:23 +08:00