Pulse/scripts/installtests/install_mcp_test.go
pulse-triage[bot] 445ebacf24
Some checks are pending
Build and Test / Secret Scan (push) Waiting to run
Build and Test / Detect changed areas (push) Waiting to run
Build and Test / Frontend (push) Blocked by required conditions
Build and Test / Backend tests (api) (push) Blocked by required conditions
Build and Test / Backend tests (rest-0) (push) Blocked by required conditions
Build and Test / Backend tests (rest-1) (push) Blocked by required conditions
Build and Test / Script smoke tests & backend build (push) Blocked by required conditions
Build and Test / Benchmarks (push) Blocked by required conditions
Canonical Governance / governance (push) Waiting to run
Canonical Private Governance / private-governance (push) Waiting to run
Helm CI / Lint and Render Chart (push) Waiting to run
Public docs / check (push) Waiting to run
Core E2E Tests / Playwright Core E2E (shard 4/8) (push) Blocked by required conditions
Core E2E Tests / Playwright Core E2E (shard 5/8) (push) Blocked by required conditions
Core E2E Tests / Playwright Core E2E (shard 6/8) (push) Blocked by required conditions
Core E2E Tests / Playwright Core E2E (shard 7/8) (push) Blocked by required conditions
Core E2E Tests / Playwright Core E2E (shard 8/8) (push) Blocked by required conditions
Core E2E Tests / Validate E2E tier selection (push) Waiting to run
Core E2E Tests / Playwright Core E2E (shard 1/8) (push) Blocked by required conditions
Core E2E Tests / Playwright Core E2E (shard 2/8) (push) Blocked by required conditions
Core E2E Tests / Playwright Core E2E (shard 3/8) (push) Blocked by required conditions
Core E2E Tests / Agent registration lifecycle (push) Waiting to run
Core E2E Tests / E2E verdict (push) Blocked by required conditions
Unified Agent Native Verification / FreeBSD cross-build contract (push) Waiting to run
Unified Agent Native Verification / Linux ARM64 (push) Waiting to run
Unified Agent Native Verification / Linux x64 (push) Waiting to run
Unified Agent Native Verification / Windows x64 (push) Waiting to run
Unified Agent Native Verification / macOS ARM64 (push) Waiting to run
Unified Agent Native Verification / macOS Intel (push) Waiting to run
Restore delivery qualification trust
Harden release workflow input transport and make native lifecycle proof honor each platform security boundary. Refresh stale telemetry and delivery qualifications.

Contract-Neutral: Formatting-only catalog updates and browser-verified alert copy; release compiler and native lifecycle changes update their owning contracts.
2026-08-31 03:02:13 +01:00

223 lines
7 KiB
Go

package installtests
import (
"crypto/sha256"
"fmt"
"os"
"os/exec"
"path/filepath"
"runtime"
"strings"
"testing"
)
const productionMCPReleaseKey = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIMZd/DaH+BldzOkq1A8KVTcFk73nAyrE8aJOyf7i00jm pulse-installer"
func TestInstallMCPRequiresSignedChecksumEvidence(t *testing.T) {
for _, command := range []string{"bash", "ssh-keygen"} {
if _, err := exec.LookPath(command); err != nil {
t.Skipf("%s not installed", command)
}
}
binaryName := "pulse-mcp-" + runtime.GOOS + "-" + runtime.GOARCH
tests := []struct {
name string
manifest func(binary []byte) string
invalidSig bool
missingAsset string
wantSuccess bool
wantOutput string
}{
{
name: "valid signed manifest",
manifest: func(binary []byte) string {
digest := sha256.Sum256(binary)
return fmt.Sprintf("%x %s\n", digest, binaryName)
},
wantSuccess: true,
wantOutput: "release signature verified",
},
{
name: "manifest unavailable",
manifest: func(binary []byte) string {
digest := sha256.Sum256(binary)
return fmt.Sprintf("%x %s\n", digest, binaryName)
},
missingAsset: "checksums.txt",
wantOutput: "could not fetch checksums.txt; refusing unverified install",
},
{
name: "signature unavailable",
manifest: func(binary []byte) string {
digest := sha256.Sum256(binary)
return fmt.Sprintf("%x %s\n", digest, binaryName)
},
missingAsset: "checksums.txt.sshsig",
wantOutput: "could not fetch checksums.txt.sshsig; refusing unverified install",
},
{
name: "signature invalid",
manifest: func(binary []byte) string {
digest := sha256.Sum256(binary)
return fmt.Sprintf("%x %s\n", digest, binaryName)
},
invalidSig: true,
wantOutput: "cryptographic signature verification failed for checksums.txt",
},
{
name: "binary omitted",
manifest: func(binary []byte) string {
digest := sha256.Sum256(binary)
return fmt.Sprintf("%x another-file\n", digest)
},
wantOutput: "checksums.txt must contain exactly one valid SHA256 entry",
},
{
name: "binary duplicated",
manifest: func(binary []byte) string {
digest := sha256.Sum256(binary)
return fmt.Sprintf("%x %s\n%x %s\n", digest, binaryName, digest, binaryName)
},
wantOutput: "checksums.txt must contain exactly one valid SHA256 entry",
},
{
name: "digest mismatch",
manifest: func(_ []byte) string {
return strings.Repeat("0", 64) + " " + binaryName + "\n"
},
wantOutput: "sha256 mismatch for " + binaryName,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
tmp := t.TempDir()
fixtureDir := filepath.Join(tmp, "fixtures")
binDir := filepath.Join(tmp, "bin")
installDir := filepath.Join(tmp, "install")
for _, dir := range []string{fixtureDir, binDir, installDir} {
if err := os.MkdirAll(dir, 0o755); err != nil {
t.Fatalf("mkdir %s: %v", dir, err)
}
}
binary := []byte("test pulse-mcp executable\n")
writeTestFile(t, filepath.Join(fixtureDir, binaryName), binary, 0o644)
manifestPath := filepath.Join(fixtureDir, "checksums.txt")
writeTestFile(t, manifestPath, []byte(tt.manifest(binary)), 0o644)
privateKeyPath := filepath.Join(tmp, "release-key")
runTestCommand(t, exec.Command("ssh-keygen", "-q", "-t", "ed25519", "-N", "", "-C", "pulse-installer", "-f", privateKeyPath))
publicKeyBytes, err := os.ReadFile(privateKeyPath + ".pub")
if err != nil {
t.Fatalf("read test public key: %v", err)
}
publicKey := strings.TrimSpace(string(publicKeyBytes))
sign := exec.Command("ssh-keygen", "-q", "-Y", "sign", "-f", privateKeyPath, "-n", "pulse-install", manifestPath)
runTestCommand(t, sign)
if err := os.Rename(manifestPath+".sig", filepath.Join(fixtureDir, "checksums.txt.sshsig")); err != nil {
t.Fatalf("rename signature: %v", err)
}
if tt.invalidSig {
writeTestFile(t, filepath.Join(fixtureDir, "checksums.txt.sshsig"), []byte("not a signature\n"), 0o644)
}
scriptBytes, err := os.ReadFile(repoFile("scripts", "install-mcp.sh"))
if err != nil {
t.Fatalf("read installer: %v", err)
}
script := strings.Replace(string(scriptBytes), productionMCPReleaseKey, publicKey, 1)
if script == string(scriptBytes) {
t.Fatal("installer no longer contains the expected pinned production key")
}
scriptPath := filepath.Join(tmp, "install-mcp.sh")
writeTestFile(t, scriptPath, []byte(script), 0o755)
curlStub := `#!/bin/sh
set -eu
output=''
url=''
while [ "$#" -gt 0 ]; do
case "$1" in
-o) output="$2"; shift 2 ;;
http*) url="$1"; shift ;;
*) shift ;;
esac
done
name="${url##*/}"
if [ -n "${MISSING_ASSET:-}" ] && [ "$name" = "$MISSING_ASSET" ]; then
exit 22
fi
cp "$FIXTURE_DIR/$name" "$output"
`
writeTestFile(t, filepath.Join(binDir, "curl"), []byte(curlStub), 0o755)
cmd := exec.Command("bash", scriptPath)
cmd.Env = append(os.Environ(),
"PATH="+binDir+string(os.PathListSeparator)+os.Getenv("PATH"),
"HOME="+tmp,
"FIXTURE_DIR="+fixtureDir,
"MISSING_ASSET="+tt.missingAsset,
"PULSE_MCP_BIN_DIR="+installDir,
"PULSE_MCP_VERSION=v-test",
)
output, runErr := cmd.CombinedOutput()
if tt.wantSuccess && runErr != nil {
t.Fatalf("installer failed: %v\n%s", runErr, output)
}
if !tt.wantSuccess && runErr == nil {
t.Fatalf("installer unexpectedly succeeded:\n%s", output)
}
if !strings.Contains(string(output), tt.wantOutput) {
t.Fatalf("installer output missing %q:\n%s", tt.wantOutput, output)
}
installedPath := filepath.Join(installDir, "pulse-mcp")
_, statErr := os.Stat(installedPath)
if tt.wantSuccess && statErr != nil {
t.Fatalf("verified binary was not installed: %v", statErr)
}
if !tt.wantSuccess && !os.IsNotExist(statErr) {
t.Fatalf("unverified binary reached install destination: stat err=%v", statErr)
}
})
}
}
func TestInstallMCPPowerShellFailsClosed(t *testing.T) {
path := repoFile("scripts", "install-mcp.ps1")
assertFileContainsAll(t, path,
"$PinnedReleaseSshPublicKey = 'ssh-ed25519 ",
"Get-SshKeygenPath",
"Assert-ChecksumManifestSignature $manifestPath $signaturePath",
"could not fetch checksums.txt; refusing unverified install",
"could not fetch checksums.txt.sshsig; refusing unverified install",
"checksums.txt must contain exactly one valid SHA256 entry",
"sha256 mismatch for ${binaryName}",
)
content, err := os.ReadFile(path)
if err != nil {
t.Fatalf("read PowerShell installer: %v", err)
}
for _, insecure := range []string{"PULSE_MCP_NO_VERIFY", "NoVerify", "skipping verification"} {
if strings.Contains(string(content), insecure) {
t.Fatalf("PowerShell installer retains fail-open control %q", insecure)
}
}
}
func writeTestFile(t *testing.T, path string, content []byte, mode os.FileMode) {
t.Helper()
if err := os.WriteFile(path, content, mode); err != nil {
t.Fatalf("write %s: %v", path, err)
}
}
func runTestCommand(t *testing.T, cmd *exec.Cmd) {
t.Helper()
if output, err := cmd.CombinedOutput(); err != nil {
t.Fatalf("%s failed: %v\n%s", strings.Join(cmd.Args, " "), err, output)
}
}