Debian GNU/Linux · Packages & updates

Bind every APT repository to its signing key

When it fails: APT repositories are not bound to a signing key or bypass signature checks. A repository without its own key is trusted with every key known to APT, and options such as trusted=yes disable signature checks entirely.

CB-DEB-PKG-001highactiveautomatedautomated fixprofile core

As released in v0.1.0 beta · pre-release. Current state.

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
DefaultSigned-By set on Debian 13 and cloud images, missing on Debian 12 installer sources
RecommendedSigned-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:

  1. each one-line entry has [signed-by=<keyring>], each deb822 stanza a Signed-By: field;
  2. no entry sets trusted=yes, allow-insecure=yes, allow-weak=yes, allow-downgrade-to-insecure=yes, check-valid-until=no or check-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

  1. 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/).
  2. Reference it from the entry: deb [signed-by=/etc/apt/keyrings/<vendor>.asc] https://... in the one-line format, or Signed-By: /etc/apt/keyrings/<vendor>.asc in a .sources stanza. Debian 13 can convert old entries with apt modernize-sources.
  3. Remove trusted=yes and the other options that weaken the checks. A repository that only works with them must be fixed by its owner (signed) or removed.
  4. Run apt-get update and 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.

Restartnone
Disruptionnone

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

Mappings

FrameworkIDRelationNote
ANSSI-BP-028 - Recommandations de configuration d'un système GNU/Linux2.0R59partialR59 asks for trusted repositories only; this control enforces that each one is authenticated with its own key.
NIST SP 800-53 Rev. 55.1.1CM-14related
NIST SP 800-53 Rev. 55.1.1SI-7related

How this control relates to the other framework's: equivalent, stricter, looser, partial or related.

Threats mitigated

  • related CB-DEB-PKG-003 Keep APT signature, freshness and TLS checks enabled

People and lifecycle

Authors
Verified2026-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.
Verified2026-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 inv0.1.0
Created, updated2026-10-18, 2026-10-18
Translationsfr