Bound signed manual-update bootstrap downloads (#3243)

**Bound signed manual-update bootstrap downloads**

Keep both bootstrap requests within curl's connection and whole-request
deadlines, without ambient retry settings. Arm cleanup before allocation
so a second mktemp failure cannot strand the first input. Exercise the
emitted helper's signed success, partial download failures, allocation
failures and signature rejection without native or production execution.

**Keep native recovery proof aligned with bounded manual downloads**

Admit only the manual helper's exact new curl envelope in the existing
closed transport fixture. Require a signed emitted-helper round trip and
reject altered deadlines, retries and TLS flags through the registered
installer suite; do not widen the adapter or execute a native journey.

**Preserve each configuration-backup safety control's deadline**

The containing installer suite exhausted a single 30-second Python-class
budget after eleven controls and killed the last. Use the existing
serial discovery runner with all twelve required methods, retaining each
control's 30-second bound and the existing inner shell bounds rather
than treating cumulative class time as a safety deadline.

---
Fleet candidate `20261025T062429Z-delivery-trust` from the
`delivery-trust` lane. Reviewed tip
`b6618c19a3f2925fff019db2651d885ad1ede9b8` on `main`.

[How Pulse handles
triage](https://github.com/rcourtman/Pulse/blob/main/docs/AI_TRANSPARENCY.md)
This commit is contained in:
pulse-triage[bot]
2026-10-25 07:54:16 +00:00
committed by GitHub
7 changed files with 258 additions and 10 deletions
@@ -6364,6 +6364,35 @@ The helper forwards both parsed channels explicitly, including the stable
default for an existing file, so the containing installer cannot reinterpret
that same configuration through its older saved-preference reader.
The helper fetches its installer and signature separately with ten-second
connection and sixty-second whole-request limits, without implicit retries.
Curl's first `-q` option disables ambient configuration, which otherwise could
introduce retries beyond those limits. Cleanup is armed before either temporary
input is allocated, so failure of the second allocation also removes the first.
A failed or partial download stops before signature verification or installer
execution; signature failure still forbids execution. Saved settings remain
untouched, and failed bootstrap cannot print the successful-update message.
`TestRootManualUpdateDownloadDeadlines` executes the emitted helper with a real
SSH verifier and modelled transport/allocation faults. Six controls cover both
partial-download timeouts, both allocation failures, invalid signatures and a
successful signed bootstrap. They require the deadline/config-isolation options,
no request replay, temporary-input cleanup and saved-configuration preservation.
They do not measure live network deadlines or establish installed/native recovery.
The native systemd proof's closed transport adapter separately admits this exact
manual-helper envelope alongside the existing unattended envelope. Its registered
portable control runs the actual emitted helper and real verifier through that
adapter, and rejects altered deadlines, retries or TLS options without online
fallback. This preserves the hosted manual-recovery consumer, not native acceptance.
Configuration-backup publication verification also uses the existing serial
per-control runner. All twelve current methods are required, all discovered
future methods run, and each retains the thirty-second execution bound. A
single cumulative suite budget must not cut off the final safety control.
The Python controls' own ten-second shell bounds and every backup assertion
remain unchanged; no host-contention diagnosis or production deadline change
follows from a cumulative harness timeout.
`TestManualUpdateChannelAdmission` generates and executes the actual helper
against local transport fixtures and genuinely signed installer/sidecar bytes.
It checks final arguments and the actual installer channel-selection functions,
+7 -3
View File
@@ -4353,14 +4353,18 @@ if [[ \${#helper_args[@]} -gt 0 ]]; then
fi
echo "Updating Pulse..."
tmp_installer=""
tmp_signature=""
trap 'rm -f "\$tmp_installer" "\$tmp_signature"' EXIT
tmp_installer=\$(mktemp /tmp/pulse-update-installer.XXXXXX)
tmp_signature=\$(mktemp /tmp/pulse-update-installer.sig.XXXXXX)
trap 'rm -f "\$tmp_installer" "\$tmp_signature"' EXIT
if ! curl -fsSL "\$INSTALLER_URL" -o "\$tmp_installer"; then
# Do not let ambient curl retries or a stalled bootstrap hold update forever.
# No installer/service change is allowed until both bounded downloads verify.
if ! curl -q -fsSL --connect-timeout 10 --max-time 60 "\$INSTALLER_URL" -o "\$tmp_installer"; then
echo "Failed to download Pulse installer from \$INSTALLER_URL" >&2
exit 1
fi
if ! curl -fsSL "\$INSTALLER_SIG_URL" -o "\$tmp_signature"; then
if ! curl -q -fsSL --connect-timeout 10 --max-time 60 "\$INSTALLER_SIG_URL" -o "\$tmp_signature"; then
echo "Failed to download Pulse installer signature from \$INSTALLER_SIG_URL" >&2
exit 1
fi
@@ -65,8 +65,11 @@ if pathlib.Path(sys.argv[0]).name == "mktemp":
# arbitrary curl option parser. The short form is used by isolated controls.
flags = ["--disable", "-fsSL", "--connect-timeout", "5", "--max-time", "60",
"--proto", "=https", "--proto-redir", "=https"]
manual_flags = ["-q", "-fsSL", "--connect-timeout", "10", "--max-time", "60"]
if args[:len(flags)] == flags:
args = args[len(flags):]
elif args[:len(manual_flags)] == manual_flags:
args = args[len(manual_flags):]
elif args[:1] == ["-fsSL"]:
args = args[1:]
else:
@@ -169,7 +169,8 @@ while [[ $# -gt 0 ]]; do
case "$1" in
-o) dest="$2"; shift 2 ;;
https://github.com/rcourtman/Pulse/releases/latest/download/install.sh*) url="$1"; shift ;;
-fsSL) shift ;;
-q|-fsSL) shift ;;
--connect-timeout|--max-time) shift 2 ;;
*) exit 98 ;;
esac
done
@@ -33,6 +33,7 @@ func TestAutoUpdateNativeSystemdHarnessContracts(t *testing.T) {
for _, name := range []string{
"test_prepare_owns_generated_signer_mode_before_first_real_signature",
"test_signer_creation_refuses_adoption_wrong_type_owner_or_failed_mode",
"test_transport_accepts_emitted_manual_helper_and_rejects_option_drift",
"test_real_verifier_accepts_fixture_and_refuses_tampering",
"test_phase_cannot_accept_wrong_bytes_state_mask_persistence_or_backup",
"test_managed_process_identity_follows_served_readiness_and_rejects_residuals",
+170 -6
View File
@@ -2751,12 +2751,176 @@ fi
}
func TestRootInstallScriptConfigBackupPublicationPreservesPriorSnapshots(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
cmd := exec.CommandContext(ctx, "python3", repoFile("scripts", "tests", "test_server_config_backups.py"), "-v")
cmd.Env = append(os.Environ(), "PULSE_INSTALLER_UNDER_TEST="+repoFile("install.sh"))
out, err := cmd.CombinedOutput()
// A cumulative suite budget is not an individual backup-control deadline.
// Keep all discovered controls serial and retain the existing per-control bound.
runPythonFixtureControls(t, repoFile("scripts", "tests", "test_server_config_backups.py"), "ConfigBackupPublication", []string{
"test_two_successful_same_second_copies_are_separate_complete_snapshots",
"test_failed_partial_copy_does_not_mutate_or_delete_a_previous_snapshot",
"test_allocation_failure_stops_before_copy_or_rotation",
"test_headroom_failure_creates_no_stage_and_preserves_prior_snapshot",
"test_failed_publication_does_not_rotate_or_publish_a_snapshot",
"test_exit_zero_skipped_publication_preserves_file_directory_and_link",
"test_current_snapshot_survives_rotation_even_after_clock_moves_backwards",
"test_incomplete_copy_is_not_another_invocations_rotation_candidate",
"test_discard_removes_only_this_invocations_new_snapshot",
"test_missing_config_clears_stale_ownership_without_deleting_prior_snapshot",
"test_read_only_config_parent_uses_the_existing_hardened_unit_fallback",
"test_every_update_entry_stops_on_failed_backup_even_in_an_or_list",
}, 30*time.Second, "PULSE_INSTALLER_UNDER_TEST="+repoFile("install.sh"))
}
// Run the emitted helper and real SSH verifier. Curl exits are modelled, so
// these controls prove deadline arguments and safe failure, not a live outage.
func TestRootManualUpdateDownloadDeadlines(t *testing.T) {
sourcePath := os.Getenv("PULSE_INSTALLER_UNDER_TEST")
if sourcePath == "" {
sourcePath = filepath.Join("..", "..", "install.sh")
}
source, err := filepath.Abs(sourcePath)
if err != nil {
t.Fatalf("configuration backup publication controls: %v\n%s", err, out)
t.Fatal(err)
}
assets := t.TempDir()
key := filepath.Join(assets, "key")
installer := filepath.Join(assets, "install.sh")
if err := os.WriteFile(installer, []byte("#!/bin/bash\nprintf '%s\\n' EXECUTED \"$@\" > \"$FIXTURE/executed\"\n"), 0600); err != nil {
t.Fatal(err)
}
for _, args := range [][]string{
{"-q", "-t", "ed25519", "-N", "", "-f", key},
{"-q", "-Y", "sign", "-f", key, "-n", "pulse-install", installer},
} {
if out, err := exec.Command("ssh-keygen", args...).CombinedOutput(); err != nil {
t.Fatalf("fixture signature: %v\n%s", err, out)
}
}
publicKey, err := os.ReadFile(key + ".pub")
if err != nil {
t.Fatal(err)
}
for _, fault := range []string{"success", "installer_timeout", "signature_timeout", "bad_signature", "first_allocation", "second_allocation"} {
t.Run(fault, func(t *testing.T) {
dir := t.TempDir()
for _, leaf := range []string{"config", "temporary", "tools"} {
if err := os.Mkdir(filepath.Join(dir, leaf), 0700); err != nil {
t.Fatal(err)
}
}
config := filepath.Join(dir, "config", "system.json")
const configBytes = `{"updateChannel":"stable","preserve":"fixture"}`
if err := os.WriteFile(config, []byte(configBytes), 0600); err != nil {
t.Fatal(err)
}
helper := filepath.Join(dir, "update")
env := append(os.Environ(), "FIXTURE="+dir, "FIXTURE_ASSETS="+assets, "FAULT="+fault,
"INSTALLER_SOURCE="+source, "FIXTURE_PUBLIC_KEY="+strings.TrimSpace(string(publicKey)),
"PULSE_INSTALL_DIR="+dir, "PULSE_CONFIG_DIR="+filepath.Join(dir, "config"),
"PULSE_UPDATE_HELPER_PATH="+helper, "PULSE_PROFILE_PATH="+filepath.Join(dir, "profile"),
"PULSE_BASHRC_PATH="+filepath.Join(dir, "bashrc"))
generate := exec.Command("bash", "-c", `source "$INSTALLER_SOURCE"
PINNED_RELEASE_SSH_PUBLIC_KEY="$FIXTURE_PUBLIC_KEY"
setup_update_command`)
generate.Env = env
if out, err := generate.CombinedOutput(); err != nil {
t.Fatalf("generate helper: %v\n%s", err, out)
}
tools := map[string]string{
"mktemp": `#!/bin/bash
set -eu
count=0
[[ ! -f "$FIXTURE/allocations" ]] || read -r count < "$FIXTURE/allocations"
count=$((count + 1))
printf '%s\n' "$count" > "$FIXTURE/allocations"
[[ "$FAULT:$count" != first_allocation:1 && "$FAULT:$count" != second_allocation:2 ]] || exit 1
exec /usr/bin/mktemp "$FIXTURE/temporary/input.XXXXXX"
`,
"curl": `#!/bin/bash
set -eu
[[ "$1" == -q ]] || { echo 'ambient curl configuration is not disabled' >&2; exit 99; }
url=""; dest=""; connect=""; maximum=""
while [[ $# -gt 0 ]]; do
case "$1" in
--connect-timeout) connect="$2"; shift 2 ;;
--max-time) maximum="$2"; shift 2 ;;
-o) dest="$2"; shift 2 ;;
-q|-fsSL) shift ;;
https://github.com/rcourtman/Pulse/releases/latest/download/install.sh*) url="$1"; shift ;;
*) exit 98 ;;
esac
done
printf '%s %s %s\n' "$url" "$connect" "$maximum" >> "$FIXTURE/transport"
[[ "$connect" == 10 && "$maximum" == 60 ]] || { echo 'missing bootstrap download deadline' >&2; exit 99; }
case "$url" in
*.sshsig) phase=signature; source="$FIXTURE_ASSETS/install.sh.sig" ;;
*/install.sh) phase=installer; source="$FIXTURE_ASSETS/install.sh" ;;
*) exit 97 ;;
esac
# Retain partial writes too: neither failure may execute the installer or
# leave a partial bootstrap input behind.
if [[ "$FAULT" == "${phase}_timeout" ]]; then
printf partial > "$dest"
exit 28
fi
if [[ "$phase:$FAULT" == signature:bad_signature ]]; then
printf invalid > "$dest"
else
cp "$source" "$dest"
fi
`,
}
for name, body := range tools {
if err := os.WriteFile(filepath.Join(dir, "tools", name), []byte(body), 0700); err != nil {
t.Fatal(err)
}
}
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
cmd := exec.CommandContext(ctx, "bash", helper)
cmd.Env = append(env, "PATH="+filepath.Join(dir, "tools")+string(os.PathListSeparator)+os.Getenv("PATH"))
out, runErr := cmd.CombinedOutput()
calls, callErr := os.ReadFile(filepath.Join(dir, "executed"))
if fault == "success" {
if runErr != nil || callErr != nil || string(calls) != "EXECUTED\n--stable\n" || !strings.Contains(string(out), "Update complete!") {
t.Fatalf("valid signed installer: %v, %v, %q\n%s", runErr, callErr, calls, out)
}
} else {
if runErr == nil || !os.IsNotExist(callErr) || strings.Contains(string(out), "Update complete!") {
t.Fatalf("failed bootstrap reported/executed success: %v, %v\n%s", runErr, callErr, out)
}
if strings.Contains(fault, "timeout") {
want := "Failed to download Pulse installer"
if fault == "signature_timeout" {
want += " signature"
}
if !strings.Contains(string(out), want) {
t.Fatalf("missing download failure diagnostic: %s", out)
}
}
}
if ctx.Err() != nil {
t.Fatalf("helper exceeded fixture bound: %v\n%s", ctx.Err(), out)
}
entries, err := os.ReadDir(filepath.Join(dir, "temporary"))
if err != nil || len(entries) != 0 {
t.Errorf("bootstrap left temporary inputs: %v, %v", entries, err)
}
got, err := os.ReadFile(config)
if err != nil || string(got) != configBytes {
t.Fatalf("bootstrap changed saved configuration: %v, %q", err, got)
}
transport, err := os.ReadFile(filepath.Join(dir, "transport"))
wantRequests := 2
if fault == "installer_timeout" {
wantRequests = 1
} else if strings.Contains(fault, "allocation") {
wantRequests = 0
}
if err != nil && !os.IsNotExist(err) {
t.Fatal(err)
}
if got := strings.Count(string(transport), "\n"); got != wantRequests {
t.Errorf("bootstrap requests = %d, want %d (no retries): %q", got, wantRequests, transport)
}
})
}
}
+46
View File
@@ -145,6 +145,52 @@ class NativeContracts(unittest.TestCase):
self.assertEqual(result.returncode, 97)
self.assertFalse((root / "refused").exists())
def test_transport_accepts_emitted_manual_helper_and_rejects_option_drift(self):
with tempfile.TemporaryDirectory() as directory:
root = Path(directory)
for leaf in ("tools", "tmp", "config"):
(root / leaf).mkdir(mode=0o700)
for name in ("curl", "mktemp"):
path = root / "tools" / name
path.write_text(native.TRANSPORT)
path.chmod(0o755)
installer = root / "installer"
installer.write_text('#!/bin/bash\nprintf "%s\\n" "$@" > "$PULSE_NATIVE_ROOT/executed"\n')
key = root / "key"
subprocess.run(["ssh-keygen", "-q", "-t", "ed25519", "-N", "", "-f", str(key)],
check=True, capture_output=True)
subprocess.run(["ssh-keygen", "-q", "-Y", "sign", "-f", str(key),
"-n", "pulse-install", str(installer)], check=True, capture_output=True)
Path(str(installer) + ".sig").rename(root / "installer.sshsig")
helper = root / "update"
env = {**os.environ, "PULSE_NATIVE_ROOT": str(root),
"FIXTURE_KEY": (root / "key.pub").read_text().strip(),
"PULSE_INSTALL_DIR": str(root), "PULSE_CONFIG_DIR": str(root / "config"),
"PULSE_UPDATE_HELPER_PATH": str(helper),
"PULSE_PROFILE_PATH": str(root / "profile"),
"PULSE_BASHRC_PATH": str(root / "bashrc"),
"PATH": str(root / "tools") + os.pathsep + os.environ["PATH"]}
generated = subprocess.run(["bash", "-c", 'source "$1"; '
'PINNED_RELEASE_SSH_PUBLIC_KEY="$FIXTURE_KEY"; setup_update_command',
"fixture", str(ROOT / "install.sh")], env=env, capture_output=True)
self.assertEqual(generated.returncode, 0, generated.stdout + generated.stderr)
result = subprocess.run(["bash", str(helper), "--version", "v0.0.1"],
env=env, capture_output=True, timeout=10)
self.assertEqual(result.returncode, 0, result.stdout + result.stderr)
self.assertEqual((root / "executed").read_text(), "--version\nv0.0.1\n")
self.assertEqual(list((root / "tmp").iterdir()), [])
flags = ["-q", "-fsSL", "--connect-timeout", "10", "--max-time", "60"]
url = "https://github.com/rcourtman/Pulse/releases/latest/download/install.sh"
for changed in (flags[1:], [*flags, "--insecure"], [*flags, "--retry", "3"],
[arg if arg != "60" else "61" for arg in flags],
[arg if arg != "10" else "11" for arg in flags]):
with self.subTest(flags=changed):
target = root / "refused"
result = subprocess.run([str(root / "tools/curl"), *changed, url, "-o", str(target)],
env=env, capture_output=True, timeout=10)
self.assertEqual(result.returncode, 97)
self.assertFalse(target.exists())
def test_real_verifier_accepts_fixture_and_refuses_tampering(self):
with tempfile.TemporaryDirectory() as directory:
root = Path(directory)