From fc19019ce4acfc252f2a2921b4cd572261b62a6c Mon Sep 17 00:00:00 2001 From: ogt Date: Tue, 14 Jul 2026 19:10:56 +0800 Subject: [PATCH] fix(agent99): terminate remote stdin with sentinel --- .../deploy-agent99-via-windows99-ssh.sh | 29 +++++++++++++++++-- ...remote_atomic_deploy_transport_contract.py | 9 +++++- 2 files changed, 34 insertions(+), 4 deletions(-) diff --git a/scripts/reboot-recovery/deploy-agent99-via-windows99-ssh.sh b/scripts/reboot-recovery/deploy-agent99-via-windows99-ssh.sh index a8255ba77..03a40d40e 100755 --- a/scripts/reboot-recovery/deploy-agent99-via-windows99-ssh.sh +++ b/scripts/reboot-recovery/deploy-agent99-via-windows99-ssh.sh @@ -11,6 +11,7 @@ readonly GITEA_ORIGIN_SSH_INTERNAL="ssh://git@192.168.0.110:2222/wooo/awoooi.git readonly RECEIVER_RELATIVE="scripts/reboot-recovery/agent99-remote-atomic-deploy-receiver.ps1" readonly PREFLIGHT_RELATIVE="scripts/reboot-recovery/agent99-live-preflight.ps1" readonly WRAPPER_RELATIVE="scripts/reboot-recovery/deploy-agent99-via-windows99-ssh.sh" +readonly REMOTE_STDIN_SENTINEL="__AWOOOI_AGENT99_REMOTE_BOOTSTRAP_END_V1__" RUNTIME_FILES=( "agent99-bootstrap.ps1" @@ -303,7 +304,8 @@ PY python3 - \ "${BOOTSTRAP_PATH}" \ - "${BOOTSTRAP_PAYLOAD_PATH}" <<'PY' + "${BOOTSTRAP_PAYLOAD_PATH}" \ + "${REMOTE_STDIN_SENTINEL}" <<'PY' from __future__ import annotations import base64 @@ -312,13 +314,14 @@ import sys from pathlib import Path bootstrap_path, output_path = map(Path, sys.argv[1:3]) +sentinel = sys.argv[3] compressed = gzip.compress( bootstrap_path.read_bytes(), compresslevel=9, mtime=0, ) output_path.write_text( - base64.b64encode(compressed).decode("ascii"), + base64.b64encode(compressed).decode("ascii") + "\n" + sentinel + "\n", encoding="ascii", ) PY @@ -349,7 +352,27 @@ from __future__ import annotations import base64 -decoder = r'''$payload = [Console]::In.ReadToEnd() +decoder = r'''$sentinel = "__AWOOOI_AGENT99_REMOTE_BOOTSTRAP_END_V1__" +$lines = New-Object System.Collections.Generic.List[string] +$lineCount = 0 +$charCount = 0 +$terminated = $false +while (($line = [Console]::In.ReadLine()) -ne $null) { + if ($line -ceq $sentinel) { + $terminated = $true + break + } + $lineCount += 1 + $charCount += $line.Length + 2 + if ($lineCount -gt 8 -or $charCount -gt 1048576) { + throw "remote_bootstrap_input_limit_exceeded" + } + [void]$lines.Add($line) +} +if (-not $terminated) { + throw "remote_bootstrap_sentinel_missing" +} +$payload = [string]::Join("", $lines) $compressed = [Convert]::FromBase64String(($payload -replace '\s', '')) $inputStream = New-Object IO.MemoryStream(,$compressed) $gzip = New-Object IO.Compression.GzipStream($inputStream, [IO.Compression.CompressionMode]::Decompress) diff --git a/scripts/reboot-recovery/tests/test_agent99_remote_atomic_deploy_transport_contract.py b/scripts/reboot-recovery/tests/test_agent99_remote_atomic_deploy_transport_contract.py index b1ceea467..d7908d8f3 100644 --- a/scripts/reboot-recovery/tests/test_agent99_remote_atomic_deploy_transport_contract.py +++ b/scripts/reboot-recovery/tests/test_agent99_remote_atomic_deploy_transport_contract.py @@ -148,7 +148,14 @@ def test_sender_builds_sha256_envelope_and_streams_it_without_remote_cli_data() assert "IO.Compression.GzipStream" in source assert 'decoder.encode("utf-16le")' in source assert "-EncodedCommand" in source - assert "[Console]::In.ReadToEnd()" in source + assert "[Console]::In.ReadToEnd()" not in source + assert "$reader.ReadToEnd()" in source + assert "__AWOOOI_AGENT99_REMOTE_BOOTSTRAP_END_V1__" in source + assert "[Console]::In.ReadLine()" in source + assert "remote_bootstrap_input_limit_exceeded" in source + assert "remote_bootstrap_sentinel_missing" in source + assert "$lineCount -gt 8" in source + assert "$charCount -gt 1048576" in source assert '<"${BOOTSTRAP_PAYLOAD_PATH}"' in source assert "rawEnvelope" not in source assert "sshpass" not in source