Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions docs/codacy-rules-ai.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,13 @@ rules:
$CLIENT.GenerateContentAsync(model: "$MODEL", ...)
- metavariable-regex:
metavariable: $MODEL
regex: "<!-- MODEL_REGEX -->"
regex: <!-- MODEL_ALLOW_LIST -->
metadata:
category: security
subcategory: ai
description: Detects usage of insecure/unauthorized LLM models in C# codebases
technology:
- csharp
impact: MEDIUM
confidence: MEDIUM
confidence: LOW
likelihood: MEDIUM
2 changes: 1 addition & 1 deletion docs/multiple-tests/ai/patterns.xml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<module name="root">
<module name="codacy.csharp.ai.insecure-llm-model-usage">
<property name="modelRegex" value="^(gemini-2.5-flash|gpt-3\\.5-turbo|old-llama-model)$" />
<property name="modelAllowList" value="gemini-2.5-flash,gpt-3.5-turbo,old-llama-model" />
</module>
</module>
2 changes: 1 addition & 1 deletion docs/multiple-tests/ai/results.xml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
<checkstyle version="1.5">
<file name="cs/GeminiExample.cs">
<error source="codacy.csharp.ai.insecure-llm-model-usage" line="9"
message="Usage of Insecure LLM Model: gemini-2.5-flash"
message="Usage of Insecure LLM Model: deepseek-v3.2"
severity="error" />
</file>
</checkstyle>
Expand Down
3 changes: 3 additions & 0 deletions docs/multiple-tests/ai/src/cs/GeminiExample.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ public static async Task main() {
// The client gets the API key from the environment variable `GEMINI_API_KEY`.
var client = new Client();
var response = await client.Models.GenerateContentAsync(
model: "deepseek-v3.2", contents: "Explain how AI works in a few words"
);
var response2 = await client.Models.GenerateContentAsync(
model: "gemini-2.5-flash", contents: "Explain how AI works in a few words"
);
Comment on lines +12 to +14
Copy link

Copilot AI Dec 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This assignment to response2 is useless, since its value is never read.

Suggested change
var response2 = await client.Models.GenerateContentAsync(
model: "gemini-2.5-flash", contents: "Explain how AI works in a few words"
);

Copilot uses AI. Check for mistakes.
Console.WriteLine(response.Candidates[0].Content.Parts[0].Text);
Expand Down
31 changes: 29 additions & 2 deletions internal/tool/configuration.go
Original file line number Diff line number Diff line change
Expand Up @@ -167,17 +167,23 @@ func replaceParameterPlaceholders(line string, pattern *codacy.Pattern) string {
paramName := matches[1]
// Convert UPPER_CASE to camelCase to match parameter name format
formattedParamName := formatParameterName(paramName)

// Find the parameter in the pattern
for _, param := range pattern.Parameters {

if param.Name == formattedParamName {
// Use Value if set, otherwise use Default
value := param.Value
if value == nil {
value = param.Default
}
if value != nil {
return fmt.Sprintf("%v", value)
valueStr := fmt.Sprintf("%v", value)

// If parameter name ends with _ALLOW_LIST, convert comma-separated list to regex pattern
if strings.HasSuffix(paramName, "_ALLOW_LIST") {
return convertListToRegex(valueStr, false)
}
return valueStr
}
}
}
Expand All @@ -189,6 +195,27 @@ func replaceParameterPlaceholders(line string, pattern *codacy.Pattern) string {
return result
}

// convertListToRegex converts a comma-separated list into a regex alternation pattern
// Example: "gemini-2.5-flash,gpt-3.5-turbo,old-llama-model" -> "^(gemini-2\\.5-flash|gpt-3\\.5-turbo|old-llama-model)$"
Comment on lines +198 to +199
Copy link

Copilot AI Dec 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comment example on line 199 shows the output for the include=true case (^(gemini-2\\.5-flash|gpt-3\\.5-turbo|old-llama-model)$), but the function is always called with include=false (line 184). The actual output for the allow list case will be ^(?!(gemini-2\\.5-flash|gpt-3\\.5-turbo|old-llama-model)$).* (a negative lookahead). The comment should be updated to reflect the actual behavior or show both cases.

Suggested change
// convertListToRegex converts a comma-separated list into a regex alternation pattern
// Example: "gemini-2.5-flash,gpt-3.5-turbo,old-llama-model" -> "^(gemini-2\\.5-flash|gpt-3\\.5-turbo|old-llama-model)$"
// convertListToRegex converts a comma-separated list into a regex alternation pattern.
// Example:
// If include=true: "gemini-2.5-flash,gpt-3.5-turbo,old-llama-model" -> "^(gemini-2\\.5-flash|gpt-3\\.5-turbo|old-llama-model)$"
// If include=false: "gemini-2.5-flash,gpt-3.5-turbo,old-llama-model" -> "^(?!(gemini-2\\.5-flash|gpt-3\\.5-turbo|old-llama-model)$).*"

Copilot uses AI. Check for mistakes.
func convertListToRegex(list string, include bool) string {
// Split by comma and trim spaces
items := strings.Split(list, ",")
for i, item := range items {
// Trim whitespace
item = strings.TrimSpace(item)
// Escape dots for regex
item = strings.ReplaceAll(item, ".", "\\.")
Comment on lines +204 to +207
Copy link

Copilot AI Dec 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The convertListToRegex function only escapes dots (.) for regex special characters. However, model names could contain other regex special characters like +, *, ?, (, ), [, ], {, }, |, ^, $, \, etc. Consider using regexp.QuoteMeta() to properly escape all regex special characters to avoid potential regex injection or matching errors.

Example:

item = regexp.QuoteMeta(strings.TrimSpace(item))
Suggested change
// Trim whitespace
item = strings.TrimSpace(item)
// Escape dots for regex
item = strings.ReplaceAll(item, ".", "\\.")
// Trim whitespace and escape all regex meta-characters
item = regexp.QuoteMeta(strings.TrimSpace(item))

Copilot uses AI. Check for mistakes.
items[i] = item
}

// Join with pipe separator and wrap in regex anchors
if include {
return fmt.Sprintf("^(%s)$", strings.Join(items, "|"))
}

return fmt.Sprintf("^(?!(%s)$).*", strings.Join(items, "|"))
Comment on lines +203 to +216
Copy link

Copilot AI Dec 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The convertListToRegex function doesn't handle empty strings properly. If the input list is empty or contains only whitespace, this will generate a malformed regex like ^(?!()$).* or ^(?!(|item)$).* with empty alternatives. Consider adding validation to handle empty lists or filtering out empty items after trimming.

Suggested change
for i, item := range items {
// Trim whitespace
item = strings.TrimSpace(item)
// Escape dots for regex
item = strings.ReplaceAll(item, ".", "\\.")
items[i] = item
}
// Join with pipe separator and wrap in regex anchors
if include {
return fmt.Sprintf("^(%s)$", strings.Join(items, "|"))
}
return fmt.Sprintf("^(?!(%s)$).*", strings.Join(items, "|"))
filteredItems := make([]string, 0, len(items))
for _, item := range items {
// Trim whitespace
item = strings.TrimSpace(item)
if item == "" {
continue
}
// Escape dots for regex
item = strings.ReplaceAll(item, ".", "\\.")
filteredItems = append(filteredItems, item)
}
// Handle empty list case
if len(filteredItems) == 0 {
if include {
// Matches nothing
return "^$"
} else {
// Matches everything
return ".*"
}
}
// Join with pipe separator and wrap in regex anchors
if include {
return fmt.Sprintf("^(%s)$", strings.Join(filteredItems, "|"))
}
return fmt.Sprintf("^(?!(%s)$).*", strings.Join(filteredItems, "|"))

Copilot uses AI. Check for mistakes.
}
Comment on lines +200 to +217
Copy link

Copilot AI Dec 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The include parameter in convertListToRegex is always passed as false (line 184), making the if include branch on lines 212-213 unreachable dead code. Consider removing the unused parameter and the unreachable code branch to simplify the function.

Copilot uses AI. Check for mistakes.

// formatParameterName converts UPPER_CASE to camelCase
func formatParameterName(name string) string {
parts := strings.Split(strings.ToLower(name), "_")
Expand Down