Skip to content
5 changes: 5 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
GOSEC_AI_API_KEY=your_atlas_cloud_api_key
ATLASCLOUD_API_KEY=your_atlas_cloud_api_key
Comment thread
ccojocar marked this conversation as resolved.
Outdated
GOSEC_AI_PROVIDER=atlas
GOSEC_AI_MODEL=atlas
GOSEC_AI_BASE_URL=https://api.atlascloud.ai/v1
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,12 @@ coverage.out
*.prof

.DS_Store
.env.local
.env.*.local

.vscode
.idea

# SBOMs generated during CI
/bom.json
1
1
42 changes: 42 additions & 0 deletions ATLAS_CLOUD_REVIEW.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# Atlas Cloud Provider Review
Comment thread
ccojocar marked this conversation as resolved.
Outdated

## What Changed

- Added a first-class `atlas` AI provider preset in `autofix`.
- Defaulted Atlas Cloud traffic to `https://api.atlascloud.ai/v1`.
- Added Atlas model aliases:
- `atlas` -> `deepseek-ai/deepseek-v4-flash`
- `atlas-deepseek-v4-flash` -> `deepseek-ai/deepseek-v4-flash`
- `atlas-qwen3-coder-next` -> `qwen/qwen3-coder-next`
- `atlas-kimi-k2.6` -> `moonshotai/kimi-k2.6`
- `atlas/<model-id>` and `atlas:<model-id>` for direct model pass-through
- Added `ATLASCLOUD_API_KEY` fallback support in the CLI when `-ai-api-provider` starts with `atlas`.
- Updated README with Atlas Cloud usage, examples, and the official link:
`https://www.atlascloud.ai/?utm_source=github&utm_medium=link&utm_campaign=gosec`
- Added `.env.example` for local setup and ignored `.env.local` files.

## Files Changed

- `autofix/ai.go`
- `autofix/atlas.go`
- `autofix/ai_test.go`
- `autofix/atlas_test.go`
- `cmd/gosec/main.go`
- `README.md`
- `.gitignore`
- `.env.example`

## Local Validation Plan

- Unit test the `autofix` package.
- Build and run `gosec` against a temporary vulnerable sample with `-ai-api-provider=atlas`.
- Validate direct Atlas Cloud non-stream and stream responses with the provided API key.

## Validation Results

- `go test ./...` passed.
- Atlas Cloud `/v1/models` responded successfully and returned account-accessible model IDs.
- Atlas Cloud non-stream chat completion succeeded with `deepseek-ai/deepseek-v4-flash`.
- Atlas Cloud stream chat completion succeeded with `deepseek-ai/deepseek-v4-flash`.
- `gosec` binary integration succeeded:
`-ai-api-provider=atlas` generated a live Autofix for a temporary `G402` sample.
21 changes: 21 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -398,6 +398,14 @@ line arguments:

- `ai-api-provider`: the name of the AI API provider.
Supported providers:
- **Atlas Cloud**: `atlas` (default model
`deepseek-ai/deepseek-v4-flash`),
`atlas-deepseek-v4-flash`,
`atlas-qwen3-coder-next`, `atlas-kimi-k2.6`, or
`atlas/<model-id>` / `atlas:<model-id>` for any Atlas Cloud
hosted chat model.
Atlas Cloud is an OpenAI-compatible provider available at
[atlascloud.ai](https://www.atlascloud.ai/?utm_source=github&utm_medium=link&utm_campaign=gosec)
- **Gemini**: `gemini-3-pro-preview` (default),
`gemini-2.5-pro`, `gemini-2.5-flash`,
`gemini-2.5-flash-lite`
Expand All @@ -411,6 +419,8 @@ line arguments:
(requires `ai-base-url`)
- `ai-api-key` or set the environment variable
`GOSEC_AI_API_KEY`: the key to access the AI API
- For Atlas Cloud, you can also set `ATLASCLOUD_API_KEY`
and use the default base URL `https://api.atlascloud.ai/v1`
- For Gemini, you can create an API key following
[these instructions](https://ai.google.dev/gemini-api/docs/api-key)
- For Claude, get your API key from
Expand All @@ -420,12 +430,23 @@ line arguments:
- `ai-base-url`: (optional) custom base URL for
OpenAI-compatible APIs (e.g., Azure OpenAI, LocalAI,
Ollama)
- Atlas Cloud uses `https://api.atlascloud.ai/v1` by default,
so `ai-base-url` is optional for the built-in `atlas`
provider
- `ai-skip-ssl`: (optional) skip SSL certificate verification
for AI API (useful for self-signed certificates)

**Examples:**

```bash
# Using Atlas Cloud with the default DeepSeek V4 Flash model
export ATLASCLOUD_API_KEY="your_key"
gosec -ai-api-provider="atlas" ./...

# Using Atlas Cloud with an explicit hosted model
gosec -ai-api-provider="atlas:qwen/qwen3-coder-next" \
-ai-api-key="your_key" ./...

# Using Gemini
gosec -ai-api-provider="gemini-3-pro-preview" \
-ai-api-key="your_key" ./...
Expand Down
13 changes: 11 additions & 2 deletions autofix/ai.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (

const (
AIProviderFlagHelp = `AI API provider to generate auto fixes to issues. Valid options are:
- atlas (Atlas Cloud default), atlas-deepseek-v4-flash, atlas-qwen3-coder-next, atlas-kimi-k2.6, atlas/<model-id>, atlas:<model-id>;
Comment thread
ccojocar marked this conversation as resolved.
Outdated
- gemini-3-pro-preview (gemini, default), gemini-2.5-pro, gemini-2.5-flash, gemini-2.5-flash-lite;
- claude-sonnet-4-6 (claude, default), claude-opus-4-7, claude-opus-4-6, claude-sonnet-4-5, claude-opus-4-5, claude-haiku-4-5;
- gpt-5.4 (openai, default), gpt-5.4-mini, gpt-5.4-nano`
Expand All @@ -32,6 +33,14 @@ func GenerateSolution(model, aiAPIKey, baseURL string, skipSSL bool, issues []*i
var client GenAIClient

switch {
case model == "atlas" || strings.HasPrefix(model, "atlas-") || strings.HasPrefix(model, "atlas/") || strings.HasPrefix(model, "atlas:"):
config := AtlasConfig{
Model: model,
APIKey: aiAPIKey,
BaseURL: baseURL,
SkipSSL: skipSSL,
}
client, err = NewAtlasClient(config)
case strings.HasPrefix(model, "claude"):
client, err = NewClaudeClient(model, aiAPIKey)
case strings.HasPrefix(model, "gemini"):
Expand Down Expand Up @@ -76,11 +85,11 @@ func generateSolution(client GenAIClient, issues []*issue.Issue) error {
prompt := fmt.Sprintf(AIPrompt, issue.What)
resp, err := client.GenerateSolution(ctx, prompt)
if err != nil {
return fmt.Errorf("generating autofix with gemini: %w", err)
return fmt.Errorf("generating autofix with AI provider: %w", err)
}

if resp == "" {
return errors.New("no autofix returned by gemini")
return errors.New("no autofix returned by AI provider")
}

issue.Autofix = resp
Expand Down
4 changes: 2 additions & 2 deletions autofix/ai_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ func TestGenerateSolutionByGemini_NoCandidates(t *testing.T) {
err := generateSolution(mockClient, issues)

// Assert
require.EqualError(t, err, "no autofix returned by gemini")
require.EqualError(t, err, "no autofix returned by AI provider")
mock.AssertExpectationsForObjects(t, mockClient)
}

Expand All @@ -70,7 +70,7 @@ func TestGenerateSolutionByGemini_APIError(t *testing.T) {
err := generateSolution(mockClient, issues)

// Assert
require.EqualError(t, err, "generating autofix with gemini: API error")
require.EqualError(t, err, "generating autofix with AI provider: API error")
mock.AssertExpectationsForObjects(t, mockClient)
}

Expand Down
68 changes: 68 additions & 0 deletions autofix/atlas.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package autofix

import "strings"

const (
ModelAtlasDefault = "deepseek-ai/deepseek-v4-flash"
Comment thread
ccojocar marked this conversation as resolved.
Outdated
ModelAtlasDeepSeekV4Flash = "deepseek-ai/deepseek-v4-flash"
ModelAtlasQwenCoderNext = "qwen/qwen3-coder-next"
ModelAtlasKimiK26 = "moonshotai/kimi-k2.6"

DefaultAtlasBaseURL = "https://api.atlascloud.ai/v1"
)

type AtlasConfig struct {
Model string
APIKey string `json:"-"`
BaseURL string
MaxTokens int
Temperature float64
SkipSSL bool
}

func NewAtlasClient(config AtlasConfig) (GenAIClient, error) {
baseURL := config.BaseURL
if baseURL == "" {
baseURL = DefaultAtlasBaseURL
}

return NewOpenAIClient(OpenAIConfig{
Model: parseAtlasModel(config.Model),
APIKey: config.APIKey,
BaseURL: baseURL,
MaxTokens: config.MaxTokens,
Temperature: config.Temperature,
SkipSSL: config.SkipSSL,
})
}

func parseAtlasModel(model string) string {
switch model {
case "", "atlas", "atlas-deepseek-v4-flash":
return ModelAtlasDefault
case "atlas-qwen3-coder-next", "atlas-qwen-turbo":
return ModelAtlasQwenCoderNext
case "atlas-kimi-k2.6", "atlas-kimi-k2":
return ModelAtlasKimiK26
}

for _, prefix := range []string{"atlas/", "atlas:"} {
if strings.HasPrefix(model, prefix) {
trimmed := strings.TrimPrefix(model, prefix)
if trimmed != "" {
return trimmed
}
return ModelAtlasDefault
}
}

if strings.HasPrefix(model, "atlas-") {
trimmed := strings.TrimPrefix(model, "atlas-")
if trimmed != "" {
return trimmed
}
return ModelAtlasDefault
}

return model
}
85 changes: 85 additions & 0 deletions autofix/atlas_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
package autofix

import (
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func TestParseAtlasModel(t *testing.T) {
tests := []struct {
name string
input string
expected string
}{
{
name: "atlas defaults to deepseek v3",
input: "atlas",
expected: ModelAtlasDefault,
},
{
name: "atlas deepseek alias",
input: "atlas-deepseek-v4-flash",
expected: ModelAtlasDeepSeekV4Flash,
},
{
name: "atlas qwen alias",
input: "atlas-qwen3-coder-next",
expected: ModelAtlasQwenCoderNext,
},
{
name: "atlas kimi alias",
input: "atlas-kimi-k2.6",
expected: ModelAtlasKimiK26,
},
{
name: "atlas slash syntax",
input: "atlas/deepseek-v3",
expected: "deepseek-v3",
},
{
name: "atlas colon syntax",
input: "atlas:qwen-plus",
expected: "qwen-plus",
},
{
name: "unknown non atlas model passes through",
input: "custom-model",
expected: "custom-model",
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert.Equal(t, tt.expected, parseAtlasModel(tt.input))
})
}
}

func TestNewAtlasClient_Defaults(t *testing.T) {
client, err := NewAtlasClient(AtlasConfig{
Model: "atlas",
APIKey: "test-key",
})
require.NoError(t, err)
require.NotNil(t, client)

wrapper, ok := client.(*openaiWrapper)
require.True(t, ok)
assert.Equal(t, ModelAtlasDefault, string(wrapper.model))

Check failure on line 70 in autofix/atlas_test.go

View workflow job for this annotation

GitHub Actions / test (1.26.3, latest)

unnecessary conversion (unconvert)
assert.Equal(t, 1024, wrapper.maxTokens)
assert.InEpsilon(t, 0.7, wrapper.temperature, 0.001)
}

func TestNewAtlasClient_CustomModelSyntax(t *testing.T) {
client, err := NewAtlasClient(AtlasConfig{
Model: "atlas/moonshot-v1-8k",
APIKey: "test-key",
BaseURL: DefaultAtlasBaseURL,
})
require.NoError(t, err)

wrapper := client.(*openaiWrapper)
assert.Equal(t, "moonshot-v1-8k", string(wrapper.model))

Check failure on line 84 in autofix/atlas_test.go

View workflow job for this annotation

GitHub Actions / test (1.26.3, latest)

unnecessary conversion (unconvert)
}
9 changes: 7 additions & 2 deletions cmd/gosec/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,9 @@
$ gosec --exclude-rules="scripts/.*:*" ./...
`
// Environment variable for AI API key.
aiAPIKeyEnv = "GOSEC_AI_API_KEY" // #nosec G101
aiAPIKeyEnv = "GOSEC_AI_API_KEY" // #nosec G101
atlasAPIKeyEnv = "ATLASCLOUD_API_KEY"

Check failure on line 70 in cmd/gosec/main.go

View workflow job for this annotation

GitHub Actions / test (1.26.3, latest)

G101: Potential hardcoded credentials (gosec)

Check failure

Code scanning / gosec

Potential hardcoded credentials Error

Potential hardcoded credentials
Comment thread
github-advanced-security[bot] marked this conversation as resolved.
Fixed
Comment thread
ccojocar marked this conversation as resolved.
Outdated
atlasProviderEnv = "atlas"

// Exit codes
exitSuccess = 0
Expand Down Expand Up @@ -181,7 +183,7 @@
flagAiAPIKey = flag.String("ai-api-key", "", "Key to access the AI API")

// base URL for AI API (optional, for OpenAI-compatible APIs)
flagAiBaseURL = flag.String("ai-base-url", "", "Base URL for AI API (e.g., for OpenAI-compatible services)")
flagAiBaseURL = flag.String("ai-base-url", "", "Base URL for AI API (e.g., for Atlas Cloud or other OpenAI-compatible services)")

// skip SSL verification for AI API
flagAiSkipSSL = flag.Bool("ai-skip-ssl", false, "Skip SSL certificate verification for AI API")
Expand Down Expand Up @@ -589,6 +591,9 @@

// Call AI request to solve the issues
aiAPIKey := os.Getenv(aiAPIKeyEnv)
if aiAPIKey == "" && strings.HasPrefix(*flagAiAPIProvider, atlasProviderEnv) {
aiAPIKey = os.Getenv(atlasAPIKeyEnv)
}
if aiAPIKey == "" {
aiAPIKey = *flagAiAPIKey
}
Expand Down
Loading