Skip to content

v1.0.0

Choose a tag to compare

@github-actions github-actions released this 30 Oct 10:55
Immutable release. Only release title and notes can be modified.
b88c4b8

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-secrets moves literal secrets out
    of a configuration file.
  • http feathers. 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 plugins lists them without
    running any.
  • Project trust, tightened. A trusted .kjak.toml reads 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.sh installs 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, no sudo, no PATH change.
  • 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 env and keychain
    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: --yes alone 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 to localhost, a
    private network or a private SMTP server fails with blocked (exit 1)
    until the project allows it (allow_networks, allow_hosts). Global
    feathers are not affected.
  • --to must name something. --to "" or --to , (an empty variable
    in a script) is a usage error (exit 2); v0.0.4 sent to defaults.targets.
  • Message templates cannot loop (range, define, template,
    block), and printf refuses 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
    service or account), and a keychain account cannot hold /.
  • config validate -o json prints a JSON document on success
    ("valid": true, the files, the literal secrets by key); v0.0.4 printed
    the PATH: OK text lines there. Failures print the same error document as
    before, and the exit status is unchanged.
  • kjak version and kjak feather types take 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 of trust.toml if 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 and feather add --secret-stdin. They
    keep working in 1.x; kjak send warns about them on stderr (and warns
    that the file is readable by others when it is), kjak config validate
    reports them and --strict refuses them. Move them with
    kjak config migrate-secrets, then rotate them.

Migration

  1. Install v1.0.0 (below).
  2. kjak config validate (and --strict to find literals);
    kjak config migrate-secrets if you have any, then rotate the secrets
    that were ever committed or shared.
  3. In each repository with a .kjak.toml: review it, then
    kjak config trust again if a send says the trust does not allow a
    reference; add allow_networks/allow_hosts to project feathers that
    send to a local or private address. In CI use
    kjak config trust --yes --allow-user-secrets after the review.
  4. Replace range/define/template/block in 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 SMTP AUTH, 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 (PATH with 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 trust shows 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 answer QUIT does not hold the
    send.
  • TLS: kjak uses the system's certificate authorities; SSL_CERT_FILE and
    SSL_CERT_DIR replace them on macOS and Windows (Go 1.27), see
    docs/security.md.

Report a vulnerability privately: SECURITY.md.

Fixes

  • results[].duration_ms in the JSON report (and the duration of the text
    output) was always 0; it is the time of the target's delivery.
  • An http body template naming something it cannot use ({{.titel}}) is
    a configuration error (exit 2, nothing sent) instead of a failure of every
    send.
  • An http request redirected to an address that refuses the connection is
    rejected (network), not unknown.
  • A plugin's retry_after_ms too 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 later

or, 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 later

or 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.0

Developers 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, windows are 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-run does 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