From 29f8e4ee63653203f1e2f7a6ad1899cbade66ee9 Mon Sep 17 00:00:00 2001 From: alex <53851759+alxxjohn@users.noreply.github.com> Date: Tue, 18 Aug 2026 12:37:49 -0400 Subject: [PATCH 1/2] fix(security): trust-gate TypeScript semantic runtime --- docs/checks.md | 2 +- .../support/typescript_semantic_discovery.go | 33 ++----------------- .../support/typescript_semantic_test.go | 33 +++++++++++++++++++ 3 files changed, 36 insertions(+), 32 deletions(-) diff --git a/docs/checks.md b/docs/checks.md index ce2b5d51..89e3fbb6 100644 --- a/docs/checks.md +++ b/docs/checks.md @@ -408,7 +408,7 @@ Current inference behavior: TypeScript semantic runtime: - native TypeScript and JavaScript built-ins use the TypeScript compiler API when `typescript.js` is available -- discovery order is `CODEGUARD_TYPESCRIPT_LIB_PATH`, then `node_modules/typescript/lib/typescript.js` from the target path upward, then the bundled VS Code TypeScript runtime +- discovery uses an explicitly trusted `CODEGUARD_TYPESCRIPT_LIB_PATH`, then the bundled VS Code TypeScript runtime; repository-local `node_modules` runtimes are never executed automatically - if no runtime is available, codeguard falls back to the lightweight parser-based checks for TypeScript and JavaScript Tree-sitter parsing (opt-in): diff --git a/internal/codeguard/checks/support/typescript_semantic_discovery.go b/internal/codeguard/checks/support/typescript_semantic_discovery.go index fc81aeb5..911a3c13 100644 --- a/internal/codeguard/checks/support/typescript_semantic_discovery.go +++ b/internal/codeguard/checks/support/typescript_semantic_discovery.go @@ -2,7 +2,6 @@ package support import ( "os" - "path/filepath" "strings" ) @@ -12,11 +11,11 @@ var defaultTypeScriptLibCandidates = []string{ "/Applications/Visual Studio Code.app/Contents/Resources/app/extensions/node_modules/typescript/lib/typescript.js", } -func discoverTypeScriptLibPath(targetPath string) string { +func discoverTypeScriptLibPath(_ string) string { if candidate := strings.TrimSpace(os.Getenv(codeguardTypeScriptLibEnv)); isTypeScriptLibPath(candidate) { return candidate } - for _, candidate := range typeScriptLibCandidates(targetPath) { + for _, candidate := range defaultTypeScriptLibCandidates { if isTypeScriptLibPath(candidate) { return candidate } @@ -24,34 +23,6 @@ func discoverTypeScriptLibPath(targetPath string) string { return "" } -func typeScriptLibCandidates(targetPath string) []string { - candidates := make([]string, 0, 8) - for _, dir := range ancestorPaths(targetPath) { - candidates = append(candidates, filepath.Join(dir, "node_modules", "typescript", "lib", "typescript.js")) - } - return append(candidates, defaultTypeScriptLibCandidates...) -} - -func ancestorPaths(path string) []string { - path = strings.TrimSpace(path) - if path == "" { - return nil - } - current, err := filepath.Abs(path) - if err != nil { - current = path - } - paths := make([]string, 0, 6) - for { - paths = append(paths, current) - parent := filepath.Dir(current) - if parent == current { - return paths - } - current = parent - } -} - func isTypeScriptLibPath(path string) bool { if strings.TrimSpace(path) == "" { return false diff --git a/internal/codeguard/checks/support/typescript_semantic_test.go b/internal/codeguard/checks/support/typescript_semantic_test.go index a32f786c..77026b13 100644 --- a/internal/codeguard/checks/support/typescript_semantic_test.go +++ b/internal/codeguard/checks/support/typescript_semantic_test.go @@ -1,6 +1,7 @@ package support import ( + "os" "path/filepath" "reflect" "testing" @@ -8,6 +9,38 @@ import ( "github.com/devr-tools/codeguard/internal/codeguard/core" ) +func TestTypeScriptRuntimeDiscoveryIgnoresTargetDependencies(t *testing.T) { + t.Setenv(codeguardTypeScriptLibEnv, "") + previousDefaults := defaultTypeScriptLibCandidates + defaultTypeScriptLibCandidates = nil + t.Cleanup(func() { defaultTypeScriptLibCandidates = previousDefaults }) + + target := t.TempDir() + repositoryRuntime := filepath.Join(target, "node_modules", "typescript", "lib", "typescript.js") + if err := os.MkdirAll(filepath.Dir(repositoryRuntime), 0o755); err != nil { + t.Fatalf("create repository runtime directory: %v", err) + } + if err := os.WriteFile(repositoryRuntime, []byte("malicious JavaScript"), 0o644); err != nil { + t.Fatalf("write repository runtime: %v", err) + } + + if got := discoverTypeScriptLibPath(target); got != "" { + t.Fatalf("discovered repository-controlled TypeScript runtime %q", got) + } +} + +func TestTypeScriptRuntimeDiscoveryHonorsExplicitRuntime(t *testing.T) { + configuredRuntime := filepath.Join(t.TempDir(), "typescript.js") + if err := os.WriteFile(configuredRuntime, []byte("trusted JavaScript"), 0o644); err != nil { + t.Fatalf("write configured runtime: %v", err) + } + t.Setenv(codeguardTypeScriptLibEnv, configuredRuntime) + + if got := discoverTypeScriptLibPath(t.TempDir()); got != configuredRuntime { + t.Fatalf("discovered runtime = %q, want explicitly configured runtime %q", got, configuredRuntime) + } +} + func TestTypeScriptTargetSourceFilesUsesFilteredCorpus(t *testing.T) { target := core.TargetConfig{Path: "/repo"} files := TypeScriptTargetSourceFiles(Context{ From 46f1e3d94c1b416fa8669971e0fb7f080977270c Mon Sep 17 00:00:00 2001 From: alex <53851759+alxxjohn@users.noreply.github.com> Date: Tue, 18 Aug 2026 18:56:12 -0400 Subject: [PATCH 2/2] test(security): use restrictive fixture permissions --- .../codeguard/checks/support/typescript_semantic_test.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/internal/codeguard/checks/support/typescript_semantic_test.go b/internal/codeguard/checks/support/typescript_semantic_test.go index 39f7c080..78527a8f 100644 --- a/internal/codeguard/checks/support/typescript_semantic_test.go +++ b/internal/codeguard/checks/support/typescript_semantic_test.go @@ -20,10 +20,10 @@ func TestTypeScriptRuntimeDiscoveryIgnoresTargetDependencies(t *testing.T) { target := t.TempDir() repositoryRuntime := filepath.Join(target, "node_modules", "typescript", "lib", "typescript.js") - if err := os.MkdirAll(filepath.Dir(repositoryRuntime), 0o755); err != nil { + if err := os.MkdirAll(filepath.Dir(repositoryRuntime), 0o750); err != nil { t.Fatalf("create repository runtime directory: %v", err) } - if err := os.WriteFile(repositoryRuntime, []byte("malicious JavaScript"), 0o644); err != nil { + if err := os.WriteFile(repositoryRuntime, []byte("malicious JavaScript"), 0o600); err != nil { t.Fatalf("write repository runtime: %v", err) } @@ -34,7 +34,7 @@ func TestTypeScriptRuntimeDiscoveryIgnoresTargetDependencies(t *testing.T) { func TestTypeScriptRuntimeDiscoveryHonorsExplicitRuntime(t *testing.T) { configuredRuntime := filepath.Join(t.TempDir(), "typescript.js") - if err := os.WriteFile(configuredRuntime, []byte("trusted JavaScript"), 0o644); err != nil { + if err := os.WriteFile(configuredRuntime, []byte("trusted JavaScript"), 0o600); err != nil { t.Fatalf("write configured runtime: %v", err) } t.Setenv(codeguardTypeScriptLibEnv, configuredRuntime)