This article explains how to track the changes that happen to a Linux binary using Chalk
Chalk is an open-source project that lets you inject metadata into Linux binaries (and JAR files and Docker images and so on; we’re focusing on Linux binaries here).
Grab the Chalk binary.
curl -fsSL "https://dl.crashoverride.run/chalk/chalk-0.6.6-$(uname -s)-$(uname -m)" > chalk && chmod +x chalkexport CHALK=$(pwd)/chalk
Now let’s build Caddy, a static web server written in Golang, from source. First, install Golang 1.25+. Then fetch the Caddy repo and build it.
git clone https://github.com/caddyserver/caddycd caddygo build ./cmd/caddy
It builds the caddy binary in the current directory.
$ file ./caddy./caddy: ELF 64-bit LSB executable, ARM aarch64, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux-aarch64.so.1, BuildID[sha1]=6d7a0f74b5d43c76f54f4232036cea496ae8824b, with debug_info, not stripped
Now we’ll mark up the caddy binary with chalk. You’ll see a bunch of info in the output about the insertion process itself.
$ $CHALK insert ./caddywarn: Could not find or install cosign; cannot sign or verify.info: /home/admin/caddy/caddy: chalk mark successfully addedinfo: /home/admin/.local/chalk/chalk.log: Open (sink conf='default_out')info: Full chalk report appended to: ~/.local/chalk/chalk.log[{"_OPERATION": "insert","_DATETIME": "2026-02-10T17:38:47.125+00:00","_CHALKS": [{"CHALK_ID": "C4T64D-B36G-R64R-K3C5HP","PRE_CHALK_HASH": "5cbc7aa20a2ebbb7ef25a7a03054159b98341772464d3d73d1b9141372e4ad4e","PATH_WHEN_CHALKED": "/home/admin/caddy/caddy","ARTIFACT_TYPE": "ELF","ORIGIN_URI": "https://github.com/caddyserver/caddy","COMMIT_ID": "5ff50779ccf1d5698bba48d140b713cff1a09390","COMMIT_SIGNED": true,"BRANCH": "master","AUTHOR": "Matt Holt <[email protected]>","COMMITTER": "GitHub <[email protected]>","COMMIT_MESSAGE": "Update LLM disclosure requirements in SECURITY.md\n\nClarified disclosure requirements for LLMs in security reports.","DATE_AUTHORED": "2026-02-09T21:40:41.000+00:00","TIMESTAMP_AUTHORED": 1770673241000,"DATE_COMMITTED": "2026-02-09T21:40:41.000+00:00","TIMESTAMP_COMMITTED": 1770673241000,"CHALK_VERSION": "0.6.6","METADATA_ID": "5XAWJ1-F79Q-B7J9-NZX4YN","_VIRTUAL": false,"_CURRENT_HASH": "5cbc7aa20a2ebbb7ef25a7a03054159b98341772464d3d73d1b9141372e4ad4e"}],"_AUTHOR": "Matt Holt <[email protected]>","_BRANCH": "master","_COMMITTER": "GitHub <[email protected]>","_COMMIT_ID": "5ff50779ccf1d5698bba48d140b713cff1a09390","_COMMIT_MESSAGE": "Update LLM disclosure requirements in SECURITY.md\n\nClarified disclosure requirements for LLMs in security reports.","_COMMIT_SIGNED": true,"_DATE_AUTHORED": "2026-02-09T21:40:41.000+00:00","_DATE_COMMITTED": "2026-02-09T21:40:41.000+00:00","_ENV": {"PWD": "/home/admin/caddy","XDG_SESSION_TYPE": "tty","USER": "admin","PATH": "/home/admin/.cargo/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin:/usr/local/go/bin","SSH_TTY": "/dev/pts/0"},"_OP_ARGV": ["/home/admin/tmp/chalk","insert","./caddy"],"_OP_CHALKER_VERSION": "0.6.6","_ORIGIN_URI": "https://github.com/caddyserver/caddy","_TIMESTAMP_AUTHORED": 1770673241000,"_TIMESTAMP_COMMITTED": 1770673241000,"_OP_CHALK_COUNT": 0,"_OP_UNMARKED_COUNT": 0}]
Most of what is durably injected into the binary are within the _CHALKS value in that output. We can find exactly what’s been injected into the binary by running strings against it and filtering on MAGIC.
$ strings ./caddy | grep MAGIC | jq{"MAGIC": "dadfedabbadabbed","CHALK_ID": "C4T64D-B36G-R64R-K3C5HP","CHALK_VERSION": "0.6.6","TIMESTAMP_WHEN_CHALKED": 1770745127125,"DATETIME_WHEN_CHALKED": "2026-02-10T17:38:47.125+00:00","ARTIFACT_TYPE": "ELF","AUTHOR": "Matt Holt <[email protected]>","BRANCH": "master","CHALK_RAND": "a65164ebe32d13fb","CODE_OWNERS": "# This is the official list of Caddy Authors for copyright purposes.\n# Authors may be either individual people or legal entities.\n#\n# Not all individual contributors are authors. For the full list of\n# contributors, refer to the project's page on GitHub or the repo's\n# commit history.\n\nMatthew Holt <[email protected]>\nLight Code Labs <[email protected]>\nArdan Labs <[email protected]>\n","COMMITTER": "GitHub <[email protected]>","COMMIT_ID": "5ff50779ccf1d5698bba48d140b713cff1a09390","COMMIT_MESSAGE": "Update LLM disclosure requirements in SECURITY.md\n\nClarified disclosure requirements for LLMs in security reports.","COMMIT_SIGNED": true,"DATE_AUTHORED": "2026-02-09T21:40:41.000+00:00","DATE_COMMITTED": "2026-02-09T21:40:41.000+00:00","HASH": "a4b5c40bbcacc8574c72141c9762252c878dfc089164303c5e4d0e4dce65b447","INJECTOR_COMMIT_ID": "f74c891bce100229365f7a3939dc89421f1beb58","ORIGIN_URI": "https://github.com/caddyserver/caddy","PLATFORM_WHEN_CHALKED": "Linux aarch64","METADATA_ID": "5XAWJ1-F79Q-B7J9-NZX4YN"}
Chalk’s defaults capture quite a bit of useful information. (You can learn more about each key by running `chalk help metadata <key>`.) But we can also customize what it collects to be even more relevant to each particular project. Specifically, let's capture the go version used to build caddy.
Chalk lets us define custom metadata keys with a simple DSL we write in config files we’ll tell Chalk to load. In the config DSL we must first tell Chalk the type and name of the key and how to collect it. Then we must tell Chalk to actually use it by default.
echo '# Define the new metadata by name and how and when to collect it.# (Custom keys must be prefixed with X_ or _X_.keyspec X_GO_VERSION {kind: ChalkTimeHosttype: stringvalue: strip(run("go version"))}# Tell Chalk to actually use this new key by default.mark_template mark_default {key.X_GO_VERSION.use = true}' > go_config.c4m
Now we register the config file with Chalk.
$ $CHALK load ./go_config.c4mwarn: Could not find or install cosign; cannot sign or verify.info: Attempting to load module from: ./go_config.c4mConfiguring Component: /home/admin/caddy/go_configFinished configuration for /home/admin/caddy/go_configinfo: [testing config]: Validating configuration.info: [testing config]: Configuration successfully validated.info: Configuration replaced in binary: /home/admin/tmp/chalkinfo: /home/admin/.local/chalk/chalk.log: Open (sink conf='default_out')info: Full chalk report appended to: ~/.local/chalk/chalk.log
Now let’s re-run chalk insert and check the new MAGIC that Chalk embeds.
$ $CHALK insert ./caddy... chalk output ...$ strings ./caddy | grep MAGIC | jq{"MAGIC": "dadfedabbadabbed","CHALK_ID": "C4T64D-B36G-R64R-K3C5HP","CHALK_VERSION": "0.6.6","TIMESTAMP_WHEN_CHALKED": 1770745252430,"DATETIME_WHEN_CHALKED": "2026-02-10T17:40:52.430+00:00","ARTIFACT_TYPE": "ELF","AUTHOR": "Matt Holt <[email protected]>","BRANCH": "master","CHALK_RAND": "3568a583d76b2bdf","CODE_OWNERS": "# This is the official list of Caddy Authors for copyright purposes.\n# Authors may be either individual people or legal entities.\n#\n# Not all individual contributors are authors. For the full list of\n# contributors, refer to the project's page on GitHub or the repo's\n# commit history.\n\nMatthew Holt <[email protected]>\nLight Code Labs <[email protected]>\nArdan Labs <[email protected]>\n","COMMITTER": "GitHub <[email protected]>","COMMIT_ID": "5ff50779ccf1d5698bba48d140b713cff1a09390","COMMIT_MESSAGE": "Update LLM disclosure requirements in SECURITY.md\n\nClarified disclosure requirements for LLMs in security reports.","COMMIT_SIGNED": true,"DATE_AUTHORED": "2026-02-09T21:40:41.000+00:00","DATE_COMMITTED": "2026-02-09T21:40:41.000+00:00","HASH": "a4b5c40bbcacc8574c72141c9762252c878dfc089164303c5e4d0e4dce65b447","INJECTOR_COMMIT_ID": "f74c891bce100229365f7a3939dc89421f1beb58","ORIGIN_URI": "https://github.com/caddyserver/caddy","PLATFORM_WHEN_CHALKED": "Linux aarch64","X_GO_VERSION": "go version go1.25.7 linux/arm64","METADATA_ID": "QH1ZT6-ZGSK-NPDX-M7PDAT"}
And now we’re storing the version of Go this binary was built with too! Now copy that caddy binary somewhere else. Copy it to your Mac laptop.
$ uname -aDarwin MacBook-Pro-2.local 25.2.0 Darwin Kernel Version 25.2.0: Tue Nov 18 21:07:05 PST 2025; root:xnu-12377.61.12~1/RELEASE_ARM64_T6020 arm64$ file ./caddy./caddy: ELF 64-bit LSB executable, ARM aarch64, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux-aarch64.so.1, BuildID[sha1]=6d7a0f74b5d43c76f54f4232036cea496ae8824b, with debug_info, not stripped$ strings caddy | grep MAGIC | jq{"MAGIC": "dadfedabbadabbed","CHALK_ID": "C4T64D-B36G-R64R-K3C5HP","CHALK_VERSION": "0.6.6","TIMESTAMP_WHEN_CHALKED": 1770745252430,"DATETIME_WHEN_CHALKED": "2026-02-10T17:40:52.430+00:00","ARTIFACT_TYPE": "ELF","AUTHOR": "Matt Holt <[email protected]>","BRANCH": "master","CHALK_RAND": "3568a583d76b2bdf","CODE_OWNERS": "# This is the official list of Caddy Authors for copyright purposes.\n# Authors may be either individual people or legal entities.\n#\n# Not all individual contributors are authors. For the full list of\n# contributors, refer to the project's page on GitHub or the repo's\n# commit history.\n\nMatthew Holt <[email protected]>\nLight Code Labs <[email protected]>\nArdan Labs <[email protected]>\n","COMMITTER": "GitHub <[email protected]>","COMMIT_ID": "5ff50779ccf1d5698bba48d140b713cff1a09390","COMMIT_MESSAGE": "Update LLM disclosure requirements in SECURITY.md\n\nClarified disclosure requirements for LLMs in security reports.","COMMIT_SIGNED": true,"DATE_AUTHORED": "2026-02-09T21:40:41.000+00:00","DATE_COMMITTED": "2026-02-09T21:40:41.000+00:00","HASH": "a4b5c40bbcacc8574c72141c9762252c878dfc089164303c5e4d0e4dce65b447","INJECTOR_COMMIT_ID": "f74c891bce100229365f7a3939dc89421f1beb58","ORIGIN_URI": "https://github.com/caddyserver/caddy","PLATFORM_WHEN_CHALKED": "Linux aarch64","X_GO_VERSION": "go version go1.25.7 linux/arm64","METADATA_ID": "QH1ZT6-ZGSK-NPDX-M7PDAT"}
Chalk embeds this metadata into the binary itself so no matter where it gets deployed you’ll retain this info.
Savvy Go users will know that you can get similar information without Chalk using go version -m, but among other reasons, Chalk 1) doesn’t require you to have Chalk installed to query this information and 2) lets you decide what Chalk injects and 3) isn’t limited to Go binaries.
One last point about integrity. While we can use strings to observe the metadata Chalk injects into artifacts, and we did this here to show how simple Chalk is, we recommend using chalk extract to pull this metadata out of binaries instead of directly inspecting the binaries. This is for a few reasons including that in production we’d be using Chalk to sign artifacts after we inject metadata. chalk extract will validate signatures which lets us rest assured that the binary hasn’t been tampered with.
And we’ve just talked about Crash itself. Crash Override offers a platform on top of Chalk that helps you easily discover and manage artifacts throughout your environments, but it’s all built on this simple open-source tool you can already start using today to track provenance on your own.
