From 62c3da5018505c0dee07d4f47d1f5746956970a7 Mon Sep 17 00:00:00 2001 From: Yiftach Cohen Date: Tue, 9 Jun 2026 10:44:19 +0200 Subject: [PATCH 1/3] chore: upgrade golangci-lint CI version from v2.10.1 to v2.12.2 - Bump golangci-lint-action version in reusable-lint.yml - Add goconst.ignore-tests: true to suppress test fixture string noise - Add named constants throughout production code to satisfy stricter goconst thresholds in v2.12.x (install JSON keys, shell names, output colors, file extensions, lockfile protocol prefixes, etc.) - Add size guard to readJSONFileAsMap (CWE-770 fix) - Add #nosec/nolint/armis:ignore comments for new gosec rules: G117 (secret field marshal), G120 (multipart form in tests), G122 (filepath.Walk TOCTOU), and CWE-22/522 scanner FPs --- .github/workflows/reusable-lint.yml | 2 +- .golangci.yml | 2 ++ internal/api/client_test.go | 4 +-- internal/auth/client.go | 4 +-- internal/install/claude.go | 2 +- internal/install/editors.go | 44 ++++++++++++++++------- internal/install/hooks.go | 32 ++++++++--------- internal/install/native_hooks.go | 55 +++++++++++++++-------------- internal/output/human.go | 5 +-- internal/output/sarif.go | 2 +- internal/output/styles.go | 46 ++++++++++++++---------- internal/scan/repo/inline.go | 41 ++++++++++++++------- internal/scan/repo/repo.go | 3 +- internal/supplychain/check/bun.go | 4 +-- internal/supplychain/check/check.go | 7 ++++ internal/supplychain/check/npm.go | 2 +- internal/supplychain/check/pnpm.go | 4 +-- internal/supplychain/check/yarn.go | 4 +-- internal/supplychain/proxy.go | 11 ++++-- internal/supplychain/shell.go | 21 ++++++++--- internal/supplychain/shell_test.go | 30 ++++++++-------- internal/testutil/mockapi.go | 9 +++-- 22 files changed, 206 insertions(+), 128 deletions(-) diff --git a/.github/workflows/reusable-lint.yml b/.github/workflows/reusable-lint.yml index 8e5abc06..063f817a 100644 --- a/.github/workflows/reusable-lint.yml +++ b/.github/workflows/reusable-lint.yml @@ -39,5 +39,5 @@ jobs: - name: golangci-lint uses: golangci/golangci-lint-action@v9 with: - version: v2.10.1 + version: v2.12.2 args: --timeout=5m diff --git a/.golangci.yml b/.golangci.yml index d2aa7dcd..5e91fd1d 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -14,6 +14,8 @@ linters: - goconst - misspell settings: + goconst: + ignore-tests: true gosec: excludes: - G705 # XSS via taint analysis - not applicable to CLI stderr output diff --git a/internal/api/client_test.go b/internal/api/client_test.go index b7774136..55a05dbc 100644 --- a/internal/api/client_test.go +++ b/internal/api/client_test.go @@ -263,7 +263,7 @@ func TestClient_StartIngest(t *testing.T) { server := testutil.NewTestServer(t, func(w http.ResponseWriter, r *http.Request) { // Attempt to parse the multipart form - this reads the body // The reader error will cause this to fail, but we still send a response - _ = r.ParseMultipartForm(32 << 20) + _ = r.ParseMultipartForm(32 << 20) //nolint:gosec // G120: test server with bounded 32MB limit testutil.JSONResponse(t, w, http.StatusOK, model.IngestUploadResponse{ScanID: testScanID}) }) @@ -302,7 +302,7 @@ func TestClient_StartIngest(t *testing.T) { t.Run("sends SBOM and VEX flags when set", func(t *testing.T) { server := testutil.NewTestServer(t, func(w http.ResponseWriter, r *http.Request) { - if err := r.ParseMultipartForm(32 << 20); err != nil { + if err := r.ParseMultipartForm(32 << 20); err != nil { //nolint:gosec // G120: test server with bounded 32MB limit t.Fatalf("Failed to parse multipart form: %v", err) } diff --git a/internal/auth/client.go b/internal/auth/client.go index 430cf2ba..77248d90 100644 --- a/internal/auth/client.go +++ b/internal/auth/client.go @@ -105,8 +105,8 @@ func (c *AuthClient) Authenticate(ctx context.Context, clientID, clientSecret st Region: regionHint, } - // armis:ignore cwe:770 reason:credentials are bounded by caller input (CLI flags/env); request is a single fixed-structure JSON with context timeout - jsonBody, err := json.Marshal(reqBody) + // armis:ignore cwe:522 cwe:770 reason:marshaling credentials is intentional for the auth token endpoint; sent over HTTPS; bounded by caller input + jsonBody, err := json.Marshal(reqBody) //nolint:gosec // G117: ClientSecret is a credential field; marshaling is intentional for the auth token request if err != nil { return nil, fmt.Errorf("failed to marshal request: %w", err) } diff --git a/internal/install/claude.go b/internal/install/claude.go index ad87fcb3..321d4030 100644 --- a/internal/install/claude.go +++ b/internal/install/claude.go @@ -140,7 +140,7 @@ func (ci *ClaudeInstaller) registerMarketplace(pluginDir string) error { func (ci *ClaudeInstaller) registerPlugin(pluginDir string) error { instFile := filepath.Join(ci.claudeDir, "plugins", "installed_plugins.json") - data := map[string]interface{}{"version": 2, "plugins": map[string]interface{}{}} + data := map[string]interface{}{jsonKeyVersion: 2, "plugins": map[string]interface{}{}} // armis:ignore cwe:770 reason:reads bounded JSON config file from user's ~/.claude dir; not unbounded input if b, err := os.ReadFile(filepath.Clean(instFile)); err == nil { _ = json.Unmarshal(b, &data) diff --git a/internal/install/editors.go b/internal/install/editors.go index d7248096..e692778b 100644 --- a/internal/install/editors.go +++ b/internal/install/editors.go @@ -9,7 +9,23 @@ import ( "runtime" ) -const mcpServerName = "armis-appsec" +const ( + mcpServerName = "armis-appsec" + maxEditorConfigSize = 10 << 20 // 10 MB — matches the settings file limit in hooks.go +) + +// JSON key constants shared across MCP/hook config builders. +const ( + jsonKeyType = "type" + jsonKeyCommand = "command" + jsonKeyArgs = "args" + jsonKeyMatcher = "matcher" + jsonKeyHooks = "hooks" + jsonKeyTimeout = "timeout" + jsonKeyVersion = "version" + + jsonTypeCommand = "command" +) // EditorID identifies a supported editor. type EditorID string @@ -302,10 +318,10 @@ func registerVSCodeFormat(pluginDir, configFile string) error { servers = make(map[string]interface{}) } servers[mcpServerName] = map[string]interface{}{ - "type": "stdio", - "command": venvPython(pluginDir), - "args": []string{filepath.Join(pluginDir, "server.py")}, - "envFile": filepath.Join(pluginDir, ".env"), + jsonKeyType: "stdio", + jsonKeyCommand: venvPython(pluginDir), + jsonKeyArgs: []string{filepath.Join(pluginDir, "server.py")}, + "envFile": filepath.Join(pluginDir, ".env"), } data["servers"] = servers @@ -321,9 +337,9 @@ func registerZedFormat(pluginDir, configFile string) error { servers = make(map[string]interface{}) } servers[mcpServerName] = map[string]interface{}{ - "command": map[string]interface{}{ - "path": venvPython(pluginDir), - "args": []string{filepath.Join(pluginDir, "server.py")}, + jsonKeyCommand: map[string]interface{}{ + "path": venvPython(pluginDir), + jsonKeyArgs: []string{filepath.Join(pluginDir, "server.py")}, }, "settings": map[string]interface{}{}, } @@ -334,16 +350,20 @@ func registerZedFormat(pluginDir, configFile string) error { func stdServerEntry(pluginDir string) map[string]interface{} { return map[string]interface{}{ - "command": venvPython(pluginDir), - "args": []string{filepath.Join(pluginDir, "server.py")}, + jsonKeyCommand: venvPython(pluginDir), + jsonKeyArgs: []string{filepath.Join(pluginDir, "server.py")}, } } func readJSONFileAsMap(path string) map[string]interface{} { data := make(map[string]interface{}) + clean := filepath.Clean(path) // armis:ignore cwe:22 reason:path from filepath.Join with known base dirs; filepath.Clean applied - // armis:ignore cwe:253 reason:ReadFile error handled by err == nil guard; non-critical config read - if b, err := os.ReadFile(filepath.Clean(path)); err == nil { + if info, err := os.Stat(clean); err != nil || info.Size() > maxEditorConfigSize { + return data + } + // armis:ignore cwe:22 cwe:253 reason:path from filepath.Join with known base dirs; filepath.Clean applied; ReadFile error handled by err == nil guard + if b, err := os.ReadFile(clean); err == nil { //nolint:gosec _ = json.Unmarshal(b, &data) } return data diff --git a/internal/install/hooks.go b/internal/install/hooks.go index 2a976e13..9c4905c0 100644 --- a/internal/install/hooks.go +++ b/internal/install/hooks.go @@ -33,7 +33,7 @@ func installHooksToFile(settingsPath string) error { if info, err := os.Stat(settingsPath); err == nil && info.Size() > maxSettingsSize { return fmt.Errorf("settings file too large (%d bytes): %s", info.Size(), settingsPath) } - // armis:ignore cwe:59 reason:settingsPath from filepath.Join(UserHomeDir, hardcoded ".claude/settings.json"); no user input + // armis:ignore cwe:59 cwe:770 reason:settingsPath from filepath.Join(UserHomeDir, hardcoded ".claude/settings.json"); size bounded by maxSettingsSize guard above data, err := os.ReadFile(settingsPath) //nolint:gosec // G304: path constructed from UserHomeDir + hardcoded segments if err != nil && !os.IsNotExist(err) { return fmt.Errorf("reading settings: %w", err) @@ -47,7 +47,7 @@ func installHooksToFile(settingsPath string) error { } } - hooks, _ := settings["hooks"].(map[string]interface{}) + hooks, _ := settings[jsonKeyHooks].(map[string]interface{}) if hooks == nil { hooks = make(map[string]interface{}) } @@ -57,11 +57,11 @@ func installHooksToFile(settingsPath string) error { // Check if Armis hook already exists for _, entry := range preToolUse { if m, ok := entry.(map[string]interface{}); ok { - if matcher, _ := m["matcher"].(string); matcher == armisHookMatcher { - if innerHooks, _ := m["hooks"].([]interface{}); len(innerHooks) > 0 { + if matcher, _ := m[jsonKeyMatcher].(string); matcher == armisHookMatcher { + if innerHooks, _ := m[jsonKeyHooks].([]interface{}); len(innerHooks) > 0 { for _, h := range innerHooks { if hm, ok := h.(map[string]interface{}); ok { - if cmd, _ := hm["command"].(string); cmd != "" && isArmisHookCommand(cmd) { + if cmd, _ := hm[jsonKeyCommand].(string); cmd != "" && isArmisHookCommand(cmd) { return nil // already installed } } @@ -72,18 +72,18 @@ func installHooksToFile(settingsPath string) error { } armisHook := map[string]interface{}{ - "matcher": armisHookMatcher, - "hooks": []map[string]interface{}{ + jsonKeyMatcher: armisHookMatcher, + jsonKeyHooks: []map[string]interface{}{ { - "type": "command", - "command": "armis-cli scan repo --format json --no-progress --fail-on CRITICAL . >/dev/null 2>&1", + jsonKeyType: jsonTypeCommand, + jsonKeyCommand: "armis-cli scan repo --format json --no-progress --fail-on CRITICAL . >/dev/null 2>&1", }, }, } preToolUse = append(preToolUse, armisHook) hooks["PreToolUse"] = preToolUse - settings["hooks"] = hooks + settings[jsonKeyHooks] = hooks if err := os.MkdirAll(filepath.Dir(settingsPath), 0o750); err != nil { return fmt.Errorf("creating settings directory: %w", err) @@ -112,7 +112,7 @@ func removeHooksFromFile(settingsPath string) error { } else if info.Size() > maxSettingsSize { return fmt.Errorf("settings file too large (%d bytes): %s", info.Size(), settingsPath) } - // armis:ignore cwe:59 reason:settingsPath from filepath.Join(UserHomeDir, hardcoded ".claude/settings.json"); no user input + // armis:ignore cwe:59 cwe:770 reason:settingsPath from filepath.Join(UserHomeDir, hardcoded ".claude/settings.json"); size bounded by maxSettingsSize guard above data, err := os.ReadFile(settingsPath) //nolint:gosec // G304: path constructed from UserHomeDir + hardcoded segments if err != nil { return fmt.Errorf("reading settings: %w", err) @@ -123,7 +123,7 @@ func removeHooksFromFile(settingsPath string) error { return fmt.Errorf("parsing settings: %w", err) } - hooks, _ := settings["hooks"].(map[string]interface{}) + hooks, _ := settings[jsonKeyHooks].(map[string]interface{}) if hooks == nil { return nil } @@ -152,9 +152,9 @@ func removeHooksFromFile(settingsPath string) error { } if len(hooks) == 0 { - delete(settings, "hooks") + delete(settings, jsonKeyHooks) } else { - settings["hooks"] = hooks + settings[jsonKeyHooks] = hooks } if err := os.MkdirAll(filepath.Dir(settingsPath), 0o750); err != nil { @@ -164,10 +164,10 @@ func removeHooksFromFile(settingsPath string) error { } func isArmisHookEntry(m map[string]interface{}) bool { - innerHooks, _ := m["hooks"].([]interface{}) + innerHooks, _ := m[jsonKeyHooks].([]interface{}) for _, h := range innerHooks { if hm, ok := h.(map[string]interface{}); ok { - if cmd, _ := hm["command"].(string); isArmisHookCommand(cmd) { + if cmd, _ := hm[jsonKeyCommand].(string); isArmisHookCommand(cmd) { return true } } diff --git a/internal/install/native_hooks.go b/internal/install/native_hooks.go index 19fae55e..2aa8ce93 100644 --- a/internal/install/native_hooks.go +++ b/internal/install/native_hooks.go @@ -402,16 +402,16 @@ func buildCursorHooks(pluginDir string) map[string]interface{} { return map[string]interface{}{ "beforeShellExecution": []interface{}{ map[string]interface{}{ - "command": cmd, - "matcher": `git\s+(commit|push)|gh\s+pr\s+create`, - "timeout": 10, + jsonKeyCommand: cmd, + jsonKeyMatcher: `git\s+(commit|push)|gh\s+pr\s+create`, + jsonKeyTimeout: 10, }, }, "preToolUse": []interface{}{ map[string]interface{}{ - "command": cmd, - "matcher": "Write|Edit", - "timeout": 5, + jsonKeyCommand: cmd, + jsonKeyMatcher: "Write|Edit", + jsonKeyTimeout: 5, }, }, } @@ -425,12 +425,12 @@ func buildGeminiHooks(pluginDir string) map[string]interface{} { return map[string]interface{}{ "BeforeTool": []interface{}{ map[string]interface{}{ - "matcher": "shell|bash|run_shell_command|write_file|edit_file|patch_file", - "hooks": []interface{}{ + jsonKeyMatcher: "shell|bash|run_shell_command|write_file|edit_file|patch_file", + jsonKeyHooks: []interface{}{ map[string]interface{}{ - "type": "command", - "command": cmd, - "timeout": 10000, + jsonKeyType: jsonTypeCommand, + jsonKeyCommand: cmd, + jsonKeyTimeout: 10000, }, }, }, @@ -446,22 +446,22 @@ func buildCodexHooks(pluginDir string) map[string]interface{} { return map[string]interface{}{ "PreToolUse": []interface{}{ map[string]interface{}{ - "matcher": "shell", - "hooks": []interface{}{ + jsonKeyMatcher: "shell", + jsonKeyHooks: []interface{}{ map[string]interface{}{ - "type": "command", - "command": cmd, - "timeout": 10, + jsonKeyType: jsonTypeCommand, + jsonKeyCommand: cmd, + jsonKeyTimeout: 10, }, }, }, map[string]interface{}{ - "matcher": "write_file|apply_patch|edit_file", - "hooks": []interface{}{ + jsonKeyMatcher: "write_file|apply_patch|edit_file", + jsonKeyHooks: []interface{}{ map[string]interface{}{ - "type": "command", - "command": cmd, - "timeout": 5, + jsonKeyType: jsonTypeCommand, + jsonKeyCommand: cmd, + jsonKeyTimeout: 5, }, }, }, @@ -477,10 +477,10 @@ func buildCopilotHooks(pluginDir string) map[string]interface{} { return map[string]interface{}{ "preToolUse": []interface{}{ map[string]interface{}{ - "type": "command", - "bash": cmd, - "matcher": "bash|shell|terminal|powershell|create|edit", - "timeoutSec": 10, + jsonKeyType: jsonTypeCommand, + "bash": cmd, + jsonKeyMatcher: "bash|shell|terminal|powershell|create|edit", + "timeoutSec": 10, }, }, } @@ -580,11 +580,12 @@ func removeLegacyFileIfArmisOnly(path string) { } } for key := range data { - if key != "version" && key != "hooks" { + if key != jsonKeyVersion && key != jsonKeyHooks { return } } - _ = os.Remove(filepath.Clean(path)) //nolint:gosec // armis:ignore cwe:22 cwe:73 reason:path from hardcoded OS config dirs + // armis:ignore cwe:22 cwe:73 reason:path from hardcoded OS config dirs; only called after verifying file contains only armis-generated content + _ = os.Remove(filepath.Clean(path)) //nolint:gosec } // posixQuote wraps a string in POSIX-safe single quotes, escaping embedded single quotes. diff --git a/internal/output/human.go b/internal/output/human.go index 6db0015b..ca3c5942 100644 --- a/internal/output/human.go +++ b/internal/output/human.go @@ -24,6 +24,7 @@ import ( const ( groupBySeverity = "severity" + groupByNone = "none" noCWELabel = "No CWE" // Resource limits for snippet loading to prevent memory exhaustion (CWE-770) @@ -284,7 +285,7 @@ func (iw *indentWriter) Write(p []byte) (int, error) { // Format formats the scan result in human-readable format with default options. func (f *HumanFormatter) Format(result *model.ScanResult, w io.Writer) error { - return f.FormatWithOptions(result, w, FormatOptions{GroupBy: "none"}) + return f.FormatWithOptions(result, w, FormatOptions{GroupBy: groupByNone}) } // FormatWithOptions formats the scan result in human-readable format with custom options. @@ -340,7 +341,7 @@ func (f *HumanFormatter) FormatWithOptions(result *model.ScanResult, w io.Writer ew.write("%s\n", sectionStyle.Render("FINDINGS")) // 5. Individual findings - if opts.GroupBy != "" && opts.GroupBy != "none" { + if opts.GroupBy != "" && opts.GroupBy != groupByNone { groups := groupFindings(displayFindings, opts.GroupBy) renderGroupedFindings(w, groups, opts) } else { diff --git a/internal/output/sarif.go b/internal/output/sarif.go index 32753c71..53b89ec5 100644 --- a/internal/output/sarif.go +++ b/internal/output/sarif.go @@ -454,7 +454,7 @@ func severityToSarifLevel(severity model.Severity) string { case model.SeverityLow, model.SeverityInfo: return "note" default: - return "none" + return groupByNone } } diff --git a/internal/output/styles.go b/internal/output/styles.go index 5b1946cf..2e5dfefb 100644 --- a/internal/output/styles.go +++ b/internal/output/styles.go @@ -13,33 +13,41 @@ import ( "golang.org/x/term" ) +// Tailwind CSS color hex values referenced by multiple palette entries. +const ( + colorRed600 = "#DC2626" + colorOrange600 = "#EA580C" + colorBlue600 = "#2563EB" + colorGray500 = "#6B7280" +) + // Color palette - using Tailwind CSS color system for consistency // AdaptiveColor automatically selects Light/Dark variant based on terminal background var ( // Severity colors (background) - high saturation works on both themes - colorCriticalBg = lipgloss.AdaptiveColor{Light: "#DC2626", Dark: "#DC2626"} // red-600 - colorHighBg = lipgloss.AdaptiveColor{Light: "#EA580C", Dark: "#EA580C"} // orange-600 - colorMediumBg = lipgloss.AdaptiveColor{Light: "#CA8A04", Dark: "#CA8A04"} // yellow-600 - colorLowBg = lipgloss.AdaptiveColor{Light: "#2563EB", Dark: "#2563EB"} // blue-600 - colorInfoBg = lipgloss.AdaptiveColor{Light: "#6B7280", Dark: "#6B7280"} // gray-500 + colorCriticalBg = lipgloss.AdaptiveColor{Light: colorRed600, Dark: colorRed600} // red-600 + colorHighBg = lipgloss.AdaptiveColor{Light: colorOrange600, Dark: colorOrange600} // orange-600 + colorMediumBg = lipgloss.AdaptiveColor{Light: "#CA8A04", Dark: "#CA8A04"} // yellow-600 + colorLowBg = lipgloss.AdaptiveColor{Light: colorBlue600, Dark: colorBlue600} // blue-600 + colorInfoBg = lipgloss.AdaptiveColor{Light: colorGray500, Dark: colorGray500} // gray-500 // Severity colors (foreground for text) - darker on light bg for contrast - colorCriticalFg = lipgloss.AdaptiveColor{Light: "#DC2626", Dark: "#EF4444"} // red-600 / red-500 - colorHighFg = lipgloss.AdaptiveColor{Light: "#EA580C", Dark: "#F97316"} // orange-600 / orange-500 - colorMediumFg = lipgloss.AdaptiveColor{Light: "#A16207", Dark: "#EAB308"} // yellow-700 / yellow-500 - colorLowFg = lipgloss.AdaptiveColor{Light: "#2563EB", Dark: "#3B82F6"} // blue-600 / blue-500 - colorInfoFg = lipgloss.AdaptiveColor{Light: "#6B7280", Dark: "#9CA3AF"} // gray-500 / gray-400 + colorCriticalFg = lipgloss.AdaptiveColor{Light: colorRed600, Dark: "#EF4444"} // red-600 / red-500 + colorHighFg = lipgloss.AdaptiveColor{Light: colorOrange600, Dark: "#F97316"} // orange-600 / orange-500 + colorMediumFg = lipgloss.AdaptiveColor{Light: "#A16207", Dark: "#EAB308"} // yellow-700 / yellow-500 + colorLowFg = lipgloss.AdaptiveColor{Light: colorBlue600, Dark: "#3B82F6"} // blue-600 / blue-500 + colorInfoFg = lipgloss.AdaptiveColor{Light: colorGray500, Dark: "#9CA3AF"} // gray-500 / gray-400 // Semantic colors - darker on light bg for contrast - colorSuccess = lipgloss.AdaptiveColor{Light: "#16A34A", Dark: "#22C55E"} // green-600 / green-500 - colorWarning = lipgloss.AdaptiveColor{Light: "#D97706", Dark: "#F59E0B"} // amber-600 / amber-500 - colorMuted = lipgloss.AdaptiveColor{Light: "#4B5563", Dark: "#6B7280"} // gray-600 / gray-500 - colorAccent = lipgloss.AdaptiveColor{Light: "#7c3aed", Dark: "#7c3aed"} // purple-600 (Armis brand) + colorSuccess = lipgloss.AdaptiveColor{Light: "#16A34A", Dark: "#22C55E"} // green-600 / green-500 + colorWarning = lipgloss.AdaptiveColor{Light: "#D97706", Dark: "#F59E0B"} // amber-600 / amber-500 + colorMuted = lipgloss.AdaptiveColor{Light: "#4B5563", Dark: colorGray500} // gray-600 / gray-500 + colorAccent = lipgloss.AdaptiveColor{Light: "#7c3aed", Dark: "#7c3aed"} // purple-600 (Armis brand) // Diff colors - darker on light bg for contrast - colorDiffAdd = lipgloss.AdaptiveColor{Light: "#16A34A", Dark: "#22C55E"} // green-600 / green-500 - colorDiffRemove = lipgloss.AdaptiveColor{Light: "#DC2626", Dark: "#EF4444"} // red-600 / red-500 - colorDiffHunk = lipgloss.AdaptiveColor{Light: "#6B7280", Dark: "#6B7280"} // gray-500 + colorDiffAdd = lipgloss.AdaptiveColor{Light: "#16A34A", Dark: "#22C55E"} // green-600 / green-500 + colorDiffRemove = lipgloss.AdaptiveColor{Light: colorRed600, Dark: "#EF4444"} // red-600 / red-500 + colorDiffHunk = lipgloss.AdaptiveColor{Light: colorGray500, Dark: colorGray500} // gray-500 // Diff background colors - light tints on light bg, dark tints on dark bg colorDiffAddBg = lipgloss.AdaptiveColor{Light: "#dcfce7", Dark: "#0f2918"} // green-50 / dark green @@ -49,8 +57,8 @@ var ( colorVulnBg = lipgloss.AdaptiveColor{Light: "#fef3c7", Dark: "#422006"} // amber-100 / amber-950 // UI colors - inverted for light/dark themes - colorBorder = lipgloss.AdaptiveColor{Light: "#D1D5DB", Dark: "#374151"} // gray-300 / gray-700 - colorDim = lipgloss.AdaptiveColor{Light: "#4B5563", Dark: "#6B7280"} // gray-600 / gray-500 + colorBorder = lipgloss.AdaptiveColor{Light: "#D1D5DB", Dark: "#374151"} // gray-300 / gray-700 + colorDim = lipgloss.AdaptiveColor{Light: "#4B5563", Dark: colorGray500} // gray-600 / gray-500 // Bright text color - dark on light bg, white on dark bg colorBright = lipgloss.AdaptiveColor{Light: "#1F2937", Dark: "#FFFFFF"} // gray-800 / white diff --git a/internal/scan/repo/inline.go b/internal/scan/repo/inline.go index 1bb150fe..6433bb72 100644 --- a/internal/scan/repo/inline.go +++ b/internal/scan/repo/inline.go @@ -20,30 +20,47 @@ const ( suppressionTypeRule = "rule" ) +// File extension constants referenced in multiple maps below. +const ( + extPy = ".py" + extRb = ".rb" + extJs = ".js" + extTS = ".ts" + extJsx = ".jsx" + extTsx = ".tsx" + extKt = ".kt" + extScala = ".scala" + + commentHTML = "