a73x

internal/arch/execwalk_test.go

Ref:   Size: 4.4 KiB   History

package arch

import (
	"slices"
	"sort"
	"strings"
	"testing"
)

// TestExecViolationsDetectsWrappers pins the transitive os/exec detector on a
// fake graph: a control-plane package must be flagged whether it imports
// os/exec directly OR reaches it through an internal wrapper package (the
// loophole a direct-import check misses). Allowed packages (provisioners and
// the composition root, per R6) are sanctioned exec users: reaching os/exec
// *via* them is fine, and they are themselves exempt.
func TestExecViolationsDetectsWrappers(t *testing.T) {
	const m = "github.com/a73x/eitri"
	graph := map[string][]string{
		m + "/internal/server/api":       {m + "/internal/util/execwrap", "net/http"},
		m + "/internal/util/execwrap":    {"os/exec", "fmt"},
		m + "/internal/server/store":     {m + "/internal/transport", "database/sql"},
		m + "/internal/transport":        {"crypto/tls"},
		m + "/internal/server/hub":       {"os/exec"}, // direct violation
		m + "/internal/agent/reconcile":  {m + "/internal/agent/cloudhv"},
		m + "/internal/agent/cloudhv":    {"os/exec", m + "/internal/util/spawnhelper"}, // sanctioned (allowed)
		m + "/internal/util/spawnhelper": {"os/exec"},
		// multi-hop: guarded -> clean intermediate -> wrapper -> os/exec
		m + "/internal/server/syncsvc": {m + "/internal/util/clean"},
		m + "/internal/util/clean":     {m + "/internal/util/execwrap"},
	}

	t.Run("server: direct and wrapper violations found, clean pkg not flagged", func(t *testing.T) {
		v := execViolations(graph, m, "internal/server/", nil)
		if got := v[m+"/internal/server/api"]; len(got) != 1 || got[0] != m+"/internal/util/execwrap" {
			t.Errorf("api must be flagged via internal/util/execwrap, got %v", got)
		}
		if got := v[m+"/internal/server/hub"]; len(got) != 1 || got[0] != m+"/internal/server/hub" {
			t.Errorf("hub must be flagged as a direct importer, got %v", got)
		}
		if got := v[m+"/internal/server/syncsvc"]; len(got) != 1 || got[0] != m+"/internal/util/execwrap" {
			t.Errorf("syncsvc must be flagged via the multi-hop chain, got %v", got)
		}
		if len(v[m+"/internal/server/store"]) != 0 {
			t.Errorf("store is clean but was flagged: %v", v[m+"/internal/server/store"])
		}
	})

	t.Run("agent: allowed package is exempt and does not taint importers", func(t *testing.T) {
		allowed := map[string]bool{
			m + "/internal/agent/cloudhv": true,
			// internal/cli: interactive sessions must be the real OpenSSH
			// client (TTY, escapes, agent forwarding); internal/cli execs it
			// deliberately.
			m + "/internal/cli": true,
		}
		v := execViolations(graph, m, "internal/agent/", allowed)
		// Includes the documented design decision: spawnhelper (a non-allowed
		// exec user) is reachable ONLY through cloudhv, so it is sanctioned by
		// extension — code in the plane can only reach it via cloudhv's API.
		if len(v) != 0 {
			t.Errorf("no agent violations expected (allowed packages and their subtrees are exempt), got %v", v)
		}
	})
}

// execViolations returns, for every package whose module-relative path has the
// given prefix, the set of packages through which os/exec becomes reachable:
// the package itself (direct import) and/or any transitively-reached internal
// package that imports os/exec. Walking never descends into (or flags)
// packages in allowed — their exec use is sanctioned (provisioners and the
// composition root, per R6).
//
// This closes the wrapper loophole: a direct-import check misses an internal
// package that wraps os/exec and is imported by the guarded plane.
func execViolations(graph map[string][]string, module, prefix string, allowed map[string]bool) map[string][]string {
	importsExec := func(pkg string) bool {
		return slices.Contains(graph[pkg], "os/exec")
	}

	out := map[string][]string{}
	for pkg := range graph {
		rel := strings.TrimPrefix(pkg, module+"/")
		if !strings.HasPrefix(rel, prefix) || allowed[pkg] {
			continue
		}
		var offenders []string
		if importsExec(pkg) {
			offenders = append(offenders, pkg)
		}
		// Walk transitive internal deps, skipping sanctioned packages.
		seen := map[string]bool{}
		var walk func(string)
		walk = func(p string) {
			for _, d := range graph[p] {
				if !strings.HasPrefix(d, module+"/") || seen[d] || allowed[d] {
					continue
				}
				seen[d] = true
				if importsExec(d) {
					offenders = append(offenders, d)
				}
				walk(d)
			}
		}
		walk(pkg)
		if len(offenders) > 0 {
			sort.Strings(offenders)
			out[pkg] = offenders
		}
	}
	return out
}