Initial commit
diff --git a/claudetool/bashkit/bashkit.go b/claudetool/bashkit/bashkit.go
new file mode 100644
index 0000000..a56eef0
--- /dev/null
+++ b/claudetool/bashkit/bashkit.go
@@ -0,0 +1,97 @@
+package bashkit
+
+import (
+	"fmt"
+	"strings"
+
+	"mvdan.cc/sh/v3/syntax"
+)
+
+var checks = []func(*syntax.CallExpr) error{
+	noGitConfigUsernameEmailChanges,
+}
+
+// Check inspects bashScript and returns an error if it ought not be executed.
+// Check DOES NOT PROVIDE SECURITY against malicious actors.
+// It is intended to catch straightforward mistakes in which a model
+// does things despite having been instructed not to do them.
+func Check(bashScript string) error {
+	r := strings.NewReader(bashScript)
+	parser := syntax.NewParser()
+	file, err := parser.Parse(r, "")
+	if err != nil {
+		// Execution will fail, but we'll get a better error message from bash.
+		// Note that if this were security load bearing, this would be a terrible idea:
+		// You could smuggle stuff past Check by exploiting differences in what is considered syntactically valid.
+		// But it is not.
+		return nil
+	}
+
+	syntax.Walk(file, func(node syntax.Node) bool {
+		if err != nil {
+			return false
+		}
+		callExpr, ok := node.(*syntax.CallExpr)
+		if !ok {
+			return true
+		}
+		for _, check := range checks {
+			err = check(callExpr)
+			if err != nil {
+				return false
+			}
+		}
+		return true
+	})
+
+	return err
+}
+
+// noGitConfigUsernameEmailChanges checks for git config username/email changes.
+// It uses simple heuristics, and has both false positives and false negatives.
+func noGitConfigUsernameEmailChanges(cmd *syntax.CallExpr) error {
+	if hasGitConfigUsernameEmailChanges(cmd) {
+		return fmt.Errorf("permission denied: changing git config username/email is not allowed, use env vars instead")
+	}
+	return nil
+}
+
+func hasGitConfigUsernameEmailChanges(cmd *syntax.CallExpr) bool {
+	if len(cmd.Args) < 3 {
+		return false
+	}
+	if cmd.Args[0].Lit() != "git" {
+		return false
+	}
+
+	configIndex := -1
+	for i, arg := range cmd.Args {
+		if arg.Lit() == "config" {
+			configIndex = i
+			break
+		}
+	}
+
+	if configIndex < 0 || configIndex == len(cmd.Args)-1 {
+		return false
+	}
+
+	// check for user.name or user.email
+	keyIndex := -1
+	for i, arg := range cmd.Args {
+		if i < configIndex {
+			continue
+		}
+		if arg.Lit() == "user.name" || arg.Lit() == "user.email" {
+			keyIndex = i
+			break
+		}
+	}
+
+	if keyIndex < 0 || keyIndex == len(cmd.Args)-1 {
+		return false
+	}
+
+	// user.name/user.email is followed by a value
+	return true
+}