bastion 0.53.2
ci / personal-data (push) Successful in 5s
ci / test (push) Successful in 3m4s
ci / fuzz (push) Successful in 3m45s

This commit is contained in:
Privatae
2026-10-20 04:42:14 +00:00
commit e4c9b69b46
471 changed files with 159794 additions and 0 deletions
+158
View File
@@ -0,0 +1,158 @@
package main
// console_test.go — every child process this daemon starts on Windows must be
// started with no console window.
//
// WHY A SOURCE-LEVEL TEST. The defect is invisible on the platform the tests run
// on: on Linux there is no console to allocate, so a behavioural test of
// hideConsole passes vacuously here and the real thing can only be seen by a
// person looking at a Windows desktop. What CAN be checked anywhere is the
// property that actually matters — that no exec site was left out. The last
// one was found by watching two blue windows flash every ten minutes
// (console_windows.go); this is what stops the next shell-out doing it again.
//
// It asks the toolchain which files are in a Windows build (go/build's own
// MatchFile, as buildtags_test.go does) rather than reading name suffixes, so a
// file behind any constraint form is judged correctly.
import (
"go/ast"
"go/build"
"go/parser"
"go/token"
"os"
"strings"
"testing"
)
// exemptFromHideConsole: files whose exec sites are deliberately different.
var exemptFromHideConsole = map[string]string{
// The self-update relaunch MUST keep DETACHED_PROCESS — that flag is the
// whole point, it is what lets the replacement outlive the process being
// replaced. It has no console by construction and starts nothing visible.
"relaunch_windows.go": "sets DETACHED_PROCESS deliberately",
// The approvals popup starts Edge itself: a GUI program with no console to
// hide, where hideConsole's HideWindow (SW_HIDE in the start-up info) would
// ask Edge to open its first window hidden — the popup, invisible. Its one
// exec site is the Edge launch; the browser fallback goes through
// openBrowser, which hides rundll32's console as every other site does.
"uipopup_windows.go": "starts a GUI window that must be seen",
}
// isExecCommand reports whether a call is exec.Command or exec.CommandContext.
func isExecCommand(call *ast.CallExpr) bool {
sel, ok := call.Fun.(*ast.SelectorExpr)
if !ok {
return false
}
pkg, ok := sel.X.(*ast.Ident)
if !ok || pkg.Name != "exec" {
return false
}
return sel.Sel.Name == "Command" || sel.Sel.Name == "CommandContext"
}
// SCOPE, because a guard is only as wide as its walk:
// this reads THIS directory only. The bastion is a single-package module today
// — `go list ./...` is one package — so that is every exec site there is. The
// day a subpackage appears, its shell-outs are invisible here and this check
// goes on passing while the thing it protects stops being true. Widen the walk
// then; do not assume the walk widened itself.
func TestEveryWindowsExecSiteHidesItsConsole(t *testing.T) {
entries, err := os.ReadDir(".")
if err != nil {
t.Fatal(err)
}
win := build.Default
win.GOOS = "windows"
checked := 0
for _, e := range entries {
name := e.Name()
if e.IsDir() || !strings.HasSuffix(name, ".go") || strings.HasSuffix(name, "_test.go") {
continue
}
if _, skip := exemptFromHideConsole[name]; skip {
continue
}
onWin, err := win.MatchFile(".", name)
if err != nil {
t.Fatalf("%s: %v", name, err)
}
if !onWin {
continue // never compiled for Windows; there is no window to hide
}
fset := token.NewFileSet()
f, err := parser.ParseFile(fset, name, nil, 0)
if err != nil {
t.Fatalf("%s: %v", name, err)
}
// One top-level declaration at a time, so a `var x = func(){…}` (as
// observe_ops.go uses) is one scope and the name match stays local.
for _, decl := range f.Decls {
var bound []struct {
name string
line int
}
hidden := map[string]bool{}
ast.Inspect(decl, func(n ast.Node) bool {
switch node := n.(type) {
case *ast.AssignStmt:
// x := exec.Command(…) — the shape that CAN be hidden.
if len(node.Rhs) == 1 && len(node.Lhs) >= 1 {
if call, ok := node.Rhs[0].(*ast.CallExpr); ok && isExecCommand(call) {
if id, ok := node.Lhs[0].(*ast.Ident); ok {
bound = append(bound, struct {
name string
line int
}{id.Name, fset.Position(node.Pos()).Line})
}
}
}
case *ast.CallExpr:
// hideConsole(x)
if id, ok := node.Fun.(*ast.Ident); ok && id.Name == "hideConsole" {
if len(node.Args) == 1 {
if arg, ok := node.Args[0].(*ast.Ident); ok {
hidden[arg.Name] = true
}
}
}
// exec.Command(…).Output() — nothing is bound, so nothing
// can be hidden. Must be split into a variable first.
if sel, ok := node.Fun.(*ast.SelectorExpr); ok {
if inner, ok := sel.X.(*ast.CallExpr); ok && isExecCommand(inner) {
t.Errorf("%s:%d — exec.Command(…).%s() is started inline, so no "+
"hideConsole can be applied and it opens a console window on "+
"Windows. Assign it first:\n"+
"\tcmd := exec.CommandContext(…)\n\thideConsole(cmd)\n\t… cmd.%s()",
name, fset.Position(node.Pos()).Line, sel.Sel.Name, sel.Sel.Name)
}
}
}
return true
})
for _, b := range bound {
checked++
if !hidden[b.name] {
t.Errorf("%s:%d — %s is an exec.Cmd that is never passed to hideConsole, "+
"so on Windows it opens a console window on the user's desktop every "+
"time it runs. Add `hideConsole(%s)` before it is started (and AFTER "+
"any assignment to %s.SysProcAttr, which would otherwise replace the "+
"flags). See console_windows.go.",
name, b.line, b.name, b.name, b.name)
}
}
}
}
// A guard that checked nothing would pass for ever after a refactor moved
// every exec site behind a helper this walk does not recognise.
if checked < 8 {
t.Fatalf("only %d exec site(s) examined — this check has stopped finding them, "+
"which makes it a green light that proves nothing", checked)
}
}