Repository navigation
Releases: evsinev/dc-agent
Release list
1.7.0
New command zip-archive-version: publish an immutable version directory, switch a pointer file and wait for the service's reload — in one call, one curl --fail-with-body (#98).
curl --fail-with-body -H "api-key: $DC_AGENT_KEY" --data-binary @"v0.4.1.zip" \
"https://agents.example.com/dc-agent/zip-archive-version/app-bundle/v0.4.1"Upgrade notes (read before rolling out)
- New
WEB_SERVER_IDLE_TIMEOUT, default10m— the idle timeout of the agent's connector, for every endpoint. Before, Jetty's own default of 30 s applied. Azip-archive-versionconfig is refused unless itswaitTimeoutplus the 1 minute lock wait fits in it; a reverse proxy in front needs the same read timeout for that path (nginxproxy_read_timeout, 60 s by default). - The operator now pins dc-agent-react v1.10.0 (create / view / edit forms for the new command).
- Control-plane create/update responses may carry the new status
INVALIDwith amessage(the field, never its value); the operator answers it with 400. An older operator cannot create the new command; a new operator against an older agent reports it as unreachable for this type only.
zip-archive-version
- A transaction with three outcomes: confirmed (200,
published|present active <version> sha256 <digest>), rolled back (the service's 409/422/503, or 502 — the pointer is back where it was), unknown (504/500 — a retry of the same command is always safe). - A version appears complete or not at all: staged next to its place, fsynced, published by one rename; the pointer is replaced atomically, its previous value kept on disk first, so a rollback survives the agent's own restart. Once the service confirms, the previous value is dropped, so a failed retry of a confirmed version does not roll it back.
- Retries are cheap: contents are compared by a digest of the files (not the ZIP bytes) — a rebuilt archive of the same files is
present; a tag that now carries other files is 409. - Archives are checked before anything is written:
\in names (on the raw bytes), absolute names,./.., duplicates, symlinks and other special entries, encrypted entries;maxUploadBytes(413),maxBytescounted from the bytes actually read,maxEntries; at most two uploads in flight per agent. - Reload call: bounded by
waitTimeoutfor the whole exchange (a service dripping its answer is cut), body capped at 256 bytes, no redirects;${version}is allowed in the path and query only, never in the scheme, host or port. - Files
0644, directories0755, set explicitly — the reading service runs as another user, so the command is not for secrets.reloadHeadersvalues are never logged and are masked in the command list. - Configs are checked on every call right after authorization and when saved through the control plane; readable values:
50mb,10k,5m.
Other fixes
- The operator closes its command-list thread pool (it was only shut down).
GsonApiServlet(control-plane / operator API) is pinned to INFO inlogback.xml: at DEBUG it logged whole create/update bodies, api keys included.
Docs: https://evsinev.github.io/dc-agent/commands/zip-archive-version/
What's Changed
- feat: zip-archive-version — publish a version, switch the pointer and wait for the service in one call by @evsinev in #102
Full Changelog: 1.6.0...1.7.0
1.6.0
Authenticate before anything: every agent task endpoint checks the api-key before it reads the request body, builds a path or touches the disk — and every refusal looks the same.
Upgrade notes (read before rolling out)
CONTROL_PLANE_ENABLED=truenow requires your ownCONTROL_PLANE_TOKEN. The agent refuses to start when it is empty or the published defaultREPLACE_THIS_TEST_CONTROL_PLANE_TOKEN. With the control plane off nothing changes. The token is now masked in the startup log.- The config
typemust match the endpoint (ZIP_ARCHIVE,ZIP_DIRS,SAVE_ARTIFACT,FETCH_URL,JAR,WAR,NODE,DOCKER); another type gets 401. A config withouttypestill works, with a "has no type" warning in the agent log — add the field. Watch for an oldnodeconfig saved through the operator asJAR. Before upgrading, on each host:grep -H '"type"\|^type:' $CONFIG_DIR/*and compare with the endpoint each command is called through. - Command names outside
0-9 a-z A-Z . _ -or containing..get 401 (the operator never created such names). zip-dirs: the sub-path segments.and..are rejected.
Security fixes
zip-dirsescaped itsdir: Jetty 12 passes a raw..through to the servlet, so/zip-dirs/{name}/..extracted abovedir; a link insidedircould lead out too. Now./..are refused and the target must stay inside the canonicaldir. Also, an anonymous request created directories underdir— directories are now created only after authorization.- A key of one command worked on another endpoint: keys belong to a config name and
typewas never checked, so asave-artifactkey could unpack an arbitrary archive through/zip-archive/{same name}into the samedir(likewisejar/war/node). - Name enumeration and path disclosure: an unknown command name returned 500 with the absolute
CONFIG_DIRpath; empty or broken configs leaked parser messages. Every refusal — no key, wrong key, unknown command, broken config, another type — is now the same 401Unauthorized; the reason goes to the agent log only, never a key. - Control plane behind the published default token let anyone create a command with their own key and deploy through it — see the upgrade note.
Authorizationheader: onlyBasicis accepted (aBearervalue is no longer Base64-decoded), bad Base64 is a 401 instead of a 500, and a password may contain:.
Docs
- The false "a config without
apiKeysaccepts any request" is gone (the code already refused every request); security, configuration, installation and every command page describe the new checks, and each example config carries itstype.
Known issues: #99 (a misspelled type value reads as no type), #100 (Jetty 12 rejects /fetch-url/https://... with 400 — pre-existing).
Full docs: https://evsinev.github.io/dc-agent/
What's Changed
Full Changelog: 1.5.0...1.6.0
1.5.0
Run containers as a dedicated user, safely: securityContext for dc-docker.yml, a hardened agent file system, strict config parsing, and every open dependency CVE closed.
Upgrade notes (read before rolling out)
- Unknown or duplicate keys in
dc-docker.ymlare now errors (#75). A typo such asreadOnly:orreadOnlyRootFilesysem:used to be ignored silently, which turned a protection off. After upgrading, runDOCKER_CHECKfor every service: each stray key is listed with its path and the closest known name. nameindc-docker.ymlmust be a docker container name ([a-zA-Z0-9][a-zA-Z0-9_.-]*). Any other name already faileddocker run --name=.TEMP_DIRmust be on a path only root can change. A sticky/tmpis fine. The agent refuses an unsafeTEMP_DIRbefore extracting anything.- New agent parameter
CONTAINER_RUNTIME(podmanby default, ordocker). It only matters forsecurityContext.passwdEntry. Setdockeron hosts with real Docker before using it; the agent refuses a mismatch withdocker --version. save-artifact: thex-dc-agent-file-extensionheader must be a plain extension (e.g.tar.gz). A missing or blank header still falls back to the configuredextension.
Containers as a user (securityContext)
runAsUser/runAsGroup→--user U:GreadOnlyRootFilesystem→--read-onlyallowPrivilegeEscalation: false→--security-opt no-new-privilegesdirectoryOrCreatevolumes getowner(runAsor{user, group}) andmode. These are applied behind a write-path preflight that checks every path the root agent writes, for CHECK and PUSH alike (#74).passwdEntrygives a UID that is not in the image a passwd entry, the way imagekit'sruntime.passwd_entrydoes, so Java gets a realuser.nameand a writableuser.home. Podman receives its own--passwd-entry; with docker the agent mounts a small generatedcontainer-passwdread-only over/etc/passwd. The host's/etc/passwdis never used.tmpfs→--tmpfs <path>[:<options>], e.g. a writable/tmpunder--read-only.
Security fixes
- Local privilege escalation via a predictable work dir: tasks were extracted into
/tmp/docker-<name>-<ms>, created non-exclusively, so a local user could pre-create it and swapdc-docker.ymlor steer root's writes. The work dir is now created exclusively with mode0700and a random suffix, under aTEMP_DIRwhose whole path is checked first. - Deleting trees no longer follows symbolic links (#76). On Linux the deletion goes through
SecureDirectoryStream, relative to open directory descriptors. save-artifact: the api-key is now checked before anything touches the disk; before, a request without a key made the root agent create arbitrary directories. The stored file must stay insidedir, and the header extension is validated.- Operator: HTML auto-escaping in the FreeMarker templates (reflected/stored XSS).
dc-docker.ymlnamecan no longer place the service directory outsideSERVICES_DEFINITION_DIR.
Dependencies
- FreeMarker 2.3.35 (CVE-2026-84939), Jetty 12.1.13 (CVE-2026-19203, CVE-2026-19204), logback 1.6.4 (CVE-2026-19880), plus slf4j, jgit, guava, handlebars and snakeyaml-engine updates.
- Docs site on pnpm 10 with
minimumReleaseAge; all website dependency alerts closed. - Build: Maven 3.9.16 through a script-only wrapper with a checksum; surefire pinned. CI: CodeQL Action v4.
Full docs: https://evsinev.github.io/dc-agent/
1.4.0
Honest GC / host diagnostics: makes the operator's GC report tell the truth and adds the signals needed to tell an idle JVM, a swapping host, and a real leak apart from noise.
Fixes (were misleading)
- Live set now counts heap pools only — no longer inflated by Metaspace / CodeHeap.
- Uptime and process CPU time are separate, clearly labelled figures (CPU time was mislabelled as uptime).
- MemAvailable added; MemFree is de-emphasised (it excludes page cache and is not a pressure signal on DB hosts).
New signals
- Linux
/proc: MemAvailable, swap-in/out rate, and how much of the JVM is swapped out. - Collectors in use, allocation rate, interval between collections, a full-GC / old-gen series, max pause over the last hour, sub-millisecond pause count.
Interpretation
GcDoctorgains Idle / host-swapping / heap-in-swap / low-MemAvailable rules; an idle-but-healthy JVM now reports OK with the idle note leading.- The "copy for LLM" payload is rewritten into labelled sections with explicit units and caveats.
- Byte sizes now use binary units (KiB/MiB/GiB).
UI
- Bundles dc-agent-react v1.9.0, which renders all the new fields in the agent view.
Wire-compatible: new fields are optional; older agents on the fleet report n/a, never a fabricated 0.
1.3.1
1.2.0
What's Changed
- Potential fix for code scanning alert no. 641: Log Injection by @evsinev in #26
- Potential fix for code scanning alert no. 65: Local information disclosure in a temporary directory by @evsinev in #27
- Potential fix for code scanning alert no. 64: Local information disclosure in a temporary directory by @evsinev in #28
- feat(gc): per-pause GC diagnostics, verdict, and copy-for-LLM payload by @evsinev in #29
New Contributors
Full Changelog: 1.1.0...1.2.0
1.1.0
1.1.0
✨ Features
- System & JVM metrics — new per-agent metrics endpoint (
SystemInfoRequest/SystemInfoResponse,TSystemInfo), with aCpuLoadSamplerandSystemInfoCollectoron the agent and operator-side aggregation (AgentMetricsMapper,TAgentMetrics). - Enriched command DTOs — operator command details now carry service state resolved server-side (
TCommandDetail,TCommandInfo).
🔒 Security & Dependencies
- Replaced OkHttp with
java.net.http.HttpClientto drop the transitive Kotlin CVE-2026-53914. - Switched to the maintained
net.i2p.crypto:eddsafork to fix CVE-2020-36843 (also enables reading ed25519 SSH keys from the fat jar). - Fixed the OWASP suppression schema so CVE suppressions apply correctly in CI.
- Suppressed the by-design SSRF alert in
FetchUrlServletvia a CodeQL inline comment. - Bumped
com.github.jknack:handlebars4.5.0 → 4.5.2 andorg.assertj:assertj-core3.27.6 → 3.27.7.
🔧 CI
- Cache the NVD database weekly so the OWASP scan stops re-downloading it on every run.
🧩 Compatibility
- Operator UI pin bumped to dc-agent-react v1.5.0.
Full Changelog: 1.0.13...1.1.0
1.0.13
1.0.13 — Security & CI hardening
Dependency CVE bumps, new supply-chain scanning workflows, and the operator UI
pinned to dc-agent-react v1.3.0.
Changes
- Bumped dependencies for CVEs: Jetty
12.0.20→12.0.34, JGit
7.1.0→7.7.0, Handlebars; added OWASP suppressions. - Added CI security scanning: CodeQL, OWASP Dependency-Check, SpotBugs, and
Dependabot; plus OpenSSF Scorecard and SonarCloud workflows. - Fixed Trivy / OWASP / SpotBugs / Scorecard workflow failures.
- Pinned the operator UI to dc-agent-react
v1.3.0(frontend.version).
Full Changelog: 1.0.12...1.0.13
1.0.12
Remove BouncyCastle
- Dropped unused
pgpainless-sopPGP code (PgpSignatureCheck,PgpTest) and removed the BouncyCastle dependency from all modules. - Pinned Apache MINA sshd to JDK-native crypto (
org.apache.sshd.security.provider.BC.enabled=false) as a defensive guard against SSH key exchange breaking if BC is ever reintroduced. - Improved git pull error message to surface the actual cause instead of the stale
GIT_SSHhint.
Full Changelog: 1.0.11...1.0.12
1.0.11
dc-agent 1.0.11
This release delivers the backend side of Commands management for the dc-operator console — list, details, and fully-typed create/edit over the control plane with write-only API keys — plus periodic agent-config backups on the operator, a mirror mode for zip-dirs, a local verification harness, and a git pull fix for non-SSH remotes. The bundled operator UI is updated to dc-agent-react v1.2.0.
Commands over the control plane
- New
command/listendpoint on the agent's control plane, with a matching operator API. Commands are discovered by scanningCONFIG_DIRfor*.json/*.ymlconfig files (the docker commanddc-docker.*is excluded); the name comes from the file name, and the type is taken from an explicittypefield or inferred from the present fields. - Command details now return all configuration parameters, with API keys masked to stable ids.
- Create and edit are fully typed: one request DTO per command type (
WAR,JAR,NODE,SAVE_ARTIFACT,ZIP_ARCHIVE,ZIP_DIRS,FETCH_URL,DOCKER), serialized from the finished config model — no hand-built JSON. API keys are write-only: the client sends masked ids to keep plus new secrets to add, and the agent merges them against the on-disk map. A server-sideCommandValidatormirrors the UI validation rules. - Together these power the new Commands screens in the React console.
Agent config backups
- The operator now periodically backs up every agent's config files over the control plane, archiving them locally — one subdirectory per agent, each backup a
<agent>-yyyy-MM-dd-HH-mm-ss.zip. Unchanged snapshots are skipped via a stable content hash, only the last N archives are kept, and each run is logged to.last.txt. - New operator startup parameters:
DC_AGENT_BACKUP_INTERVAL(ISO-8601 duration, defaultPT1H),DC_AGENT_BACKUP_DIR, andDC_AGENT_BACKUP_KEEP(default5).
zip-dirs: mirror mode
- New
deleteoption (defaultfalse). Withdelete: true, the target directory exactly mirrors the uploaded archive: after extraction, every file under the resolved target dir that is not in the ZIP is removed, along with directories left empty. This is destructive by design — it applies to the whole resolved target dir — and is documented with an explicit caution in the docs.
Fixes
- git pull no longer throws
ClassCastExceptionon non-SSH remotes. The publickey session factory is now applied only toSshTransport, sofile://andhttp(s)config-repo remotes work as expected.
Operator
- New
/infoendpoint reporting the operator's build version from the jar manifest (Implementation-Version, set by theoperator-shadedprofile; falls back todevwhen running unshaded). This powers the new version footer in the console. - Bundled UI pin bumped to dc-agent-react v1.2.0 (SHA-256 verified): Commands screens, renamed navigation (Applications / Agents / Services / Commands), reworked error handling, and the frontend/backend version footer.
Developer experience
- New
verify/harness for a repeatable local run of the whole system — dc-agent, dc-operator, and the React UI wired together with test config — to manually verify the UI before cutting a release. Two frontend modes:separate(Rsbuild dev server on :3000 proxying the API) andbundled(UI baked into the operator jar, mirroring a real release). - New
frontend-localMaven profile builds the operator with a locally builtdc-agent-reactdist instead of downloading the pinned GitHub release:./mvnw -pl dc-agent-operator -am -Pfrontend-local,operator-shaded package.
Documentation
- Documented the
/app-statusendpoint: guarded by its own bearer token (APP_STATUS_TOKEN, separate from the control-plane token), response fields (appInstanceName,appVersion,uptimeMs, …), and thematch-all/host/{h}/instance/{i}/port/{p}variant that returns412on mismatch — handy as a load-balancer or deploy sanity check. - Documented the operator's
agent/listendpoint that powers the dc-agent list screen, and updated the architecture page with the per-host token channels.
Full changelog: 1.0.10...1.0.11