Debian GNU/Linux · Packages & updates

Download packages over HTTPS

When it fails: APT downloads from repositories over an unencrypted protocol. Anyone on the network path sees which packages and versions the system installs, and can delay or block its updates.

CB-DEB-PKG-002lowactiveautomatedautomated fixprofile core

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

Description

Each APT source entry names one or more URIs. The https method encrypts and authenticates the connection to the mirror; http and ftp do not. Entries using mirror+file: point to a local list of mirrors, whose URIs are checked too.

The Debian 12 and 13 cloud images already use https://deb.debian.org. A system installed with the Debian installer uses http://deb.debian.org; the automated fix switches deb.debian.org and security.debian.org to HTTPS. Other mirrors may not offer HTTPS and are left to a human.

Rationale

APT verifies the signature of the Debian archive whatever the transport, so HTTP does not let an attacker change packages. It still exposes the list of installed packages and their versions, which tells an attacker which vulnerabilities are left, and lets a network attacker serve stale but validly signed metadata until it expires, or block updates silently. HTTPS removes these exposures and is required for repositories that are not signed by their provider.

Impact

The system needs the ca-certificates package and outbound HTTPS. Caching proxies such as apt-cacher-ng cannot cache HTTPS traffic they do not terminate: list their HTTP URIs in the http_allowed_uris parameter rather than weaken the other controls.

Setting

Location/etc/apt/sources.list, /etc/apt/sources.list.d/*.list, /etc/apt/sources.list.d/*.sources
Defaulthttps on cloud images, http after a Debian installer setup
Recommendedhttps (or a local method such as file:, cdrom:, copy:) for every URI, except those starting with: {{ http_allowed_uris }}

Parameters

ParameterCoreServerServer hardenedDesktopDesktop hardenedBounds
http_allowed_urislist. URI prefixes allowed over plain HTTP, for example a local apt-cacher-ng proxy (http://apt-cache.example.org:3142/). Empty means none.

The value used is your override (tailoring file), else the profile's, else the one of the profile it extends, else the default. Bounds limit what an override may set.

Audit

Run grep -rhiE '^[^#]*(http|ftp)://' /etc/apt/sources.list /etc/apt/sources.list.d/, and read the files named after mirror+file: if any. Every URI must use https or a local method, or start with one of the allowed prefixes.

Automated: every check must pass. Scanners report each check by its id.

uris

Transport of every URI of the enabled APT source entries, including mirror lists.

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
  entries=$(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)
  bad=$(printf '%s\n' "$entries" | while IFS="$(printf '\t')" read -r src _ uris _ _; do
    for u in $uris; do
      case $u in
        mirror+file:*)
          p=$(printf '%s' "$u" | sed 's#^mirror+file:/*#/#')
          [ -f "$p" ] || { echo "$src: $u (missing mirror list)"; continue; }
          list=$(grep -v '^[[:space:]]*#' "$p" 2>/dev/null | awk '{ print $1 }') ;;
        *) list=$u ;;
      esac
      for m in $list; do
        case $m in https://* | tor+https://* | mirror+https://* | file:* | cdrom:* | copy:*) continue ;; esac
        for a in ${CB_HTTP_ALLOWED_URIS:-}; do
          case $m in "$a"*) continue 2 ;; esac
        done
        echo "$src: $m"
      done
    done
  done)
  n=$(printf '%s\n' "$entries" | grep -c .)
  if [ -n "$bad" ]; then
    printf '%s\n' "$bad"
    exit 1
  fi
  echo "$n entries, all over https or a local method"

Remediation

  1. Install ca-certificates.
  2. In each source entry, and in the mirror lists named after mirror+file:, replace http:// with https:// when the mirror serves HTTPS (deb.debian.org and security.debian.org do). Pick another mirror otherwise.
  3. Run apt-get update and check that it prints no error.
Restartnone
Disruptionlow

Rollback

Restore the source files from the backup directory printed by remediate.sh, or replace https:// with http:// again.

https-debian

#!/bin/sh
# CB-DEB-PKG-002 / https-debian - switch the Debian archive entries from HTTP to HTTPS.
# Only deb.debian.org and security.debian.org are changed: they serve HTTPS. Other mirrors need a
# human. Hosts listed in CB_HTTP_ALLOWED_URIS are left alone. Idempotent, no-op when compliant.
# SPDX-License-Identifier: Apache-2.0
set -eu

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" ] || exit 0
# Mirror lists referenced as mirror+file: hold the real URIs.
# shellcheck disable=SC2086
for p in $(grep -hoE 'mirror\+file:/*[^[:space:]]+' $files | sed -E 's#^mirror\+file:/*#/#' | sort -u); do
  [ -f "$p" ] && files="$files $p"
done

for host in deb security; do
  for a in ${CB_HTTP_ALLOWED_URIS:-}; do
    case $a in *"://$host.debian.org"*) continue 2 ;; esac
  done
  for f in $files; do
    grep -Eq "(^|[[:space:]+])http://$host\.debian\.org/" "$f" || continue
    if [ ! -s /etc/ssl/certs/ca-certificates.crt ]; then
      echo "ca-certificates is not installed, APT could not check the HTTPS servers" >&2
      exit 1
    fi
    cb_backup "$f"
    sed -E -i "s#(^|[[:space:]+])http://$host\.debian\.org/#\1https://$host.debian.org/#g" "$f"
  done
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

FrameworkIDRelation
NIST SP 800-53 Rev. 55.1.1SC-8related

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

Threats mitigated

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 (https mirror lists); http entries in sources.list, .sources and mirror+file lists detected; fix switches deb/security.debian.org to https, apt-get update works without warning; http_allowed_uris honoured by check and fix; 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 (https mirror lists); http entries in sources.list, .sources and mirror+file lists detected; fix switches deb/security.debian.org to https, apt-get update works without warning; http_allowed_uris honoured by check and fix; kept after reboot.
Introduced inv0.1.0
Created, updated2026-10-18, 2026-10-18
Translationsfr