Releases: Ghostwritten/harpoon
v2.0.3
Harpoon v2.0.3 β Release Notes
Released: 2026-03-14
Summary
v2.0.3 is a quality and security hardening release. It contains no new user-facing
features but resolves all critical and high-priority issues identified in the v2.0.x
code review, along with targeted refactoring that eliminates code duplication and
improves long-term maintainability.
Bug Fixes
readImageList reported wrong line numbers after deduplication (cmd/hpn/common.go)
Before: Image validation ran on the deduplicated slice, so the reported line number
referred to the post-dedup position rather than the original line in the file.
After: Validation runs on the raw (pre-dedup) lines with original line numbers
tracked per entry. Error messages now correctly reference the actual line in the
image list file.
Skopeo Pull() discarded downloaded data immediately (internal/runtime/skopeo.go)
Before: Pull() copied the image to a /tmp directory and then immediately called
defer os.RemoveAll(tempDir), making the entire operation a no-op.
After: Pull() copies the image directly into the first available daemon store
(Docker via docker-daemon:, or Podman via containers-storage:). If neither daemon
is available a clear error is returned directing the user to hpn save instead.
Duplicate --output flag binding in save command (cmd/hpn/save.go)
Before: Both --path and --output were bound to the same variable with identical
defaults. Cobra resolved them silently, producing confusing behaviour when both were
supplied.
After: --path is the canonical flag. --output is marked deprecated via Cobra's
MarkDeprecated so users receive an upgrade notice while backwards compatibility is
preserved during a transition period.
trimWhitespace reimplemented strings.TrimSpace (internal/runtime/checksum.go)
Before: A private trimWhitespace function with a manual loop existed alongside a
usage of strings.TrimSpace in the same file.
After: The redundant function is deleted; all callers use strings.TrimSpace
directly.
verifyChecksum duplicated VerifyTarChecksum (internal/runtime/checksum.go)
Before: Two nearly-identical functions existed (VerifyTarChecksum exported,
verifyChecksum unexported). The unexported version was never called outside the file.
After: The dead function is removed. All callers use the single exported
VerifyTarChecksum.
Nerdctl added --insecure-registry to every pull and push (internal/runtime/nerdctl.go)
Before: Pull() and Push() unconditionally appended --insecure-registry,
silently disabling TLS verification for all nerdctl operations.
After: The flag is removed from the defaults. Users who need it can pass it via
ExtraArgs (e.g. hpn pull -f list.txt -- --insecure-registry).
Security Fixes
Password exposed in process table via -p flag (all runtimes)
Severity: High
Before: When a password was supplied programmatically (e.g. from an env var or
--password flag), it was passed as a -p <value> argument to the underlying CLI
command. This made the credential visible to all local users via ps.
After: All runtimes (docker, podman, nerdctl, skopeo) now unconditionally
use --password-stdin and pipe the password to the command's stdin. The credential
never appears in the process table.
Predictable /tmp paths in Skopeo enabled TOCTOU / symlink attacks (internal/runtime/skopeo.go)
Severity: High
Before: Temporary directories were constructed with deterministic names such as
/tmp/skopeo-pull-<image> and /tmp/skopeo-oci-<image>-<unix-timestamp>. A local
attacker could pre-create these paths as symlinks before the process ran.
After: All temporary directories are created with os.MkdirTemp("", "hpn-skopeo-*")
which produces a cryptographically random suffix and is atomic with the directory
creation, eliminating the TOCTOU window.
Save directory created world-readable (cmd/hpn/save.go)
Severity: Medium
Before: os.MkdirAll(saveDir, 0755) allowed other local users to read image
archives stored in the save directory.
After: os.MkdirAll(saveDir, 0700) β only the owner can access the directory.
Checksum files written world-readable (internal/runtime/checksum.go)
Severity: Low
Before: .sha256 files were written with 0644 permissions.
After: Written with 0600 (owner read/write only).
Auth directory created with broad permissions (internal/runtime/auth.go)
Severity: Low
Before: os.MkdirAll(dir, 0755) for the credential config directory.
After: os.MkdirAll(dir, 0700) β credential directories must be private.
Interface Changes
ContainerRuntime.Load() now accepts an imageName parameter
// Before
Load(ctx context.Context, tarPath string) error
// After
Load(ctx context.Context, tarPath string, imageName string) errorDocker, Podman, and Nerdctl implementations ignore imageName (they parse the image
reference from the tar manifest automatically). The Skopeo implementation uses it as
the destination reference in skopeo copy docker-archive:tarPath docker-daemon:imageName
so it no longer has to fall back to calling docker load directly.
New ContainerRuntime.Logout() method
Logout(ctx context.Context, registry string) errorAll four runtimes implement logout <registry>. A new hpn logout <registry> command
is available.
New Features
hpn logout command
hpn logout registry.example.com
Logs out from the specified registry using the active container runtime.
Refactoring
Generic concurrent worker pool (cmd/hpn/worker.go)
The goroutine+channel worker pool pattern was duplicated verbatim across pull.go,
push.go, save.go, and load.go (~50 lines Γ 4 = ~200 lines of duplication).
A single generic function now serves all four commands:
func runWorkerPool[J any](ctx context.Context, jobs []J, maxWorkers int, fn func(J) error) []workerResult[J]The implementation honours context cancellation: when the context is done, remaining
queued jobs are marked with ctx.Err() instead of spawning new sub-processes.
Retry-with-backoff utility (cmd/hpn/retry.go)
pullImageWithRetry and pushImageWithRetry were identical 40-line loops.
Extracted to:
func retryWithBackoff(ctx context.Context, fn func() error, cfg RetryConfig) errorCorrectly uses select { case <-ctx.Done(): ... case <-time.After(delay): } so that a
Ctrl+C during a backoff sleep returns immediately instead of blocking for up to 30 s.
Shared runtime helpers extracted to docker.go
Two helper functions shared by all four runtimes are now defined once in docker.go:
applyProxyEnv(cmd, proxy)β setshttp_proxy/https_proxywhen proxy is enabledbuildErrMsg(base, stderr, debug)β constructs error messages from stderr output
~20 occurrences of the raw debug-output boilerplate are replaced with calls to these
helpers, reducing per-runtime complexity significantly.
pull.go, push.go, save.go, load.go refactored
Each command now:
- Calls
runWorkerPoolinstead of maintaining its own goroutine pool - Calls
retryWithBackoffinstead of its own retry loop - Derives per-operation contexts from
cmd.Context()so SIGINT propagates correctly
CI/CD Improvements
Race detector added to CI (.github/workflows/test.yml)
go test -race ./... is now a required check for all PRs.
golangci-lint added to CI
The existing .golangci.yml configuration was never invoked in CI. The workflow now
runs golangci-lint using the official action on every push and PR.
actions/setup-go upgraded to v5 with caching enabled
Signal Handling
SIGINT / SIGTERM cancels the active operation (cmd/hpn/main.go)
Before: Pressing Ctrl+C sent SIGINT to hpn, but the process would continue running
until the current sub-process (docker pull, etc.) finished.
After: signal.NotifyContext creates a top-level context that is cancelled
immediately on SIGINT or SIGTERM. This context is threaded through
cobra.ExecuteContext β cmd.Context() β runWorkerPool β retryWithBackoff β each context.WithTimeout. In-progress sub-processes receive a cancelled context on their
next retry boundary, and queued jobs are skipped immediately.
Files Changed
| File | Change type |
|---|---|
internal/runtime/checksum.go |
Bug fix, dead-code removal, permission fix |
internal/runtime/interface.go |
Interface change (Load, Logout) |
internal/runtime/docker.go |
Security fix, interface update, helper extraction |
internal/runtime/podman.go |
Security fix, interface update |
internal/runtime/nerdctl.go |
Security fix, interface update, unconditional flag removal |
internal/runtime/skopeo.go |
Bug fix, security fix, interface update, os.MkdirTemp |
internal/runtime/auth.go |
Permission fix |
cmd/hpn/common.go |
Bug fix (line numbers), restore validateImageList |
cmd/hpn/save.go |
Flag dedup fix, permission fix, worker pool adoption |
cmd/hpn/load.go |
Worker pool adoption, imageName parameter |
cmd/hpn/pull.go |
Worker pool + retry adoption, context propagation |
cmd/hpn/push.go |
Worker pool + retry adoption, context propagation |
cmd/hpn/main.go |
SIGINT / SIGTERM handling |
cmd/hpn/root.go |
Register logoutCmd |
cmd/hpn/worker.go |
New β generic worker pool |
cmd/hpn/retry.go |
New β retry-with-backoff utility |
cmd/hpn/logout.go |
New β hpn logout command |
cmd/hpn/worker_test.go |
New β worker pool tests (8 cases) |
cmd/hpn/retry_test.go |
New β retry tests (7 cases) |
.github/workflows/test.yml |
Race detector, golangci-lint, setup-go v5 |
Test Results
Run:
go test -v -race ./...| Go 1.25.0 darwin/ar...
v2.0.2
Harpoon v2.0.2 Release Notes
Release Date: February 13, 2025
Version: v2.0.2
What's New in v2.0.2
This release focuses on code quality and professionalism: consistent exit codes for scripting, clearer usage errors, a contributing guide, additional tests, and CI improvements. It also adds the project logo and updates .gitignore for the default save/load directory.
Exit codes and usage errors
- Exit codes: The CLI now uses exit code 0 (success), 1 (runtime or operational error), and 2 (usage error). Scripts can rely on exit code 2 when required flags are missing or arguments are invalid.
- Usage errors: When you omit required flags (e.g.
-f,--chart,--registry), the error message no longer appends ": usage"; only the clear message is shown (e.g. "missing required --file parameter").
Contributing and code quality
- CONTRIBUTING.md: New guide covering clone, build (
go build ./cmd/hpn), tests (go test ./...),go vet, code style (gofmt, English comments), and PR process. Exit code and logging conventions are documented. - Package doc comments:
pkg/errorsandinternal/runtimenow have package-level doc comments forgo doc. - Cmd tests: Table-driven tests for
readImageList(empty file, comments, valid lines, trim/skip) andmergeExtraArgs(nil/empty, config only, passthrough, order) incmd/hpn/common_test.go. - CI: The test workflow now runs
go mod verify, ago mod tidycheck, andgo vet ./....
Other code changes: Subcommand registration is centralized in root.go. All Go comments in runtime packages are in English.
Project logo
- New project logo logo.png at the repository root, used in the README.
Other
- .gitignore: Added
images/so the default save/load output directory is not committed.
Download
Download the latest release from GitHub Releases:
- Linux AMD64: hpn-linux-amd64
- Linux ARM64: hpn-linux-arm64
- macOS Intel: hpn-darwin-amd64
- macOS Apple Silicon: hpn-darwin-arm64
- Windows AMD64: hpn-windows-amd64.exe
For the full changelog, see Changelog.
v2.0.1
Harpoon v2.0.1 Release Notes
Release Date: February 13, 2025
Version: v2.0.1
What's New in v2.0.1
This release adds three new subcommands (list-images, ls, rmi), a Helm chart image extraction pipeline, and improved filtering for chart-derived image lists.
New Features
list-images β Extract image list from Helm charts
Get a list of container images from a Helm chart (remote or local) for use with hpn pull -f or hpn push -f.
Requirements: Helm CLI must be installed.
Examples:
# From a remote chart
hpn list-images --chart bitnami/nginx --version 15.0.0 -o images.txt
hpn pull -f images.txt
# From a local chart directory or .tgz
hpn list-images --chart ./mychart -o images.txt
hpn list-images --chart ./mychart.tgz -f values-prod.yaml -o images.txtOptions: --chart, --version, -f/--values, -o/--output, --release-name.
ls β List images (runtime, path, or check)
Three modes:
- Runtime:
hpn lsβ list images in the local runtime (Docker/Podman/Nerdctl). Alias:hpn list. - Path:
hpn ls --path ./imagesβ list saved tar files in a directory with IMAGE, SIZE, CHECKSUM. - Check:
hpn ls -f images.txtβ verify images from a file exist in the runtime or in--path.
Examples:
hpn ls
hpn ls --path ./images
hpn ls -f images.txt
hpn ls -f images.txt -o plainSkopeo is not supported (it does not store images locally).
rmi β Remove images from local storage
Remove images listed in a file from the local runtime. No interactive confirmation.
Examples:
hpn rmi -f images.txt
# Force remove (e.g. Docker) when images are in use
hpn rmi -f images.txt -- -fSupported runtimes: Docker, Podman, Nerdctl. Skopeo returns an error (no local image store).
Improved
- list-images filtering: Image extraction now excludes URLs, Prometheus-style metric names,
unix:///tcp:///fd://addresses, and port-only tags to reduce false positives in chart image lists.
Download
Download the latest release from GitHub Releases:
- Linux AMD64: hpn-linux-amd64
- Linux ARM64: hpn-linux-arm64
- macOS Intel: hpn-darwin-amd64
- macOS Apple Silicon: hpn-darwin-arm64
- Windows AMD64: hpn-windows-amd64.exe
For the full changelog, see Changelog.
v2.0.0
Harpoon v2.0 Release Notes
Release Date: January 21, 2025
Version: v2.0.0
π What's New in v2.0
Harpoon v2.0 is a major release that introduces significant improvements to the CLI architecture, adds unified registry authentication, integrates Skopeo support, and enhances the overall developer experience.
π Key Features
Unified Registry Authentication
The new hpn login command provides a unified interface for authenticating with container registries across all supported runtimes (Docker, Podman, Skopeo, Nerdctl).
Features:
- Interactive password input (most secure)
- Stdin password support for CI/CD pipelines
- Environment variable support
- Insecure registry support for private environments
- Automatic runtime detection
Example:
# Interactive login
hpn login harbor.company.com
# CI/CD usage
echo "password" | hpn login harbor.company.com -u admin --password-stdinModern CLI Architecture
The CLI has been completely refactored to use subcommands, following industry standards (similar to docker, kubectl).
Before (v1.x):
hpn -a pull -f images.txt
hpn -a save -f images.txt --save-mode 2After (v2.0):
hpn pull -f images.txt
hpn save -f images.txt --path ./imagesSkopeo Integration
Skopeo is now supported as a first-class runtime, enabling daemonless container image operations.
Benefits:
- No Docker daemon required
- Multi-architecture image support
- Better suited for CI/CD environments
- Automatic detection and fallback
Enhanced Developer Experience
- Debug Mode:
--debugflag for detailed troubleshooting - Progress Bars: Visual progress indicators for all operations
- Better Error Messages: More actionable error messages
- Improved Help: Subcommand-specific help information
π Breaking Changes
CLI Syntax Changes
All commands now use subcommands instead of the -a flag:
| Old Syntax | New Syntax |
|---|---|
hpn -a pull -f images.txt |
hpn pull -f images.txt |
hpn -a save -f images.txt --save-mode 2 |
hpn save -f images.txt --path ./images |
hpn -a load --load-mode 2 |
hpn load --path ./images |
hpn -a push -f images.txt -r registry.com --push-mode 2 |
hpn push -f images.txt --registry registry.com --project project |
Configuration File Changes
The modes section has been replaced with paths:
Old config.yaml:
modes:
save_mode: 2
load_mode: 2
push_mode: 2New config.yaml:
paths:
save_path: ./images
load_path: ./imagesπ Migration Guide
For detailed migration instructions, see the Migration Guide in the changelog.
π¦ Installation
Download Pre-built Binaries
Download the latest release from GitHub Releases:
- Linux AMD64:
hpn-linux-amd64 - Linux ARM64:
hpn-linux-arm64 - macOS Intel:
hpn-darwin-amd64 - macOS Apple Silicon:
hpn-darwin-arm64 - Windows AMD64:
hpn-windows-amd64.exe
Build from Source
git clone https://github.com/Ghostwritten/harpoon.git
cd harpoon
make build-allπ Documentation
π Acknowledgments
Thank you to all contributors and users who provided feedback and helped improve Harpoon.
π Links
- GitHub Repository: https://github.com/Ghostwritten/harpoon
- Issues: https://github.com/Ghostwritten/harpoon/issues
- Discussions: https://github.com/Ghostwritten/harpoon/discussions
For the complete list of changes, see the Changelog.
v1.1
Full Changelog: v1.0...v1.1
v1.0
Harpoon v1.0
What's New
This is the first stable release of Harpoon - a powerful cloud-native container image management tool.
Key Features
- Multi-container runtime support (Docker, Podman, Nerdctl)
- Flexible operation modes for different deployment scenarios
- Cross-platform support (Linux, macOS, Windows)
- Configuration management with YAML files
- Proxy support for corporate environments
- Batch operations for bulk image processing
Download
Choose the appropriate binary for your platform:
| Platform | Architecture | Download |
|---|---|---|
| Linux | AMD64 | hpn-v1.0-linux-amd64.tar.gz |
| Linux | ARM64 | hpn-v1.0-linux-arm64.tar.gz |
| macOS | Intel | hpn-v1.0-darwin-amd64.tar.gz |
| macOS | Apple Silicon | hpn-v1.0-darwin-arm64.tar.gz |
| Windows | AMD64 | hpn-v1.0-windows-amd64.zip |