package main import ( "context" "github.com/guildhouse-cooperative/guildhouse-spire-plugins/pkg/gsap" ) type GsapAttestorConfig struct { ProcRoot string `hcl:"proc_root"` MaxDepth int `hcl:"max_depth"` } type GsapAttestor struct { procRoot string maxDepth int } func (a *GsapAttestor) Configure(cfg GsapAttestorConfig) error { a.procRoot = cfg.ProcRoot if a.procRoot == "" { a.procRoot = "/proc" } a.maxDepth = cfg.MaxDepth if a.maxDepth <= 0 { a.maxDepth = 10 } return nil } func (a *GsapAttestor) Attest(_ context.Context, pid int32) ([]string, error) { currentPid := pid for depth := 0; depth < a.maxDepth; depth++ { env, err := readProcEnviron(a.procRoot, currentPid) if err != nil { return nil, nil } if isGoverned(env) { return extractSelectors(env), nil } parentPid, err := getParentPid(a.procRoot, currentPid) if err != nil || parentPid <= 1 || parentPid == currentPid { break } currentPid = parentPid } return nil, nil } // governanceMarkers are env vars whose presence indicates a governed shell. var governanceMarkers = []string{ "BASCULE_PRINCIPAL", "GSH_DID", "BASCULE_SESSION_ID", "BASCULE_AUTH_METHOD", } func isGoverned(env map[string]string) bool { for _, key := range governanceMarkers { if v, ok := env[key]; ok && v != "" { return true } } return false } type envMapping struct { envVar string selectorKey string transform func(string) string } // envMappings defines the env var → selector mapping with priority ordering. // For selectors with fallbacks (e.g. principal_did), the primary source // is listed first and checked in extractSelectors before the fallback. var envMappings = []envMapping{ {"BASCULE_AUTH_METHOD", gsap.SelectorDriverID, nil}, {"BASCULE_SESSION_ID", gsap.SelectorContextID, nil}, {"BASCULE_CORPUS_CID", gsap.SelectorCorpusCID, nil}, {"GSH_ACCORD_TEMPLATE", gsap.SelectorAccordTemplate, nil}, {"GSH_PLAYBOOK", gsap.SelectorPlaybook, nil}, {"GSH_PARAMETERS_CID", gsap.SelectorParametersCID, nil}, {"GSH_SESSION_MODE", gsap.SelectorSessionMode, nil}, {"GSH_SHELL_CLASS", gsap.SelectorShellClass, nil}, } func extractSelectors(env map[string]string) []string { var selectors []string emit := func(key, value string) { if value != "" { selectors = append(selectors, gsap.FormatSelector(key, value)) } } // principal_did: GSH_DID takes priority over BASCULE_PRINCIPAL if v := env["GSH_DID"]; v != "" { emit(gsap.SelectorPrincipalDID, v) } else { emit(gsap.SelectorPrincipalDID, env["BASCULE_PRINCIPAL"]) } // posture_level: BASCULE_POSTURE_LEVEL takes priority over BASCULE_DEFCON_LEVEL if v := env["BASCULE_POSTURE_LEVEL"]; v != "" { emit(gsap.SelectorPostureLevel, v) } else { emit(gsap.SelectorPostureLevel, env["BASCULE_DEFCON_LEVEL"]) } // capability_mask: GSH_CAPABILITY_MASK (hex) takes priority, // then BASCULE_CAPABILITY_CEILING (name → hex translation) if v := env["GSH_CAPABILITY_MASK"]; v != "" { emit(gsap.SelectorCapabilityMask, v) } else if v := env["BASCULE_CAPABILITY_CEILING"]; v != "" { if hex, ok := gsap.CapabilityCeilingToHex[v]; ok { emit(gsap.SelectorCapabilityMask, hex) } } // Simple 1:1 mappings for _, m := range envMappings { v := env[m.envVar] if v == "" { continue } if m.transform != nil { v = m.transform(v) } emit(m.selectorKey, v) } return selectors }