Repository navigation
v1.0.0
kjak v1.0.0
kjak sends one notification to Slack, Discord, e-mail, any HTTP API or a
plugin of your own, from the command line, a script or CI. v1.0.0 is the
first stable release: the command line, the configuration, the JSON output,
the exit status, the error codes and the plugin protocol are now a contract
kept for all of 1.x (docs/stability.md).
Highlights
- Stable contract. docs/stability.md lists what 1.x
guarantees (commands and flags, exit status, JSON schema version 1, error
codes, configuration and secrets files, the trust store, retry semantics,
plugin protocol v1, the version line), what it does not (text output,
messages, provider payloads, descriptive values inside JSON documents),
and what makes a major, a minor or a patch release. Tests check it against
the code. - Secrets files. Secrets live in a private TOML file of their own
({ secret = "dev_slack.webhook_url" }), personal (--secrets,
$KJAK_SECRETS,~/.config/kjak/secrets.toml) or per project
(.kjak.secrets.toml, trusted with the project). kjak refuses a secrets
file anyone else can read, checking the mode bits and the ACLs on Linux,
macOS and Windows.kjak config migrate-secretsmoves literal secrets out
of a configuration file. httpfeathers. Any HTTP API: method, headers, query, bearer/basic/
header authentication by reference, a JSON or text body or a template,
success statuses, idempotency keys and method-aware retries, behind an
address policy (https only, no redirects, no loopback, private,
link-local or cloud-metadata address unless allowed; checked where the
connection is made).- External feather plugins.
kjak-feather-<name>executables speaking
a documented JSON protocol (v1) on stdin/stdout. kjak checks which
executable runs, gives it only its own secrets and a minimal environment
in a private working directory, and ends its whole process tree (process
group on Unix, Job Object on Windows) at its timeout, the deadline and
Ctrl-C, SIGTERM or SIGHUP.kjak feather pluginslists them without
running any. - Project trust, tightened. A trusted
.kjak.tomlreads your
environment variables, keychain entries and personal secrets only by the
names you allowed when you trusted it, and its slack, discord and email
feathers reach public addresses only unless the file allows more. - Install script.
install.shinstalls a release on macOS and Linux
(amd64, arm64) without Homebrew: checked against the release's checksums,
only the binary taken out of the archive, the installed one replaced in a
single rename, nosudo, noPATHchange. - Quality. golangci-lint, fuzzing, mutation testing, end-to-end tests of
the built binary (and of v0.0.4 configurations), native CI on every
release platform.
Senders
| feather | sends | secrets | retries |
|---|---|---|---|
slack |
an incoming webhook message | webhook_url |
429, 5xx except 502/504, a connection that failed before the request was sent |
discord |
a webhook message (title, body, level color, URL) | webhook_url |
as slack, retry_after honored |
email |
an e-mail over SMTP (STARTTLS, TLS or, explicitly, plain), AUTH PLAIN/LOGIN |
password |
SMTP 4xx, a connection that failed before the data |
http |
any HTTP request; the body is JSON, text or a template of the message | url_secret, auth |
by method (retry.policy): GET/PUT/DELETE, and POST/PATCH with an idempotency key |
external |
whatever the plugin does | the ones it is given | only with retry.policy = "plugin" |
Groups send to several feathers at once; templates turn variables into a
message (--template, --template-file, kjak template render). Every
target is delivered on its own, at most --concurrency at a time, within
one deadline (--timeout, default 10s): one feather's failure never hides
another's result. The report (-o json) gives each target's status,
acceptance (confirmed, rejected, unknown), attempts and errors, and the
outcome of the send (success, partial, failure, canceled); exit 0
when nothing failed, 1 when a delivery failed, 2 when nothing was sent
(usage, configuration, credentials, trust), 130 when interrupted. An
unknown acceptance (a request that may have been delivered) is never
retried.
Breaking changes
Compared to v0.0.4. Each is a security fix or a fix of the output contract,
refused before anything is sent; the migration guide has the steps.
- Project feathers need a new trust for
envandkeychain
references. A project trusted by v0.0.4 stays trusted, but its slack,
discord and email feathers that read an environment variable or a
keychain entry are refused (exit 2, nothing sent) until you trust the
project again and allow those names (kjak config trust; in CI
--yes --allow-user-secrets:--yesalone now fails for such a project). - A project's slack, discord and email feathers reach public addresses
only, without a proxy. A project feather that sends tolocalhost, a
private network or a private SMTP server fails withblocked(exit 1)
until the project allows it (allow_networks,allow_hosts). Global
feathers are not affected. --tomust name something.--to ""or--to ,(an empty variable
in a script) is a usage error (exit 2); v0.0.4 sent todefaults.targets.- Message templates cannot loop (
range,define,template,
block), andprintfrefuses a width or precision of 1000 or more,*
and argument indexes. - Configuration files must be regular files. A configuration file, a
project file or the trust store that is a FIFO, a device or a directory is
refused (a symbolic link to a regular file still works). - Text fields cannot hold control characters (
username,host,
user,from,subject_prefix,plugin,path, ..., a keychain
serviceoraccount), and a keychainaccountcannot hold/. config validate -o jsonprints a JSON document on success
("valid": true, the files, the literal secrets by key); v0.0.4 printed
thePATH: OKtext lines there. Failures print the same error document as
before, and the exit status is unchanged.kjak versionandkjak feather typestake no argument but
--help(an unexpected one is exit 2).- Downgrade: once a trust entry uses the new keys (a project secrets
file, allowed names, plugins), v0.0.4 cannot read the trust store
("broken trust store"). Keep a copy oftrust.tomlif you may go back.
Unchanged: configurations without literals or control characters, env
and keychain references of the global configuration, groups, templates
without loops, the JSON report (fields were only added), the exit status,
the request bodies sent to Slack, Discord and e-mail.
Deprecated
{ literal = "..." }credentials andfeather add --secret-stdin. They
keep working in 1.x;kjak sendwarns about them on stderr (and warns
that the file is readable by others when it is),kjak config validate
reports them and--strictrefuses them. Move them with
kjak config migrate-secrets, then rotate them.
Migration
- Install v1.0.0 (below).
kjak config validate(and--strictto find literals);
kjak config migrate-secretsif you have any, then rotate the secrets
that were ever committed or shared.- In each repository with a
.kjak.toml: review it, then
kjak config trustagain if a send says the trust does not allow a
reference; addallow_networks/allow_hoststo project feathers that
send to a local or private address. In CI use
kjak config trust --yes --allow-user-secretsafter the review. - Replace
range/define/template/blockin message templates; check
scripts that pass--to "$VAR".
Details: docs/migration.md.
Security
- Secrets: separate private files, checked on the opened file (owner, mode,
ACLs; fail closed), never printed. Every error and report is redacted of
every resolved secret — also its URL forms, the base64 forms sent for HTTP
basic authentication and SMTPAUTH, a fragment left by a truncation and
multi-line secrets. A configuration file with literal secrets that others
can read is warned about. - Projects: every
env/keychain/personal secret reference and plugin
environment variable is allowed by name; an address policy for every
network feather of a project; plugins pinned by the trust (path and
SHA-256). - Plugins: a minimal environment (
PATHwith absolute directories only), a
private working directory per attempt, the whole process tree ended; a
plugin file is found only under its exact name (also on file systems that
ignore case). - Untrusted text: terminal escape sequences in a project file are refused
or escaped before they reach your terminal (config trustshows what you
approve). - SSRF: metadata services (AWS, GCP, Azure WireServer, Tencent, ...),
IPv4-in-IPv6 and NAT64 forms are refused; DNS rebinding is covered by
checking the address where the connection is made. - Denial of service: a FIFO as a configuration file and looping templates
are refused; an SMTP server that does not answerQUITdoes not hold the
send. - TLS: kjak uses the system's certificate authorities;
SSL_CERT_FILEand
SSL_CERT_DIRreplace them on macOS and Windows (Go 1.27), see
docs/security.md.
Report a vulnerability privately: SECURITY.md.
Fixes
results[].duration_msin the JSON report (and the duration of the text
output) was always 0; it is the time of the target's delivery.- An
httpbody template naming something it cannot use ({{.titel}}) is
a configuration error (exit 2, nothing sent) instead of a failure of every
send. - An
httprequest redirected to an address that refuses the connection is
rejected(network), notunknown. - A plugin's
retry_after_mstoo large for a duration is capped at 24h
instead of overflowing into a wrong wait. - A Windows plugin canceled while it starts is reported as canceled.
- SIGHUP (a closed terminal) cancels a send like SIGTERM: the report is
printed, the plugins are ended, exit 130.
Install
brew install magicdrive/tap/kjak # macOS, Linux; brew upgrade kjak lateror, without Homebrew, on macOS and Linux:
curl -fsSLO https://raw.githubusercontent.com/magicdrive/kjak/v1.0.0/install.sh
sh install.sh --version v1.0.0 # into ~/.local/bin; sh install.sh --upgrade lateror the archive of your platform below, checked with kjak_checksums.txt
and, optionally, its build provenance:
gh attestation verify kjak_1.0.0_linux_amd64.tar.gz --repo magicdrive/kjak \
--signer-workflow magicdrive/kjak/.github/workflows/release.yml --source-ref refs/tags/v1.0.0Developers can build from source with Go 1.27 (make build, or
go install github.com/magicdrive/kjak@v1.0.0).
Platforms
| platform | archive | notes |
|---|---|---|
| macOS arm64, amd64 | tar.gz; Homebrew, install script |
keychain (security); secrets files: mode bits and ACLs (an allow entry, even your own, is refused: chmod -N). A browser download is quarantined: xattr -d com.apple.quarantine kjak |
| Linux amd64, arm64 | tar.gz; Homebrew, install script |
keychain through secret-tool (a Secret Service must run); secrets files: mode bits and POSIX ACLs (NFSv4 ACLs are not read) |
| Windows amd64 | zip |
no keychain (use env or a secrets file); secrets files: owner and DACL (a file under a folder made in C:\ inherits Authenticated Users and is refused); plugins run in a Job Object |
windows/arm64 is built and vetted in CI but not released. Every archive is
attested and listed in kjak_checksums.txt. The tests run natively on every
released platform in CI.
Known limitations
- OS-native notifications are not supported (the type names
os,native,
macos,linux,windowsare reserved and refused). - Windows has no keychain support. On macOS and Linux the keychain tools are
tested as test doubles, not against a real keychain; keep keychain secrets
to printable ASCII. - Commands that change a configuration file (
feather add,group set,
...) rewrite it without its comments, and keep its mode (a file you made
readable by others stays so: a literal secret in it is warned about). - Plugins are not sandboxed: they run with your rights. A descendant that
leaves the process group (setsid) or the Job Object (through a service)
is not ended by kjak, and on Unix a plugin outlives a kjak killed with
SIGKILL. send --dry-rundoes not check the address policy (a refused address
shows in a real send).- The macOS binaries are not notarized.
- The Discord end-to-end check against a real webhook is manual
(docs/discord-e2e.md); CI uses local test servers.
Full Changelog: v0.0.4...v1.0.0