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
94 changes: 77 additions & 17 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,10 @@

DockSec combines traditional Docker security scanners (Trivy, Hadolint, Docker Scout) with AI to provide **context-aware security analysis**. Instead of dumping 200 CVEs and leaving you to figure it out, DockSec:

- 🎯 Prioritizes what actually matters
- 💡 Explains vulnerabilities in plain English
- 🔧 Suggests specific fixes for YOUR Dockerfile
- 📊 Generates professional security reports
- Prioritizes what actually matters
- Explains vulnerabilities in plain English
- Suggests specific fixes for YOUR Dockerfile
- Generates professional security reports

Think of it as having a security expert review your Dockerfiles.

Expand Down Expand Up @@ -62,12 +62,13 @@ docksec Dockerfile --scan-only

## Features

- **Smart Analysis**: AI explains what vulnerabilities mean for your specific setup
- **Multiple Scanners**: Integrates Trivy, Hadolint, and Docker Scout
- **Security Scoring**: Get a 0-100 score to track improvements
- **Multiple Formats**: Export reports as HTML, PDF, JSON, or CSV
- **No AI Required**: Works offline with `--scan-only` mode
- **CI/CD Ready**: Easy integration into build pipelines
- Smart Analysis: AI explains what vulnerabilities mean for your specific setup
- Multiple LLM Providers: Support for OpenAI, Anthropic Claude, Google Gemini, and Ollama (local models)
- Multiple Scanners: Integrates Trivy, Hadolint, and Docker Scout
- Security Scoring: Get a 0-100 score to track improvements
- Multiple Formats: Export reports as HTML, PDF, JSON, or CSV
- No AI Required: Works offline with `--scan-only` mode
- CI/CD Ready: Easy integration into build pipelines

## Installation

Expand All @@ -77,11 +78,37 @@ docksec Dockerfile --scan-only
pip install docksec
```

**For AI features**, set your OpenAI API key:
**For AI features**, choose your preferred LLM provider:

### OpenAI (Default)
```bash
export OPENAI_API_KEY="your-key-here"
```

### Anthropic Claude
```bash
export ANTHROPIC_API_KEY="your-key-here"
export LLM_PROVIDER="anthropic"
export LLM_MODEL="claude-3-5-sonnet-20241022"
```

### Google Gemini
```bash
export GOOGLE_API_KEY="your-key-here"
export LLM_PROVIDER="google"
export LLM_MODEL="gemini-1.5-pro"
```

### Ollama (Local Models)
```bash
# First, install and run Ollama: https://ollama.ai
# Then pull a model: ollama pull llama3.1
export LLM_PROVIDER="ollama"
export LLM_MODEL="llama3.1"
# Optional: customize Ollama URL
export OLLAMA_BASE_URL="http://localhost:11434"
```

**External tools** (optional, for full scanning):
```bash
# Install Trivy and Hadolint
Expand All @@ -108,6 +135,12 @@ docksec Dockerfile --scan-only

# Scan image without Dockerfile
docksec --image-only -i nginx:latest

# Use specific LLM provider and model
docksec Dockerfile --provider anthropic --model claude-3-5-sonnet-20241022

# Use local Ollama model
docksec Dockerfile --provider ollama --model llama3.1
```

### CLI Options
Expand All @@ -117,6 +150,8 @@ docksec --image-only -i nginx:latest
| `dockerfile` | Path to Dockerfile |
| `-i, --image` | Docker image to scan |
| `-o, --output` | Output file path |
| `--provider` | LLM provider (openai, anthropic, google, ollama) |
| `--model` | Model name (e.g., gpt-4o, claude-3-5-sonnet-20241022) |
| `--ai-only` | AI analysis only (no scanning) |
| `--scan-only` | Scanning only (no AI) |
| `--image-only` | Scan image without Dockerfile |
Expand All @@ -126,9 +161,22 @@ docksec --image-only -i nginx:latest
Create a `.env` file for advanced configuration:

```bash
OPENAI_API_KEY=your-key
LLM_MODEL=gpt-4o
# LLM Provider Configuration
LLM_PROVIDER=openai # Options: openai, anthropic, google, ollama
LLM_MODEL=gpt-4o # Model to use
LLM_TEMPERATURE=0.0 # Temperature (0-1)

# API Keys
OPENAI_API_KEY=your-openai-key
ANTHROPIC_API_KEY=your-anthropic-key
GOOGLE_API_KEY=your-google-key

# Ollama Configuration (for local models)
OLLAMA_BASE_URL=http://localhost:11434

# Scanning Configuration
TRIVY_SCAN_TIMEOUT=600
DOCKSEC_DEFAULT_SEVERITY=CRITICAL,HIGH
```

See [full configuration options](docs/CONTRIBUTING.md#configuration).
Expand Down Expand Up @@ -158,19 +206,25 @@ Critical Issues (3):
Dockerfile → [Trivy + Hadolint + Scout] → AI Analysis → Reports
```

DockSec runs security scanners locally, then uses GPT-4 to:
DockSec runs security scanners locally, then uses AI to:
1. Combine and deduplicate findings
2. Assess real-world impact for your context
3. Generate actionable remediation steps
4. Calculate security score

All scanning happens on your machine. Only scan results (not your code) are sent to OpenAI when using AI features.
**Supported AI Providers:**
- **OpenAI**: GPT-4o, GPT-4 Turbo, GPT-3.5 Turbo
- **Anthropic**: Claude 3.5 Sonnet, Claude 3 Opus
- **Google**: Gemini 1.5 Pro, Gemini 1.5 Flash
- **Ollama**: Llama 3.1, Mistral, Phi-3, and other local models

All scanning happens on your machine. Only scan results (not your code) are sent to the AI provider when using AI features.

## Roadmap

- [x] Multiple LLM provider support (OpenAI, Anthropic, Google, Ollama)
- [ ] Docker Compose support
- [ ] Kubernetes manifest scanning
- [ ] Additional LLM providers (Claude, local models)
- [ ] GitHub Actions integration
- [ ] Custom security policies

Expand All @@ -195,14 +249,20 @@ Quick links:
## Troubleshooting

**"No OpenAI API Key provided"**
→ Set `OPENAI_API_KEY` or use `--scan-only` mode
→ Set appropriate API key for your provider (OPENAI_API_KEY, ANTHROPIC_API_KEY, GOOGLE_API_KEY) or use `--scan-only` mode

**"Unsupported LLM provider"**
→ Valid providers: openai, anthropic, google, ollama. Set with `--provider` flag or LLM_PROVIDER env var

**"Hadolint not found"**
→ Run `python -m docksec.setup_external_tools`

**"Python version not supported"**
→ DockSec requires Python 3.12+. Use `pyenv install 3.12` to upgrade.

**"Connection refused" with Ollama**
→ Make sure Ollama is running: `ollama serve` and the model is pulled: `ollama pull llama3.1`

**"Where are my scan results?"**
→ Results are saved to `results/` directory in your DockSec installation
→ Customize location: `export DOCKSEC_RESULTS_DIR=/custom/path`
Expand Down
86 changes: 81 additions & 5 deletions config_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,20 +28,30 @@ class DocksecConfig:
- Scan parameters

Attributes:
llm_provider: LLM provider to use (openai, anthropic, google, ollama)
openai_api_key: OpenAI API key for AI features
anthropic_api_key: Anthropic API key for Claude
google_api_key: Google API key for Gemini
ollama_base_url: Base URL for Ollama server (local models)
base_dir: Base directory of the application
results_dir: Directory for storing scan results
timeout_hadolint: Timeout for Hadolint scans (seconds)
timeout_trivy: Timeout for Trivy scans (seconds)
timeout_docker_scout: Timeout for Docker Scout scans (seconds)
timeout_llm: Timeout for LLM API calls (seconds)
max_retries_llm: Maximum number of retry attempts for LLM calls
llm_model: OpenAI model to use (default: gpt-4o)
llm_model: Model name to use (e.g., gpt-4o, claude-3-5-sonnet-20241022, gemini-pro, llama3.1)
llm_temperature: Temperature setting for LLM (0-1)
"""

# LLM Provider Configuration
llm_provider: str = "openai"

# API Configuration
openai_api_key: Optional[str] = None
anthropic_api_key: Optional[str] = None
google_api_key: Optional[str] = None
ollama_base_url: str = "http://localhost:11434"

# Directory Configuration
base_dir: str = field(default_factory=lambda: os.path.abspath(os.path.dirname(__file__)))
Expand Down Expand Up @@ -76,12 +86,28 @@ def __post_init__(self):
if not self.openai_api_key:
self.openai_api_key = os.getenv("OPENAI_API_KEY", "")

if not self.anthropic_api_key:
self.anthropic_api_key = os.getenv("ANTHROPIC_API_KEY", "")

if not self.google_api_key:
self.google_api_key = os.getenv("GOOGLE_API_KEY", "")

# Load provider from environment
self.llm_provider = os.getenv("LLM_PROVIDER", self.llm_provider).lower()

# Load Ollama base URL from environment
self.ollama_base_url = os.getenv("OLLAMA_BASE_URL", self.ollama_base_url)

# Ensure results directory exists
os.makedirs(self.results_dir, exist_ok=True)

# Set environment variable for OpenAI (for backward compatibility)
# Set environment variables for backward compatibility
if self.openai_api_key:
os.environ["OPENAI_API_KEY"] = self.openai_api_key
if self.anthropic_api_key:
os.environ["ANTHROPIC_API_KEY"] = self.anthropic_api_key
if self.google_api_key:
os.environ["GOOGLE_API_KEY"] = self.google_api_key

# Validate configuration
self._validate()
Expand All @@ -93,6 +119,11 @@ def _validate(self) -> None:
Raises:
ValueError: If configuration values are invalid
"""
# Validate LLM provider
valid_providers = ['openai', 'anthropic', 'google', 'ollama']
if self.llm_provider not in valid_providers:
raise ValueError(f"Invalid llm_provider: {self.llm_provider}. Valid options: {valid_providers}")

# Validate timeouts
if self.timeout_hadolint <= 0:
raise ValueError(f"Invalid timeout_hadolint: {self.timeout_hadolint}. Must be positive.")
Expand Down Expand Up @@ -169,6 +200,43 @@ def get_openai_api_key(self) -> str:
raise EnvironmentError(error_message.strip())
return self.openai_api_key

def get_api_key_for_provider(self) -> str:
"""
Get API key for the configured LLM provider.

Returns:
str: The API key for the current provider

Raises:
EnvironmentError: If API key is not set for the provider
"""
if self.llm_provider == "openai":
if not self.openai_api_key:
raise EnvironmentError(
"OpenAI API key not found. Set OPENAI_API_KEY environment variable or use --scan-only mode."
)
return self.openai_api_key

elif self.llm_provider == "anthropic":
if not self.anthropic_api_key:
raise EnvironmentError(
"Anthropic API key not found. Set ANTHROPIC_API_KEY environment variable or use --scan-only mode."
)
return self.anthropic_api_key

elif self.llm_provider == "google":
if not self.google_api_key:
raise EnvironmentError(
"Google API key not found. Set GOOGLE_API_KEY environment variable or use --scan-only mode."
)
return self.google_api_key

elif self.llm_provider == "ollama":
return ""

else:
raise ValueError(f"Unsupported LLM provider: {self.llm_provider}")

def update(self, **kwargs: Any) -> None:
"""
Update configuration values.
Expand Down Expand Up @@ -196,6 +264,7 @@ def to_dict(self) -> Dict[str, Any]:
Dictionary of configuration values
"""
return {
'llm_provider': self.llm_provider,
'base_dir': self.base_dir,
'results_dir': self.results_dir,
'timeout_hadolint': self.timeout_hadolint,
Expand All @@ -210,7 +279,10 @@ def to_dict(self) -> Dict[str, Any]:
'retry_max_wait': self.retry_max_wait,
'default_severity': self.default_severity,
'max_file_size_mb': self.max_file_size_mb,
'has_api_key': bool(self.openai_api_key)
'ollama_base_url': self.ollama_base_url,
'has_openai_key': bool(self.openai_api_key),
'has_anthropic_key': bool(self.anthropic_api_key),
'has_google_key': bool(self.google_api_key)
}

@classmethod
Expand All @@ -222,15 +294,19 @@ def from_env(cls) -> 'DocksecConfig':
DocksecConfig instance with values from environment
"""
return cls(
llm_provider=os.getenv("LLM_PROVIDER", "openai").lower(),
openai_api_key=os.getenv("OPENAI_API_KEY"),
anthropic_api_key=os.getenv("ANTHROPIC_API_KEY"),
google_api_key=os.getenv("GOOGLE_API_KEY"),
ollama_base_url=os.getenv("OLLAMA_BASE_URL", "http://localhost:11434"),
results_dir=os.getenv("DOCKSEC_RESULTS_DIR", os.path.join(os.getcwd(), "results")),
timeout_hadolint=int(os.getenv("DOCKSEC_TIMEOUT_HADOLINT", "300")),
timeout_trivy=int(os.getenv("DOCKSEC_TIMEOUT_TRIVY", "600")),
timeout_docker_scout=int(os.getenv("DOCKSEC_TIMEOUT_DOCKER_SCOUT", "300")),
timeout_llm=int(os.getenv("DOCKSEC_TIMEOUT_LLM", "60")),
max_retries_llm=int(os.getenv("DOCKSEC_MAX_RETRIES_LLM", "2")),
llm_model=os.getenv("DOCKSEC_LLM_MODEL", "gpt-4o"),
llm_temperature=float(os.getenv("DOCKSEC_LLM_TEMPERATURE", "0.0")),
llm_model=os.getenv("LLM_MODEL", "gpt-4o"),
llm_temperature=float(os.getenv("LLM_TEMPERATURE", "0.0")),
retry_attempts=int(os.getenv("DOCKSEC_RETRY_ATTEMPTS", "3")),
retry_min_wait=int(os.getenv("DOCKSEC_RETRY_MIN_WAIT", "2")),
retry_max_wait=int(os.getenv("DOCKSEC_RETRY_MAX_WAIT", "10")),
Expand Down
11 changes: 10 additions & 1 deletion docksec.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from typing import NoReturn, Optional

# Version - keep in sync with setup.py
__version__ = "2026.1.24"
__version__ = "2026.2.23"

def get_version():
"""Get version from setup.py if available, otherwise use hardcoded version."""
Expand Down Expand Up @@ -35,10 +35,19 @@ def main() -> None:
parser.add_argument('--ai-only', action='store_true', help='Run only AI-based recommendations (requires Dockerfile)')
parser.add_argument('--scan-only', action='store_true', help='Run only Dockerfile/image scanning (requires --image)')
parser.add_argument('--image-only', action='store_true', help='Scan only the Docker image without Dockerfile analysis')
parser.add_argument('--provider', choices=['openai', 'anthropic', 'google', 'ollama'],
help='LLM provider to use (default: openai, can also set LLM_PROVIDER env var)')
parser.add_argument('--model', help='Model name to use (e.g., gpt-4o, claude-3-5-sonnet-20241022, gemini-1.5-pro, llama3.1)')
parser.add_argument('--version', action='version', version=f'DockSec {get_version()}')

args = parser.parse_args()

# Set provider and model from CLI args if provided (overrides env vars)
if args.provider:
os.environ["LLM_PROVIDER"] = args.provider
if args.model:
os.environ["LLM_MODEL"] = args.model

# Validate argument combinations
if args.image_only and args.ai_only:
print("Error: --image-only and --ai-only cannot be used together (AI analysis requires Dockerfile)")
Expand Down
Loading