Repository navigation
Refuse to export a layer that carries a private key - #14
Merged
Merged
Conversation
Every image built from a layer holds the same bytes, so a private key in a layer is a key every machine built from it shares, and anyone who downloads the layer has. keel-core#8 found three such keys in all twelve published layers: the turnkey-ssl certificate key, which its postinst also copies into /etc/ssl/private where the web servers, Webmin and the init fence read it; the SSH host keys openssh-server generates at build time; and Webmin's miniserv.pem. lapp and postgresql add the ssl-cert snakeoil key. Removing them is common's job (removelists-final). This is the check that holds whether or not a list remembers every package: layer_check_build now scans the built tree for a PEM private key header on a line of its own, text files only, symlinks not followed, and refuses the layer, naming every key by its path in the image. A grep that fails is a failed audit, not a clean one. The exceptions are four inert examples that Debian and npm packages ship, listed in conf/layer-inert-keys with the reason for each and accepted only at their own path and digest, so a package update that changes one stops the build until somebody reads it. Measured on the build host's layer trees: the anchored pattern finds exactly the real keys and those four, in about a second per layer. Refs keel-core#8 Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]> Claude-Session: https://claude.ai/code/session_01SfWQScmDZ94KCS5DMYrfe6
From the security review of #14: the pattern required the header to end in "PRIVATE KEY", and an armoured OpenPGP secret key ends in "PRIVATE KEY BLOCK", so a secret keyring exported into a layer passed. Test: a secret key block is refused, a public key block is not. The comment on the pattern now also says what it cannot see: binary key formats (DER, PKCS#12, Java keystores, PuTTY, GnuPG's private-keys-v1.d). The review checked every layer tree on the build host for them and found none; one that appears needs its own check. Refs keel-core#8 Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]> Claude-Session: https://claude.ai/code/session_01SfWQScmDZ94KCS5DMYrfe6
marcos-mendez
added a commit
that referenced
this pull request
Sep 29, 2026
From the security review of #14: the pattern required the header to end in "PRIVATE KEY", and an armoured OpenPGP secret key ends in "PRIVATE KEY BLOCK", so a secret keyring exported into a layer passed. Test: a secret key block is refused, a public key block is not. The comment on the pattern now also says what it cannot see: binary key formats (DER, PKCS#12, Java keystores, PuTTY, GnuPG's private-keys-v1.d). The review checked every layer tree on the build host for them and found none; one that appears needs its own check. Refs keel-core#8
marcos-mendez
added a commit
that referenced
this pull request
Sep 29, 2026
Refuse to export a layer that carries a private key
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
keel-core#8: all twelve layers exported so far carry the same private keys, and nothing in the build noticed. A list of files to remove only holds while someone remembers every package that makes a key.
Fix
layer_check_buildrunslayer_audit_keyson the built tree before anything is packed: a PEM private key header on a line of its own, text files only (grep -I), symlinks not followed. Every key found is named by its path in the image, and the layer is refused. A grep that fails is a failed audit, not a clean one. The exceptions are four inert examples shipped by packages, inconf/layer-inert-keyswith a reason each, accepted only at their path and digest.Evidence
tests/layer: every key format, a key after a certificate, the quoted marker, a binary, a symlink out of the tree, the inert list by path and digest, grep failing or missing, and abt-layerrun whose build leaks a host key: refused, no tarball, no manifest. Five mutations each turn it red.Deploying the three together
This is one of three pull requests for keel-core#8: Keel-Linux/inithooks (generate on the machine), Keel-Linux/common (remove from the build), Keel-Linux/buildtasks (refuse at export). Merging deploys nothing: the build host checkouts are updated by hand. The order that matters is deployment:
inithooks 2.3.6+keel6to the archive (attended, signing). Until then a layer without keys would boot with no TLS certificate until15regen-sslcert, which only restarts services already running./turnkey/fab-keel/commonand/turnkey/buildtasks-keelon the build host in the same sitting: the audit without the removals refuses every layer.keel-host-keysreplaces them on any machine made from one.