Description
APT checks the signature of the Release file of each repository. The Signed-By option
([signed-by=...] in the one-line format, Signed-By: field in the deb822 .sources format)
names the keyring allowed to sign that repository. Without it, any key in
/etc/apt/trusted.gpg.d/ is accepted for every repository.
Per-source options can also weaken or disable the checks: trusted=yes, allow-insecure=yes,
allow-weak=yes, allow-downgrade-to-insecure=yes, check-valid-until=no and check-date=no.
Debian 13 and the Debian 12 cloud images use deb822 sources with Signed-By. A Debian 12
installed with the installer uses one-line entries in /etc/apt/sources.list without it; the
automated fix adds the Debian archive keyring to entries of deb.debian.org,
security.debian.org and ftp.<country>.debian.org. Other repositories, and the bypass
options, are left to a human.
Rationale
Binding each repository to its own key limits a compromised or malicious third-party key to the repository it was added for: it cannot sign packages that replace Debian ones. Sources that bypass the signature checks let anyone on the network path, or controlling the mirror, install arbitrary packages as root.
Impact
None for the Debian archive. A third-party repository needs its key stored as a separate file
(usually under /etc/apt/keyrings/) and referenced from its source entry.
Setting
| Location | /etc/apt/sources.list, /etc/apt/sources.list.d/*.list, /etc/apt/sources.list.d/*.sources |
| Default | Signed-By set on Debian 13 and cloud images, missing on Debian 12 installer sources |
| Recommended | Signed-By on every entry, no option that disables or weakens signature checks |
Audit
Read every enabled entry in /etc/apt/sources.list, /etc/apt/sources.list.d/*.list and
/etc/apt/sources.list.d/*.sources:
- each one-line entry has
[signed-by=<keyring>], each deb822 stanza aSigned-By:field; - no entry sets
trusted=yes,allow-insecure=yes,allow-weak=yes,allow-downgrade-to-insecure=yes,check-valid-until=noorcheck-date=no(or the matching deb822 fields).
Automated: every check must pass. Scanners report each check by its id.
sources
Signed-By and signature options of every enabled APT source entry.
script: |
files=
for f in /etc/apt/sources.list /etc/apt/sources.list.d/*.list /etc/apt/sources.list.d/*.sources; do
[ -f "$f" ] && files="$files $f"
done
[ -n "$files" ] || { echo "no APT source file"; exit 2; }
# One line per entry: file:line, types, URIs, suites, ;option=value list (lowercase).
# shellcheck disable=SC2086
awk '
function flush() { if (st && en != "no") print src "\t" ty "\t" ur "\t" su "\t" op; st = 0; ty = ur = su = op = en = "" }
FNR == 1 { flush(); d = FILENAME ~ /\.sources$/ }
!d {
sub(/#.*/, ""); if ($1 !~ /^deb(-src)?$/) next
l = $0; o = ""; i = index(l, "["); j = index(l, "]")
if (i && j > i) { o = substr(l, i + 1, j - i - 1); l = substr(l, 1, i - 1) " " substr(l, j + 1) }
split(l, w, " "); gsub(/[ \t]+/, ";", o)
print FILENAME ":" FNR "\t" w[1] "\t" w[2] "\t" w[3] "\t;" tolower(o); next
}
/^#/ { next }
/^[ \t]*$/ { flush(); next }
/^[ \t]/ { next }
{
if (!st) { st = 1; src = FILENAME ":" FNR }
k = $0; sub(/:.*/, "", k); k = tolower(k); v = $0; sub(/^[^:]*:[ \t]*/, "", v)
if (k == "types") ty = v; else if (k == "uris") ur = v; else if (k == "suites") su = v
else if (k == "enabled") en = tolower(v)
else op = op ";" k "=" tolower(v == "" ? "inline" : v)
}
END { flush() }' $files | awk -F '\t' '
{
bad = ""
if ($5 !~ /;signed-by=/) bad = bad " no-signed-by"
n = split("trusted=yes allow-insecure=yes allow-weak=yes allow-downgrade-to-insecure=yes check-valid-until=no check-date=no", o, " ")
for (i = 1; i <= n; i++) if (index($5, ";" o[i])) bad = bad " " o[i]
if (bad != "") { print $1 " " $3 ":" bad; rc = 1 } else ok++
}
END { print ok + 0 " entries bound to a key"; exit rc }'
Remediation
- For each repository, store its public key in its own file, for example
/etc/apt/keyrings/<vendor>.asc(Debian keys are in/usr/share/keyrings/). - Reference it from the entry:
deb [signed-by=/etc/apt/keyrings/<vendor>.asc] https://...in the one-line format, orSigned-By: /etc/apt/keyrings/<vendor>.ascin a.sourcesstanza. Debian 13 can convert old entries withapt modernize-sources. - Remove
trusted=yesand the other options that weaken the checks. A repository that only works with them must be fixed by its owner (signed) or removed. - Run
apt-get updateand check that it prints no signature warning.
The automated fix only adds /usr/share/keyrings/debian-archive-keyring.gpg to entries of the
Debian archive.
| Restart | none |
| Disruption | none |
Rollback
Restore the source files from the backup directory printed by remediate.sh.
signed-by-debian
#!/bin/sh
# CB-DEB-PKG-001 / signed-by-debian - add Signed-By to Debian archive entries that lack it.
# Only entries pointing to deb.debian.org, security.debian.org or ftp.<cc>.debian.org are changed:
# the key of any other repository must be chosen by a human. Idempotent, no-op when compliant.
# SPDX-License-Identifier: Apache-2.0
set -eu
k=/usr/share/keyrings/debian-archive-keyring.gpg
h='(https?|tor\+https?)://(deb|security|ftp\.[a-z]+)\.debian\.org/'
# One-line format: sources.list and *.list
for f in /etc/apt/sources.list /etc/apt/sources.list.d/*.list; do
[ -f "$f" ] || continue
grep -E '^[[:space:]]*deb(-src)?[[:space:]]' "$f" | grep -v 'signed-by=' | grep -Eq "$h" || continue
[ -e "$k" ] || { echo "$k is missing, install debian-archive-keyring" >&2; exit 1; }
cb_backup "$f"
sed -E -i "/signed-by=/! {
s#^([[:space:]]*deb(-src)?[[:space:]]+)\[([^]]*)\]([[:space:]]*$h)#\1[signed-by=$k \3]\4#
t
s#^([[:space:]]*deb(-src)?[[:space:]]+)($h)#\1[signed-by=$k] \3#
}" "$f"
done
# deb822 format: *.sources, one stanza per paragraph
for f in /etc/apt/sources.list.d/*.sources; do
[ -f "$f" ] || continue
t=$(mktemp)
if awk -v k="$k" '
function flush( i) {
for (i = 1; i <= n; i++) {
print b[i]
if (i == u && deb && !sig) { print "Signed-By: " k; c = 1 }
}
n = u = deb = sig = 0
}
/^[ \t]*$/ { flush(); print; next }
{ b[++n] = $0 }
tolower($0) ~ /^signed-by:/ { sig = 1 }
tolower($0) ~ /^uris:/ { u = n; if ($0 ~ /:\/\/(deb|security|ftp\.[a-z]+)\.debian\.org\//) deb = 1 }
END { flush(); exit !c }' "$f" >"$t"; then
[ -e "$k" ] || { rm -f "$t"; echo "$k is missing, install debian-archive-keyring" >&2; exit 1; }
cb_backup "$f"
cat "$t" >"$f"
fi
rm -f "$t"
done
Handwritten code reads parameters from CB_<NAME> environment variables. Each release turns these fixes into remediate.sh and an Ansible role per profile: dry run by default, a fix runs only when its check fails.
References
- sources.list(5), Debian manual pages Debian, vendor-doc, accessed 2026-10-18
- apt-secure(8), Debian manual pages Debian, vendor-doc, accessed 2026-10-18
- Debian Wiki - DebianRepository/UseThirdParty Debian, vendor-doc, accessed 2026-10-18
- ANSSI-BP-028 v2.0 - Recommandations de configuration d'un système GNU/Linux (R59) ANSSI, guide, accessed 2026-10-18
Mappings
| Framework | ID | Relation | Note |
|---|---|---|---|
| ANSSI-BP-028 - Recommandations de configuration d'un système GNU/Linux2.0 | R59 | partial | R59 asks for trusted repositories only; this control enforces that each one is authenticated with its own key. |
| NIST SP 800-53 Rev. 55.1.1 | CM-14 | related | |
| NIST SP 800-53 Rev. 55.1.1 | SI-7 | related |
How this control relates to the other framework's: equivalent, stricter, looser, partial or related.
Threats mitigated
Related controls
- related
CB-DEB-PKG-003Keep APT signature, freshness and TLS checks enabled
People and lifecycle
| Authors |
|
| Verified | 2026-10-18 by Sn0wAlice on 12, Proxmox VM, Debian 12 genericcloud image, kernel 6.1.0-54-cloud-amd64. Passes on stock cloud images (deb822 with Signed-By); one-line and deb822 entries without Signed-By, trusted=yes (both formats) detected, disabled stanzas skipped; fix adds the Debian keyring, apt-get update works, idempotent, kept after reboot. |
| Verified | 2026-10-18 by Sn0wAlice on 13, Proxmox VM, Debian 13 genericcloud image, kernel 6.12.113+deb13-cloud-amd64. Passes on stock cloud images (deb822 with Signed-By); one-line and deb822 entries without Signed-By, trusted=yes (both formats) detected, disabled stanzas skipped; fix adds the Debian keyring, apt-get update works, idempotent, kept after reboot. |
| Introduced in | v0.1.0 |
| Created, updated | 2026-10-18, 2026-10-18 |
| Translations | fr |