diff --git a/conformance/incluster/Containerfile b/conformance/incluster/Containerfile new file mode 100644 index 0000000..adae986 --- /dev/null +++ b/conformance/incluster/Containerfile @@ -0,0 +1,168 @@ +# ============================================================================= +# conformance-runner — the image the in-cluster L2/L3 Chainsaw Job actually runs. +# Closes docs/plans/l2-l3-conformance-ci-wiring.md §3 item 1. +# +# WHY THIS EXISTS. chainsaw-job.yaml pins `ghcr.io/kyverno/chainsaw:v0.2.12` and +# `command: ["./run.sh"]`. That image carries the chainsaw binary and almost nothing else — +# no bash, no python3, no kubectl, no git. So the committed Job could never have run: it +# would have failed at exec of `./run.sh`. This image carries exactly what +# `conformance/run.sh` needs: +# +# bash run.sh and every tests/**/check.sh are bash (arrays, mapfile, BASH_SOURCE) +# python3 lib/{run_l1_offline,gen_coverage,merge_junit,emit_skipped_junit,status}.py +# (stdlib only — conformance/versions.env PYTHON_MIN=3.9, so no pip layer) +# chainsaw the L1/L2/L3 runner itself (run_chainsaw / lint_tests) +# kubectl conf_cluster_reachable()'s `kubectl cluster-info` preflight + probe scripts +# git the init-container's `clone --depth 1` + `checkout ` (§3 item 2) and +# run.sh's `rev-parse --short HEAD` for the run id +# tar/gzip/coreutils the `=== ARTIFACTS-TGZ-BASE64 ===` results channel (§3 item 3) +# +# VERSIONS COME FROM infra/devcontainer/versions.env — NOT from new pins invented here. +# That file is the single source of truth for CHAINSAW_VERSION / KUBECTL_VERSION / +# FEDORA_VERSION, and build-image.sh sources it and passes them in as --build-arg. The +# ARG defaults below are deliberately EMPTY so a build that forgets to pass them fails +# loudly (see the assertion in the first RUN) instead of silently baking a stale version. +# +# KNOWN LIMIT — READ BEFORE CHOOSING THE JOB'S `args`. +# `run.sh` runs the selected OFFLINE L0/L1 checks (run_offline_checks) before it ever +# touches the cluster, for ANY selection that is not offline-only. Those checks shell out +# to flux / promtool / kyverno / kustomize, which this image deliberately does NOT carry — +# baking the whole devcontainer toolchain into the runner would roughly double its size to +# re-run work that ci/l0.sh + ci/l1.sh already did offline in the same CI run. So the Job +# must select the cluster-gated layers only, e.g. `args: ["--layer", "l2"]` / +# `["--layer", "l3"]`, where offline_selected_dirs() resolves to nothing and +# run_offline_checks() returns 0. `--layer all` and a bare `--requirements ` would drag +# the L0 checks in and fail on a missing tool. Flagged to the plan owner; choosing the Job's +# args is T-6.1b's call, not this image's. +# +# THE ADMISSION CONTRACT THIS IMAGE MUST SATISFY. +# platform/kyverno/admission/conformance-trigger-job-shape.yaml is a fail-closed Enforce +# ClusterPolicy (R-SEC-02) and it is the security boundary. It requires the Job's container +# to be `gitea.home.keithjackson.dev/home-network/conformance-runner@sha256:*` — a DIGEST, +# never a tag — with `runAsNonRoot: true` and `allowPrivilegeEscalation: false`. Two +# consequences are baked in here: +# +# 1. `USER` is NUMERIC. With `runAsNonRoot: true` and no explicit `runAsUser`, the kubelet +# REJECTS an image whose USER is a name it cannot resolve to a uid +# ("container has runAsNonRoot and image has non-numeric user"). So: USER 65532:65532. +# 2. The pod is `readOnlyRootFilesystem: true` and the policy admits only the `workspace` +# and `results` volumes — there is no writable /tmp and one cannot be added without +# changing the boundary. So HOME and every cache path point INTO /workspace (the +# emptyDir), not at the read-only root. That is why HOME=/workspace below. +# ============================================================================= + +# --- builder: fetch the pinned release binaries ------------------------------ +# A full Fedora for the download stage (it has curl + tar); none of it ships in the runtime +# image, so the runner does not carry a downloader it has no business having at runtime. +ARG FEDORA_VERSION=44 +FROM registry.fedoraproject.org/fedora:${FEDORA_VERSION} AS builder + +ARG CHAINSAW_VERSION= +ARG KUBECTL_VERSION= +ARG TARGETARCH=amd64 + +# Fail the BUILD — not the Job, three weeks later — if a pin was not passed in. +RUN set -eux; \ + : "${CHAINSAW_VERSION:?pass --build-arg CHAINSAW_VERSION from infra/devcontainer/versions.env}"; \ + : "${KUBECTL_VERSION:?pass --build-arg KUBECTL_VERSION from infra/devcontainer/versions.env}" + +# Same URLs and the same arch handling as infra/devcontainer/Containerfile, so the chainsaw +# and kubectl in this image are the same builds the toolbox gate runs with. kubectl's +# published sha256 is verified; chainsaw publishes no per-asset checksum file at a stable +# URL, so its integrity rests on the tag + TLS (and, after the operator pushes, on this +# image's own digest). +RUN set -eux; \ + mkdir -p /out; \ + curl -fsSL "https://dl.k8s.io/release/v${KUBECTL_VERSION}/bin/linux/${TARGETARCH}/kubectl" \ + -o /out/kubectl; \ + curl -fsSL "https://dl.k8s.io/release/v${KUBECTL_VERSION}/bin/linux/${TARGETARCH}/kubectl.sha256" \ + -o /tmp/kubectl.sha256; \ + echo "$(cat /tmp/kubectl.sha256) /out/kubectl" | sha256sum -c -; \ + curl -fsSL "https://github.com/kyverno/chainsaw/releases/download/v${CHAINSAW_VERSION}/chainsaw_linux_${TARGETARCH}.tar.gz" \ + | tar -xz -C /out chainsaw; \ + chmod 0755 /out/kubectl /out/chainsaw; \ + /out/kubectl version --client=true --output=yaml >/dev/null; \ + /out/chainsaw version >/dev/null + +# --- runtime ----------------------------------------------------------------- +ARG FEDORA_VERSION=44 +FROM registry.fedoraproject.org/fedora-minimal:${FEDORA_VERSION} + +ARG CHAINSAW_VERSION= +ARG KUBECTL_VERSION= +ARG FEDORA_VERSION=44 +# Nonroot uid/gid. 65532 is the distroless `nonroot` convention; the value is arbitrary but +# it MUST be numeric and non-zero — see "THE ADMISSION CONTRACT" above. +ARG RUNNER_UID=65532 +ARG RUNNER_GID=65532 + +LABEL org.opencontainers.image.title="conformance-runner" \ + org.opencontainers.image.description="bash + python3 + chainsaw + kubectl + git for the in-cluster L2/L3 conformance Job (conformance/run.sh)" \ + org.opencontainers.image.source="https://gitea.home.keithjackson.dev/Home-Network/home-network-k3s-impl" \ + org.opencontainers.image.documentation="conformance/incluster/README.md" \ + homelab.keithjackson.dev/requirements="R-TEST-02,R-SEC-02" + +# bash: run.sh + every check.sh. python3: the stdlib report generators. +# git-core: the init-container clone (git-core, not git — drops the perl/svn tail). +# coreutils/findutils/grep/sed/gawk: find, sort, base64, xargs, sed -nE, awk in lib/*.sh. +# tar+gzip: the base64-tar results channel. diffutils: `diff` in the L1 golden compare. +# ca-certificates: TLS for the init clone and for kubectl's in-cluster API calls. +RUN set -eux; \ + microdnf -y install --setopt=install_weak_deps=0 --nodocs \ + bash python3 git-core coreutils findutils grep sed gawk tar gzip diffutils \ + ca-certificates; \ + microdnf clean all; \ + rm -rf /var/cache/* /var/log/dnf* + +COPY --from=builder /out/kubectl /out/chainsaw /usr/local/bin/ + +# The nonroot account is written directly rather than pulling in shadow-utils to run useradd +# once. HOME is /workspace because at runtime that emptyDir is the only writable path. +RUN set -eux; \ + echo "runner:x:${RUNNER_UID}:${RUNNER_GID}:conformance runner:/workspace:/bin/bash" >> /etc/passwd; \ + echo "runner:x:${RUNNER_GID}:" >> /etc/group; \ + install -d -m 0775 -o "${RUNNER_UID}" -g "${RUNNER_GID}" /workspace + +# SYSTEM-level config, because HOME is an emptyDir that starts empty on every run and the +# root filesystem is read-only — a runtime `--global` write would fail. +# safe.directory=*: the init-container clones into the shared emptyDir and the main container +# then runs `rev-parse` over it; any uid mismatch between the two would otherwise abort with +# "detected dubious ownership". The repo is fetched fresh per run into an emptyDir, so there +# is no pre-existing hostile checkout this relaxes a check against. +RUN set -eux; \ + printf '%s\n' \ + '[safe]' \ + ' directory = *' \ + '[advice]' \ + ' detachedHead = false' \ + > /etc/gitconfig; \ + chmod 0644 /etc/gitconfig + +# Provenance: what is actually in this layer, readable from a running pod. +RUN set -eux; \ + { echo "fedora=${FEDORA_VERSION}"; \ + echo "chainsaw=${CHAINSAW_VERSION}"; \ + echo "kubectl=${KUBECTL_VERSION}"; \ + echo "bash=$(bash --version | head -1)"; \ + echo "python3=$(python3 --version)"; \ + echo "git=$(git --version)"; \ + } > /etc/conformance-runner-versions; \ + chmod 0444 /etc/conformance-runner-versions + +# Every writable path points into the `workspace` emptyDir — the root filesystem is +# read-only and the admission policy forbids adding a third volume for scratch. +ENV HOME=/workspace \ + XDG_CACHE_HOME=/workspace/.cache \ + XDG_CONFIG_HOME=/workspace/.config \ + TMPDIR=/workspace/.tmp \ + PYTHONDONTWRITEBYTECODE=1 \ + CONF_ROOT=/workspace/conformance + +USER ${RUNNER_UID}:${RUNNER_GID} +WORKDIR /workspace + +# No ENTRYPOINT on purpose. The admission policy pins `command: ["./run.sh"]` with +# `workingDir: /workspace/conformance`; an ENTRYPOINT here would either be ignored or +# prepend something the policy cannot see. `bash` is the interactive fallback for a human +# debugging the image locally. +CMD ["/bin/bash"] diff --git a/conformance/incluster/README.md b/conformance/incluster/README.md index 77e3021..a46cb07 100644 --- a/conformance/incluster/README.md +++ b/conformance/incluster/README.md @@ -27,7 +27,22 @@ credentials — the Job's own scoped ServiceAccount does (R-SEC-02). audit item. - `chainsaw-job.yaml` — the Job skeleton. Single entrypoint `./run.sh`; CI sets `args` to `--requirements ` (merge gate) or `--layer all --strict-coverage` (phase - exit). Image pinned to `versions.env` `CHAINSAW_IMAGE`. + exit). **Its `image:` is STALE**: it still names `ghcr.io/kyverno/chainsaw:v0.2.12`, which + (a) cannot run `./run.sh` at all and (b) is DENIED at admission. The trigger renders this + template per run with `CONFORMANCE_RUNNER_IMAGE` injected (plan §3 item 4, T-6.1b); until + that lands, treat the literal `image:` here as a placeholder, not a pin. +- `Containerfile` — the **`conformance-runner`** image: `bash` + `python3` + `chainsaw` + + `kubectl` + `git`, non-root (numeric uid 65532) and working under a read-only root + filesystem. This is what the Job actually needs; the stock chainsaw image carries none of + it. Versions are `--build-arg`-injected from `infra/devcontainer/versions.env` — the single + source of truth — so the chainsaw/kubectl in the runner are the same pins the toolbox gate + uses. Read its header before changing the Job's `args`: `run.sh` runs the selected offline + L0/L1 checks for any non-offline-only selection, and those need flux/promtool/kyverno/ + kustomize, which this image deliberately omits. Use `--layer l2` / `--layer l3`. +- `build-image.sh` — builds the above, **verifies the toolchain by running it** inside the + image as uid 65532 with `--read-only`, and prints the digest. It **never pushes**: it + prints the exact `podman push --digestfile …` command for the operator instead. See + `conformance/versions.env` `CONFORMANCE_RUNNER_IMAGE` for the post-push pin. ## Flow 1. CI (Gitea Actions, delivered in T-6.1b) creates a Job from `chainsaw-job.yaml` (via a diff --git a/conformance/incluster/build-image.sh b/conformance/incluster/build-image.sh new file mode 100755 index 0000000..c41817a --- /dev/null +++ b/conformance/incluster/build-image.sh @@ -0,0 +1,156 @@ +#!/usr/bin/env bash +############################################################################### +# conformance/incluster/build-image.sh — build the pinned `conformance-runner` +# image and print its digest. Closes docs/plans/l2-l3-conformance-ci-wiring.md §3 item 1. +# +# ./build-image.sh # build + verify + print the digest and push command +# ./build-image.sh --no-verify # build only (skip the in-image tool proof) +# ENGINE=docker ./build-image.sh # build with docker instead of podman +# +# THIS SCRIPT NEVER PUSHES. Publishing is an operator action: the registry credential is +# not an agent's to hold, and the pushed digest is what a fail-closed admission policy +# then trusts. So this builds locally, proves the toolchain works INSIDE the image as the +# non-root uid the policy demands, and prints the exact `podman push` line for a human. +# +# VERSIONS ARE NOT DEFINED HERE. They are read from infra/devcontainer/versions.env, the +# single source of truth (CHAINSAW_VERSION, KUBECTL_VERSION, FEDORA_VERSION) — the same +# pins the toolbox gate runs with. Nothing in this file invents a version. +# +# THE DIGEST CAVEAT — read it before pasting a digest into anything. +# The digest printed here is the LOCAL manifest digest. It is normally identical to the +# registry digest (podman pushes the manifest it already has rather than recompressing), +# but that is a property of the push, not a guarantee. So the push command below uses +# `--digestfile`: the digest the registry actually returns is the authoritative one, and +# that is the one that goes into conformance/versions.env. +############################################################################### +set -euo pipefail + +HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +REPO_ROOT="$(cd "${HERE}/../.." && pwd)" +ENGINE="${ENGINE:-podman}" + +VERIFY=1 +for arg in "$@"; do + case "$arg" in + --no-verify) VERIFY=0 ;; + -h|--help) sed -n '2,/^####/p' "${BASH_SOURCE[0]}" | sed '$d' | sed 's/^# \{0,1\}//'; exit 0 ;; + *) echo "unknown argument: $arg" >&2; exit 2 ;; + esac +done + +log() { printf '\n\033[1m==> %s\033[0m\n' "$*"; } +info() { printf ' %s\n' "$*"; } +die() { printf '\033[31mERROR: %s\033[0m\n' "$*" >&2; exit 1; } + +command -v "${ENGINE}" >/dev/null 2>&1 || die "'${ENGINE}' not on PATH (set ENGINE=docker to use docker)" + +# --- pins: sourced, never invented ------------------------------------------- +TOOLCHAIN_ENV="${REPO_ROOT}/infra/devcontainer/versions.env" +[ -f "${TOOLCHAIN_ENV}" ] || die "missing ${TOOLCHAIN_ENV} — the pins live there, not here" +# shellcheck disable=SC1090 +source "${TOOLCHAIN_ENV}" +: "${CHAINSAW_VERSION:?CHAINSAW_VERSION not set in ${TOOLCHAIN_ENV}}" +: "${KUBECTL_VERSION:?KUBECTL_VERSION not set in ${TOOLCHAIN_ENV}}" +: "${FEDORA_VERSION:?FEDORA_VERSION not set in ${TOOLCHAIN_ENV}}" + +# --- image name: fixed by the admission policy ------------------------------- +# platform/kyverno/admission/conformance-trigger-job-shape.yaml admits ONLY +# gitea.home.keithjackson.dev/home-network/conformance-runner@sha256:*. The repository path +# is therefore not a preference; a different path is a DENIED Job. Do not "improve" it here. +IMAGE_REPO="gitea.home.keithjackson.dev/home-network/conformance-runner" +# The tag is build convenience only (a human-readable handle for the push). Admission +# ignores it: only the digest is ever referenced from a Job. +IMAGE_TAG="chainsaw${CHAINSAW_VERSION}-kubectl${KUBECTL_VERSION}-fc${FEDORA_VERSION}" +IMAGE_REF="${IMAGE_REPO}:${IMAGE_TAG}" + +log "Building ${IMAGE_REF}" +info "engine ${ENGINE} ($(${ENGINE} --version 2>/dev/null | head -1))" +info "pins from ${TOOLCHAIN_ENV}" +info " CHAINSAW ${CHAINSAW_VERSION}" +info " KUBECTL ${KUBECTL_VERSION}" +info " FEDORA ${FEDORA_VERSION}" + +"${ENGINE}" build \ + --file "${HERE}/Containerfile" \ + --tag "${IMAGE_REF}" \ + --build-arg "CHAINSAW_VERSION=${CHAINSAW_VERSION}" \ + --build-arg "KUBECTL_VERSION=${KUBECTL_VERSION}" \ + --build-arg "FEDORA_VERSION=${FEDORA_VERSION}" \ + "${HERE}" + +# --- verify: the point of the whole exercise --------------------------------- +# The stock chainsaw image "had" chainsaw and still could not run ./run.sh. So prove the +# tools EXECUTE, and prove they execute as the NON-ROOT uid with a READ-ONLY root +# filesystem — the shape the admission policy forces. A pass here is the only evidence +# this image is usable; a build that merely succeeded is not. +if [ "${VERIFY}" -eq 1 ]; then + log "Verifying the toolchain inside the image (non-root, read-only rootfs)" + "${ENGINE}" run --rm \ + --user 65532:65532 \ + --read-only \ + --tmpfs /workspace:rw,mode=1777 \ + --entrypoint "" \ + "${IMAGE_REF}" \ + /bin/bash -euo pipefail -c ' + echo "--- id ---"; id + echo "--- HOME ---"; echo "HOME=${HOME}" + echo "--- bash ---"; bash --version | head -1 + echo "--- python3 ---"; python3 --version + echo "--- git ---"; git --version + echo "--- kubectl ---"; kubectl version --client + echo "--- chainsaw ---"; chainsaw version + echo "--- baked versions ---"; cat /etc/conformance-runner-versions + echo "--- rootfs is read-only (expected: a failure) ---" + if touch /should-fail 2>/dev/null; then echo "UNEXPECTED: rootfs is writable"; exit 1; fi + echo "confirmed: / is read-only" + echo "--- HOME is writable (the workspace emptyDir) ---" + touch "${HOME}/.probe" && echo "confirmed: ${HOME} is writable" + echo "--- uid is non-zero ---" + [ "$(id -u)" -ne 0 ] && echo "confirmed: running as uid $(id -u), not root" + ' + log "Toolchain verified" +fi + +# --- digest ------------------------------------------------------------------ +DIGEST="$("${ENGINE}" image inspect --format '{{.Digest}}' "${IMAGE_REF}")" +IMAGE_ID="$("${ENGINE}" image inspect --format '{{.Id}}' "${IMAGE_REF}")" +SIZE="$("${ENGINE}" image inspect --format '{{.Size}}' "${IMAGE_REF}")" + +log "Built ${IMAGE_REF}" +info "local manifest digest ${DIGEST}" +info "image id ${IMAGE_ID}" +info "size $(( SIZE / 1024 / 1024 )) MiB" + +cat < sha256:.... THIS is the authoritative digest, not the local one above. + + # conformance/versions.env + CONFORMANCE_RUNNER_IMAGE="${IMAGE_REPO}@\$(cat /tmp/conformance-runner.digest)" + +WHY THE DIGEST AND NOT THE TAG: platform/kyverno/admission/conformance-trigger-job-shape.yaml +is a fail-closed Enforce policy. It DENIES any conformance-trigger Job whose image is not +${IMAGE_REPO}@sha256:. A tag reference is denied, +and so is any other repository path. + +TIGHTEN THE BOUNDARY AFTERWARDS: that policy's header asks for the wildcard +\`${IMAGE_REPO}@sha256:*\` to be replaced with the +LITERAL digest once the image exists, closing the "anyone who can push to this path" gap. +That is a separate, human-reviewed PR against the security boundary. +================================================================================ +EOF diff --git a/conformance/versions.env b/conformance/versions.env index 269b927..3a4ee47 100644 --- a/conformance/versions.env +++ b/conformance/versions.env @@ -6,6 +6,10 @@ # Chainsaw — L1 rendering / L2 behavioral / L3 fault-injection runner (ADR-0011). CHAINSAW_VERSION="v0.2.12" +# The UPSTREAM chainsaw image. It is NOT what the in-cluster Job runs — see +# CONFORMANCE_RUNNER_IMAGE below. It carries the chainsaw binary and essentially nothing +# else (no bash, no python3, no kubectl, no git), so `command: ["./run.sh"]` on this image +# fails at exec. Kept only as the provenance of the chainsaw binary we rebuild from. CHAINSAW_IMAGE="ghcr.io/kyverno/chainsaw:v0.2.12" # kubectl — used by run.sh for cluster reachability preflight and by probe scripts. @@ -18,3 +22,46 @@ KYVERNO_VERSION="v1.19.0" # Python 3 — coverage generator + JUnit emitter use stdlib only (no pip deps), so the # only constraint is a modern interpreter. PYTHON_MIN="3.9" + +# --- In-cluster conformance runner image ------------------------------------- +# Built from conformance/incluster/Containerfile by conformance/incluster/build-image.sh. +# This — not CHAINSAW_IMAGE — is what the L2/L3 Job runs (bash + python3 + chainsaw + +# kubectl + git). Closes docs/plans/l2-l3-conformance-ci-wiring.md §3 item 1. +# +# THE REPOSITORY PATH IS NOT A PREFERENCE. platform/kyverno/admission/ +# conformance-trigger-job-shape.yaml is a fail-closed Enforce ClusterPolicy (R-SEC-02) that +# ADMITS a conformance-trigger Job only when its image matches +# gitea.home.keithjackson.dev/home-network/conformance-runner@sha256:* +# Any other path, and any TAG reference, is DENIED at admission. Do not "tidy" these values. +CONFORMANCE_RUNNER_IMAGE_REPO="gitea.home.keithjackson.dev/home-network/conformance-runner" +# Build handle only. Admission never sees the tag; a Job must reference the digest. +CONFORMANCE_RUNNER_IMAGE_TAG="chainsaw0.2.12-kubectl1.36.4-fc44" +# +# ############################################################################ +# # DIGEST DELIBERATELY EMPTY — THE IMAGE HAS NOT BEEN PUBLISHED YET. # +# ############################################################################ +# The image has been BUILT and its toolchain verified locally (non-root uid 65532, +# read-only rootfs: bash 5.3.9, Python 3.14.7, git 2.55.0, kubectl v1.36.4, chainsaw 0.2.12, +# and `./run.sh --layer l2 --no-cluster` completing with rc 0). It has NOT been pushed: +# pushing needs a registry credential, and publishing the artifact a fail-closed security +# boundary then trusts is an OPERATOR action, not an agent's. +# +# OPERATOR, to complete the pin: +# 1. ./conformance/incluster/build-image.sh # rebuild, verify, print the commands +# 2. podman login gitea.home.keithjackson.dev +# 3. podman push --digestfile /tmp/conformance-runner.digest \ +# "${CONFORMANCE_RUNNER_IMAGE_REPO}:${CONFORMANCE_RUNNER_IMAGE_TAG}" +# 4. Set CONFORMANCE_RUNNER_IMAGE_DIGEST below to `cat /tmp/conformance-runner.digest` +# — the digest the REGISTRY returned, not the local manifest digest the build printed. +# 5. Land steps 4 in a PR (this file is GitOps input, not a live edit). +# +# UNTIL THEN: this is empty on purpose, NOT a placeholder to be guessed at. An empty value +# makes any consumer that renders the Job fail visibly; a fake or stale digest would instead +# produce an ImagePullBackOff that looks like a cluster fault. Do not invent one. +CONFORMANCE_RUNNER_IMAGE_DIGEST="" +CONFORMANCE_RUNNER_IMAGE="${CONFORMANCE_RUNNER_IMAGE_DIGEST:+${CONFORMANCE_RUNNER_IMAGE_REPO}@${CONFORMANCE_RUNNER_IMAGE_DIGEST}}" +# +# AFTERWARDS, TIGHTEN THE BOUNDARY: the header of conformance-trigger-job-shape.yaml asks for +# its `@sha256:*` wildcard to be replaced with the LITERAL digest once the image exists — +# that closes the residual "anyone who can push to this path" gap. Separate PR, human +# reviewed, because it edits the security boundary itself.