diff --git a/CHANGELOG.md b/CHANGELOG.md index 0246fe2..07f7992 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,15 @@ All notable changes to kage are recorded here. The format follows ## [Unreleased] +## [0.3.9] - 2026-07-08 + +### Fixed + +- The Windows build no longer embeds the leakless watchdog binary that Windows Defender flags as `Trojan:Win32/Kepavll!rfn`, which made a fresh `scoop install` fail with a virus warning on `leakless.exe` ([#68](https://github.com/tamnd/kage/issues/68)). + go-rod's launcher imports [leakless](https://github.com/ysmood/leakless), which base64/gzip-embeds a prebuilt helper for every platform and links the Windows one into `kage.exe`. + kage already launches Chrome with leakless disabled, so the helper never ran, only added the flagged bytes. + A `replace` directive now points the package at an API-compatible stub under `third_party/leakless` that carries no embedded binary, dropping about 1.28 MB from the Windows build. + ## [0.3.6] - 2026-06-19 ### Fixed @@ -255,7 +264,8 @@ can browse offline, with every script stripped out. a multi-arch container image on GHCR (Chromium bundled), checksums, SBOMs, and a cosign signature, all cut from one version tag by GoReleaser. -[Unreleased]: https://github.com/tamnd/kage/compare/v0.3.4...HEAD +[Unreleased]: https://github.com/tamnd/kage/compare/v0.3.9...HEAD +[0.3.9]: https://github.com/tamnd/kage/compare/v0.3.8...v0.3.9 [0.3.4]: https://github.com/tamnd/kage/compare/v0.3.3...v0.3.4 [0.3.3]: https://github.com/tamnd/kage/compare/v0.3.2...v0.3.3 [0.3.2]: https://github.com/tamnd/kage/compare/v0.3.1...v0.3.2 diff --git a/docs/content/reference/release-notes.md b/docs/content/reference/release-notes.md index 4e89fb1..a26b83a 100644 --- a/docs/content/reference/release-notes.md +++ b/docs/content/reference/release-notes.md @@ -6,6 +6,12 @@ weight: 40 The authoritative, commit-level history lives in [`CHANGELOG.md`](https://github.com/tamnd/kage/blob/main/CHANGELOG.md) and on the [releases page](https://github.com/tamnd/kage/releases). This page summarises each version. +## v0.3.9 + +A fix for the antivirus warning some Windows users hit when installing kage. + +- **The Windows build no longer ships the leakless helper antivirus flags.** kage renders pages with [go-rod](https://github.com/go-rod/rod), whose launcher pulls in [leakless](https://github.com/ysmood/leakless), a small watchdog that force-kills Chrome if kage exits. leakless carries a prebuilt helper binary for every platform and links the Windows one straight into `kage.exe`. Windows Defender recognises that helper as `Trojan:Win32/Kepavll!rfn` and quarantines it, so a fresh `scoop install` failed with a virus warning on `leakless.exe` ([#68](https://github.com/tamnd/kage/issues/68)). kage already launches Chrome with leakless switched off, so the helper never ran anyway. It is now replaced with a stub that carries no embedded binary, which drops about 1.28 MB from the Windows build and clears the warning. Thanks to John Pywtorak for the report. `go install`, unaffected before, stays clean. + ## v0.3.4 Two community fixes: a clean stop for `kage serve`, and pages with heavy JavaScript that used to be dropped. diff --git a/go.mod b/go.mod index 5a2b0ac..8135f24 100644 --- a/go.mod +++ b/go.mod @@ -54,3 +54,11 @@ require ( golang.org/x/text v0.38.0 // indirect google.golang.org/protobuf v1.34.2 // indirect ) + +// go-rod's launcher imports github.com/ysmood/leakless, which base64/gzip-embeds +// a prebuilt leakless.exe into the Windows build. Antivirus engines flag that +// embedded helper as malware and quarantine kage on install (issue #68). kage +// always launches Chrome with leakless disabled (browser/leakless.go), so the +// guard is dead weight; this replace swaps in an API-compatible stub that +// carries no embedded binary. +replace github.com/ysmood/leakless => ./third_party/leakless diff --git a/go.sum b/go.sum index 4be5fec..15a5bc9 100644 --- a/go.sum +++ b/go.sum @@ -107,9 +107,6 @@ github.com/ysmood/gotrace v0.6.0 h1:SyI1d4jclswLhg7SWTL6os3L1WOKeNn/ZtzVQF8QmdY= github.com/ysmood/gotrace v0.6.0/go.mod h1:TzhIG7nHDry5//eYZDYcTzuJLYQIkykJzCRIo4/dzQM= github.com/ysmood/gson v0.7.3 h1:QFkWbTH8MxyUTKPkVWAENJhxqdBa4lYTQWqZCiLG6kE= github.com/ysmood/gson v0.7.3/go.mod h1:3Kzs5zDl21g5F/BlLTNcuAGAYLKt2lV5G8D1zF3RNmg= -github.com/ysmood/leakless v0.8.0/go.mod h1:R8iAXPRaG97QJwqxs74RdwzcRHT1SWCGTNqY8q0JvMQ= -github.com/ysmood/leakless v0.9.0 h1:qxCG5VirSBvmi3uynXFkcnLMzkphdh3xx5FtrORwDCU= -github.com/ysmood/leakless v0.9.0/go.mod h1:R8iAXPRaG97QJwqxs74RdwzcRHT1SWCGTNqY8q0JvMQ= go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI= golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo= diff --git a/third_party/leakless/go.mod b/third_party/leakless/go.mod new file mode 100644 index 0000000..309d7cf --- /dev/null +++ b/third_party/leakless/go.mod @@ -0,0 +1,3 @@ +module github.com/ysmood/leakless + +go 1.26.4 diff --git a/third_party/leakless/leakless.go b/third_party/leakless/leakless.go new file mode 100644 index 0000000..07c3011 --- /dev/null +++ b/third_party/leakless/leakless.go @@ -0,0 +1,55 @@ +// Package leakless is kage's drop-in replacement for +// github.com/ysmood/leakless, wired in through a replace directive in the root +// go.mod. +// +// The upstream package guards a child process by extracting a small helper +// executable that force-kills the child when the parent dies. It ships that +// helper by base64/gzip-embedding a prebuilt binary for every target +// (bin_amd64_windows.go and friends), so the packed leakless.exe ends up linked +// into any program that imports the package, kage included. Antivirus engines +// flag that embedded Windows helper as malware, so a fresh install of kage got +// quarantined before it ever ran (issue #68). +// +// kage already launches Chrome with leakless disabled (see +// browser/leakless.go), so the guard is never used. This stub keeps the exact +// public surface go-rod's launcher depends on (New, Support, LockPort, and the +// Launcher type's Command/Pid/Err) while carrying no embedded binary, which +// removes the false positive entirely. Support reports no guard is available, +// so go-rod's launcher never takes the leakless path even if a caller asked +// for it. +package leakless + +import "os/exec" + +// Launcher mirrors the upstream type. The channel is left unbuffered and is +// never written to, matching the "may never receive the pid" contract go-rod +// already tolerates. +type Launcher struct { + pid chan int +} + +// New returns a Launcher. It allocates nothing beyond the pid channel. +func New() *Launcher { + return &Launcher{pid: make(chan int)} +} + +// Command builds the command without a guard wrapper. Because Support returns +// false, go-rod never calls this in practice; if some other caller did, running +// the target directly is the correct no-guard behaviour. +func (l *Launcher) Command(name string, arg ...string) *exec.Cmd { + return exec.Command(name, arg...) +} + +// Pid returns the (never-signalled) pid channel. +func (l *Launcher) Pid() chan int { return l.pid } + +// Err returns the guard error, always empty here since there is no guard. +func (l *Launcher) Err() string { return "" } + +// Support reports whether a guard binary is available. It always returns false +// so callers skip leakless entirely. +func Support() bool { return false } + +// LockPort is the cross-process mutex the upstream guard uses to serialise +// extraction. With no guard there is nothing to serialise, so it is a no-op. +func LockPort(port int) func() { return func() {} }