Bound private token input without terminal truncation

The executable overflow regression and a bounded Bash PTY probe show canonical terminals truncate long input before the size check. Read a bounded character count with echo held off, drain an invalid line before returning to the calling shell, and restore its modes on every exit. Pin maximum-size preservation, overflow history safety and terminal restoration.

Contract-Neutral: Repair bounded private terminal entry, overflow draining and mode restoration; no issuance, scope, install transport or response-shape delta.
Change-source: pulse-maintainer
This commit is contained in:
pulse-triage[bot]
2026-10-01 10:04:11 +01:00
parent 166f6d8aae
commit 413d5ae7ea
3 changed files with 65 additions and 2 deletions
@@ -47,6 +47,52 @@ func TestContainerBootstrapHistoryAndShellData(t *testing.T) {
assertContainerBootstrapCleanup(t, root)
}
func TestContainerBootstrapOverflowDoesNotBecomeShellHistory(t *testing.T) {
root, env := bootstrapFixture(t, recordingInstaller)
token := strings.Repeat("x", 10000) + "-OVERFLOW_HISTORY_MARKER"
command := BuildContainerRuntimeAgentInstallCommand("https://pulse.example", "separate-issued-token", true)
history := filepath.Join(root, "shell-history")
payload, err := json.Marshal(map[string]string{"command": command, "input": token, "history": history})
if err != nil {
t.Fatal(err)
}
cmd := exec.Command("python3", "-c", bootstrapPTYRunner)
cmd.Stdin = strings.NewReader(string(payload))
cmd.Env = append(os.Environ(), env...)
out, err := cmd.CombinedOutput()
if err == nil {
t.Fatal("overflowed input was accepted")
}
hist, err := os.ReadFile(history)
if err != nil || !strings.Contains(string(hist), command) {
t.Fatal("history control did not record the copied command")
}
for _, output := range []string{string(out), string(hist)} {
if strings.Contains(output, "OVERFLOW_HISTORY_MARKER") || strings.Contains(output, strings.Repeat("x", 512)) {
t.Fatal("discarded credential input was echoed or returned to shell history")
}
}
if _, err := os.Stat(filepath.Join(root, "captured-token")); !os.IsNotExist(err) {
t.Fatal("installer ran after overflowed input")
}
assertContainerBootstrapCleanup(t, root)
}
func TestContainerBootstrapPreservesMaximumAcceptedInput(t *testing.T) {
root, env := bootstrapFixture(t, recordingInstaller)
token := strings.Repeat("a", 4096)
command := BuildContainerRuntimeAgentInstallCommand("https://pulse.example", "separate-issued-token", true)
out, err := runBootstrap(t, command, token, env, true)
if err != nil {
t.Fatalf("maximum-size private input: %v\n%s", err, out)
}
captured, err := os.ReadFile(filepath.Join(root, "captured-token"))
if err != nil || string(captured) != token {
t.Fatal("terminal silently truncated an accepted token")
}
assertContainerBootstrapCleanup(t, root)
}
func assertContainerBootstrapCleanup(t *testing.T, root string) {
t.Helper()
directories, err := os.ReadFile(filepath.Join(root, "private-directories"))
+11 -2
View File
@@ -83,9 +83,18 @@ func privateBootstrapCommand(scriptURL, curlFlags, preflight, run string, needsT
privileged = append(privileged,
`token_dir=$(mktemp -d /tmp/pulse-agent-bootstrap.XXXXXX);`,
`token_file="$token_dir/token";`,
`cleanup() { unset pulse_token; rm -f -- "$token_file"; rmdir -- "$token_dir"; };`,
`unset pulse_token pulse_discard tty_state;`,
`cleanup() { unset pulse_token pulse_discard; if [ -n "${tty_state:-}" ]; then stty "$tty_state" </dev/tty 2>/dev/null || :; fi; rm -f -- "$token_file"; rmdir -- "$token_dir"; };`,
`trap cleanup EXIT; trap 'exit 129' HUP; trap 'exit 130' INT; trap 'exit 143' TERM;`,
`if ! IFS= read -r -s -p `+posixShellQuote(prompt+" (paste at this prompt, not in the command): ")+` pulse_token </dev/tty; then echo "A terminal is required to enter the token. No installation was attempted." >&2; exit 1; fi;`,
// A normal canonical tty silently truncates long lines (4095 bytes on
// Linux). Bounded character input preserves valid input and detects
// overflow; keep echo off while draining the rest of an invalid line
// so it cannot become a second command in the caller's shell history.
`if ! tty_state=$(stty -g </dev/tty); then echo "A terminal is required to enter the token. No installation was attempted." >&2; exit 1; fi;`,
`stty -echo </dev/tty;`,
`if ! IFS= read -r -s -p `+posixShellQuote(prompt+" (paste at this prompt, not in the command): ")+` -n 4097 pulse_token </dev/tty; then echo "Token input was interrupted. No installation was attempted." >&2; exit 1; fi;`,
`if [ "${#pulse_token}" -gt 4096 ]; then while IFS= read -r -s -n 4097 pulse_discard </dev/tty && [ "${#pulse_discard}" -eq 4097 ]; do :; done; unset pulse_discard; fi;`,
`stty "$tty_state" </dev/tty; unset tty_state;`,
`printf '\n' >/dev/tty;`,
`if [ -z "$pulse_token" ] || [ "${#pulse_token}" -gt 4096 ]; then echo "A non-empty token of at most 4096 characters is required." >&2; exit 1; fi;`,
`printf %s "$pulse_token" > "$token_file"; unset pulse_token;`,
@@ -16,6 +16,8 @@ const bootstrapPTYRunner = `
import errno, fcntl, json, os, pty, select, signal, subprocess, sys, termios, time
p = json.load(sys.stdin)
master, slave = pty.openpty()
tty_watch = os.dup(slave)
initial_tty = termios.tcgetattr(tty_watch)
def session():
os.setsid()
fcntl.ioctl(0, termios.TIOCSCTTY, 0)
@@ -48,6 +50,12 @@ else:
os.killpg(child.pid, signal.SIGKILL)
output += b"\nPTY fixture deadline expired\n"
code = child.wait()
final_tty = termios.tcgetattr(tty_watch)
mode_mask = termios.ECHO | termios.ICANON
if (initial_tty[3] & mode_mask) != (final_tty[3] & mode_mask):
output += b"\nBootstrap did not restore terminal echo/canonical mode\n"
code = 1
os.close(tty_watch)
os.close(master)
sys.stdout.buffer.write(output)
sys.exit(code if code >= 0 else 128 - code)