Skip to content

feat: Simple volume mount validation for Linux#115

Merged
williajm merged 25 commits into
mainfrom
fix/simple-volume-validation
Nov 15, 2025
Merged

feat: Simple volume mount validation for Linux#115
williajm merged 25 commits into
mainfrom
fix/simple-volume-validation

Conversation

@williajm
Copy link
Copy Markdown
Owner

Summary

Simple, maintainable volume mount validation (~85 lines vs previous 1000+ line approach).

Philosophy: Prevent common Linux mistakes, trust users for advanced cases. Not a security fortress - just accident prevention.

What's New

  • Named volume detection: Docker-managed volumes always allowed (they're safe)
  • Basic path normalization: Collapse duplicate slashes, handle Windows separators
  • Default Linux blocklist: /etc, /root, /var/run/docker.sock, credential dirs
  • Optional allowlist: Restrict to specific paths if needed
  • YOLO mode: SAFETY_YOLO_MODE=true bypasses all checks (user responsibility)

Configuration

# Bypass all safety checks (for advanced users)
SAFETY_YOLO_MODE=true

# Custom blocklist (comma-separated)
SAFETY_VOLUME_MOUNT_BLOCKLIST=/etc,/root,/var/run/docker.sock

# Restrict to specific paths (empty = allow all except blocked)
SAFETY_VOLUME_MOUNT_ALLOWLIST=/home/user/data,/opt/myapp

Examples

# Named volumes: Always allowed
docker_create_container(image="nginx", volumes={"mydata": {"bind": "/data"}})

# Safe path: Allowed by default
docker_create_container(image="nginx", volumes={"/home/user/data": {"bind": "/data"}})

# Blocked path: Raises UnsafeOperationError
docker_create_container(image="nginx", volumes={"/etc": {"bind": "/etc"}})

# Blocked path with YOLO mode: Allowed (user responsibility)
SAFETY_YOLO_MODE=true docker_create_container(image="nginx", volumes={"/etc": {"bind": "/etc"}})

Testing

Manual smoke test confirms:

  • Named volumes detected correctly
  • Blocklist enforcement works
  • YOLO mode bypasses all checks

Why Simple?

Previous approach grew to 1000+ lines with Unix duplicate slash normalization, Windows root drive logic, UNC share conversion, path traversal resolution, symlink resolution, etc.

This approach: Just prevent obvious Linux mistakes. Let users handle edge cases via YOLO mode.

🤖 Generated with Claude Code

williajm and others added 2 commits November 15, 2025 21:04
Simple, maintainable approach focused on preventing common mistakes:

- Named volumes: Always allowed (managed by Docker, safe)
- Default blocklist: /etc, /root, /var/run/docker.sock, credential dirs
- YOLO mode: SAFETY_YOLO_MODE=true bypasses all validation
- Configurable: SAFETY_VOLUME_MOUNT_BLOCKLIST, SAFETY_VOLUME_MOUNT_ALLOWLIST

Philosophy: Prevent accidents, trust users for advanced cases.
No complex Windows path normalization or edge case handling.
About 85 lines of code vs 1000+ in previous over-engineered approach.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
Simple, maintainable approach (~85 lines):
- Named volume detection (always safe)
- Basic path normalization (collapse slashes, handle Windows separators)
- Default Linux blocklist (/etc, /root, docker.sock, credential dirs)
- Optional allowlist for strict mode
- YOLO mode to bypass all checks

Philosophy: Prevent common Linux mistakes, trust users for advanced cases.
Not a security fortress - just accident prevention.

Configuration:
- SAFETY_YOLO_MODE: Bypass all checks (user responsibility)
- SAFETY_VOLUME_MOUNT_BLOCKLIST: Custom blocklist
- SAFETY_VOLUME_MOUNT_ALLOWLIST: Restrict to specific paths

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
Comment thread .github/workflows/gemini-dispatch.yml Fixed
Comment thread .github/workflows/gemini-invoke.yml Fixed
Comment thread .github/workflows/gemini-scheduled-triage.yml Fixed
Comment thread .github/workflows/gemini-triage.yml Fixed
Comment thread .github/workflows/gemini-invoke.yml Fixed
Comment thread .github/workflows/gemini-scheduled-triage.yml Fixed
Comment thread .github/workflows/gemini-triage.yml Fixed
Removed 4 Gemini workflow files that were accidentally added:
- gemini-dispatch.yml
- gemini-invoke.yml
- gemini-triage.yml
- gemini-scheduled-triage.yml

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/mcp_docker/utils/safety.py Outdated
williajm and others added 3 commits November 15, 2025 21:10
Removed security review markdown files from version control:
- security_review_gpt-5.md
- security_review_claude.md
- security_review_tasks.md
- security_review_gemini.md

Files are preserved locally and added to .gitignore.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
Removed volume_mount_validation_proposal.md from version control.
File is preserved locally and added to .gitignore.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
Added 20 new unit tests for the simple volume mount validation:

Named Volume Detection (4 tests):
- Simple alphanumeric volume names
- Paths with separators (not named volumes)
- Volumes starting with dot
- Special characters

Mount Path Validation (16 tests):
- YOLO mode bypasses all checks
- Named volumes always allowed
- Safe paths allowed
- Default blocklist (etc, root, docker socket, SSH, credentials)
- Custom blocklist
- Empty blocklist
- Path normalization (duplicate slashes, Windows separators)
- Allowlist restricts to specific paths
- Allowlist + blocklist interaction
- Error messages include path and suggest YOLO mode

All 78 safety unit tests passing.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
@codecov
Copy link
Copy Markdown

codecov Bot commented Nov 15, 2025

Codecov Report

❌ Patch coverage is 94.11765% with 5 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
src/mcp_docker/tools/container_lifecycle_tools.py 60.00% 4 Missing ⚠️
src/mcp_docker/security/rate_limiter.py 92.85% 1 Missing ⚠️

📢 Thoughts on this report? Let us know!

@github-actions
Copy link
Copy Markdown

github-actions Bot commented Nov 15, 2025

🧪 Test Results for 91ac4d4

Summary

All tests passed!

Test Suite Status Passed Failed Skipped Duration
Unit Tests - Python 3.11 850 0 0 57s
Unit Tests - Python 3.13 850 0 0 1.0m
Unit Tests - Python 3.12 850 0 0 1.0m
Unit Tests - Python 3.14 850 0 0 59s
E2E 62 0 0 1.3m
Integration 83 0 1 1.9m

| Total | ✅ | 3545 | 0 | 1 | - |

📊 Coverage

Coverage reports are available in the workflow artifacts.

📦 Download Reports

📊 View Full Report |
📝 All Checks

williajm and others added 5 commits November 15, 2025 21:20
Improved volume mount validation to catch credential directories
anywhere in the path, not just at root level.

Changes:
- Credential dirs (.ssh, .aws, .kube, .docker) now use substring matching
- Blocks /home/user/.ssh, /opt/app/.docker, etc.
- System paths (/etc, /root, docker.sock) use prefix matching
- Credential protection is always active (even with empty blocklist)

Tests:
- Added 3 new tests for credential directory substring matching
- Updated existing tests to verify new behavior
- All 79 safety unit tests passing

This closes the security gap where /home/user/.ssh was previously
allowed despite comments saying credential dirs were blocked.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
The SECURITY.md documentation claimed the startup scripts enable
"TLS, OAuth, and security features" but both scripts explicitly
set SECURITY_OAUTH_ENABLED=false and display "Authentication: DISABLED".

Changes:
- Updated SECURITY.md to accurately describe what's enabled by default
- Added clear checkmarks showing OAuth is disabled by default
- Added note that OAuth must be manually enabled by editing scripts
- Scripts themselves were already honest (comments and output show disabled)

What the scripts actually enable:
✅ TLS/HTTPS (requires certs)
✅ Rate limiting (60 req/min)
✅ Audit logging
❌ OAuth/OIDC (disabled, must manually enable)

This fixes the discrepancy between docs and actual script behavior.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
CRITICAL SECURITY FIX: Environment variables in docker exec were not
validated, allowing command injection attacks.

Attack Vector:
- ExecCommandTool accepted arbitrary environment variables
- Combined with shell commands like ["sh", "-c", "$VAR"]
- Attacker payload: {"environment": {"X": "$(cat /etc/passwd)"}}
- Shell would execute the command substitution

Fix:
- Enhanced validate_environment_variable() to reject dangerous chars
- Blocks: $(, `, ;, &, |, \n, \r (command injection characters)
- Added validation loop in ExecCommandTool.execute()
- Safe values (paths, URLs, flags) still allowed

Tests:
- Added 8 new unit tests for command injection scenarios
- All 87 safety unit tests passing
- Verified attacks are blocked while legitimate values work

Security Impact: CVSS 8.1 → Mitigated
Status: H1 HIGH PRIORITY issue resolved

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
CRITICAL SECURITY FIX: Environment variables were exposed in
generate_compose prompt, leaking secrets to remote LLM APIs.

Attack Vector:
- generate_compose prompt included full env var values
- Example: DATABASE_URL=postgresql://user:PASSWORD@db/app
- Secrets sent to Claude API, OpenAI API, etc.
- Potentially logged, used for training, or exposed

Fix:
- Redact all environment variable values in prompts
- Show only keys: DATABASE_URL=<REDACTED>
- Added warning in prompt description
- Documented protection in SECURITY.md

Implementation:
- Simple redaction: var.split("=", 1)[0] + "=<REDACTED>"
- Applied in GenerateComposePrompt.generate()
- LLM still knows which env vars exist (enough for compose generation)
- No secrets sent to remote APIs

Tests:
- Added comprehensive test with real secret examples
- Verifies passwords, API keys, AWS secrets NOT in output
- Verifies keys shown with <REDACTED>
- All 7 GenerateComposePrompt tests passing

Documentation:
- Added "Secret Redaction in Prompts" section to SECURITY.md
- Explains the risk, protection mechanism, and scope
- Updated overview to list this as security feature #10

Security Impact: HIGH severity credential disclosure → Mitigated
Status: H2 HIGH PRIORITY issue resolved

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
SECURITY FIX: Rate limiter created unbounded semaphores for each
unique client_id, allowing attackers to exhaust memory with many
fake client IDs.

Changes:
- Add max_clients limit to RateLimiter (default: 10, max: 100)
- Reject new clients when limit reached with clear error message
- Add SECURITY_RATE_LIMIT_MAX_CLIENTS config option (env var)
- Existing clients unaffected - can still acquire slots

Implementation:
- src/mcp_docker/security/rate_limiter.py: Add max_clients check
- src/mcp_docker/config.py: Add rate_limit_max_clients field
- src/mcp_docker/server.py: Pass config to RateLimiter
- tests/unit/test_security/test_rate_limiter.py: 3 new tests

Tests:
- test_init_max_clients: Verify max_clients is set
- test_max_clients_limit_enforced: Verify limit blocks new clients
- test_existing_client_can_acquire_at_max_clients: Existing clients OK

All 19 rate limiter tests passing.

Security Impact: HIGH severity DoS → Mitigated
Default: 10 max clients (configurable 1-100)
Status: H5 HIGH PRIORITY issue resolved

🤖 Generated with Claude Code
@williajm williajm force-pushed the fix/simple-volume-validation branch from 680bccd to a1ff724 Compare November 15, 2025 21:58
Add test coverage to verify that dangerous patterns are properly
detected in list-form commands (e.g., ['rm', '-rf', '/']).

While the implementation already correctly validates list commands
by joining them to strings before pattern matching, there were no
tests explicitly verifying this behavior.

Tests Added (6 new, all fast unit tests):
- test_sanitize_command_list_rm_rf_root: Verify rm -rf / blocked
- test_sanitize_command_list_shutdown: Verify shutdown blocked
- test_sanitize_command_list_curl_pipe_bash: Verify curl|bash blocked
- test_sanitize_command_list_chmod_777_root: Verify chmod 777 / blocked
- test_sanitize_command_list_dd_disk_wipe: Verify dd disk wipe blocked
- test_sanitize_command_list_safe_commands: Verify safe commands allowed

All 93 safety tests passing in 0.12s.

Addresses M5 concern from security review (no code changes needed,
implementation was already correct, just missing test coverage).

🤖 Generated with Claude Code
SECURITY FIX: Audit log directory and files were created with
world-readable permissions (0o755/0o644), allowing any user on
the system to read sensitive operation logs.

Changes:
- Set directory permissions to 0o700 (owner-only access)
- Set file permissions to 0o600 (owner-only read/write)
- Fix permissions on existing directories (chmod after mkdir)

Implementation:
- src/mcp_docker/security/audit.py: Add chmod calls after mkdir/logger.add
- tests/unit/test_security/test_audit.py: 3 new permission tests

Tests Added (3 new, all fast unit tests):
- test_audit_log_directory_permissions: Verify directory is 0o700
- test_audit_log_file_permissions: Verify file is 0o600
- test_audit_log_permissions_existing_directory: Verify existing dirs fixed

All 11 audit logger tests passing in 0.21s.

Security Impact: LOW severity information disclosure → Mitigated
Permissions: Directory 0o700, File 0o600 (owner-only)
Status: L2 LOW PRIORITY issue resolved

🤖 Generated with Claude Code
Version bump and changelog update for v1.1.1 release.

This release includes critical security fixes and new safety features:
- Command injection prevention (H1)
- Secret redaction in prompts (H2)
- Rate limiter DoS protection (H5)
- Simple volume mount validation
- Audit log permissions hardening (L2)

Changes:
- pyproject.toml: version 1.1.1.dev0 → 1.1.1
- CHANGELOG.md: Move Unreleased to [1.1.1] - 2025-11-15
  - Added: Volume mount validation, rate limiter max clients, audit log permissions
  - Security: 4 security fixes (H1, H2, H5, L2)
  - Fixed: Documentation accuracy for OAuth
  - Tests: 41 new unit tests, all passing

Ready for release.

🤖 Generated with Claude Code
Extract container context building into separate helper method to
reduce cognitive complexity from 16 to 15.

Changes:
- Add _build_container_context() helper method
- Move all container data extraction and formatting logic to helper
- Simplify generate() method by calling helper instead of inline logic

Benefits:
- Reduced cognitive complexity (16 → 15)
- Better separation of concerns
- More testable code structure
- No functional changes

All 7 GenerateComposePrompt tests passing.

🤖 Generated with Claude Code
SECURITY FIX: Volume mount validation was vulnerable to path
traversal bypass attacks using .. segments (e.g., ../../etc).

Attack Vector:
- Attacker provides: "../../etc" or "../../../../var/run/docker.sock"
- Previous validation: "/../../etc" doesn't start with "/etc" ✗ BYPASSED
- Docker resolves the path, potentially mounting blocked directories

Fix:
- Reject any mount path containing ".." segments
- Simple check: if ".." in normalized → raise UnsafeOperationError
- Clear error message directs users to use absolute paths
- YOLO mode bypass available for advanced users

Changes:
- src/mcp_docker/utils/safety.py: Add .. check after path normalization
- tests/unit/test_safety.py: 5 new tests for path traversal

Tests Added (5 new, all passing in 0.10s):
- test_validate_mount_path_blocks_path_traversal_leading: ../../etc
- test_validate_mount_path_blocks_path_traversal_middle: /home/user/../../etc
- test_validate_mount_path_blocks_path_traversal_multiple: ../../../../var/run/docker.sock
- test_validate_mount_path_blocks_path_traversal_docker_socket: ../../var/run/docker.sock
- test_validate_mount_path_yolo_mode_allows_path_traversal: YOLO mode bypass works

All 98 safety tests passing in 0.12s.

Security Impact: MEDIUM-HIGH severity path traversal → Mitigated
Status: P1 CRITICAL issue resolved

🤖 Generated with Claude Code
@williajm
Copy link
Copy Markdown
Owner Author

@codex review

@williajm
Copy link
Copy Markdown
Owner Author

@claude review

@github-actions
Copy link
Copy Markdown

github-actions Bot commented Nov 15, 2025

Claude finished @williajm's task —— View job


Code Review

I've reviewed the PR implementing simple volume mount validation for Linux. Here's my comprehensive analysis:


Summary

This PR implements a pragmatic approach to volume mount validation that focuses on preventing common mistakes while remaining maintainable. The implementation is clean and well-tested, with 94% coverage on new code.

Strengths

  1. Clear Philosophy: The "prevent accidents, trust advanced users" approach is well-executed with YOLO mode as an escape hatch
  2. Named Volume Detection: Smart recognition of Docker-managed volumes (src/mcp_docker/utils/safety.py:364-387)
  3. Path Traversal Protection: Blocks .. sequences to prevent bypass attempts (safety.py:422-426)
  4. Comprehensive Testing: 286 lines of new tests with excellent coverage of edge cases
  5. Good Documentation: SECURITY.md updated, clear error messages suggesting YOLO mode

Critical Security Issue (P1)

The Codex P1 review comment is valid and needs to be addressed.

Issue: Credential directories are only blocked via substring matching, but the current blocklist entries like "/.ssh", "/.aws", etc. in the default list (safety.py:430-437) will only match paths that start with these exact strings (e.g., /.ssh matches root-level only).

Evidence from code (safety.py:436-455):

# Default credential directories (substring matching to catch /home/user/.ssh etc.)
credential_dirs = ["/.ssh", "/.aws", "/.kube", "/.docker"]

# Check system paths (prefix matching)
for blocked in blocked_paths:
    if normalized.startswith(blocked):  # Prefix matching
        raise UnsafeOperationError(...)

# Check credential directories (substring matching to catch any user)
for cred_dir in credential_dirs:
    if cred_dir in normalized:  # Substring matching - THIS WORKS CORRECTLY
        raise UnsafeOperationError(...)

Actually, the implementation is CORRECT! The credential directories use in operator for substring matching (line 450), not startswith(). The comment at line 436 clearly states "substring matching to catch /home/user/.ssh etc."

Verification from tests (test_safety.py:452-472):

def test_validate_mount_path_credential_dirs_under_home(self) -> None:
    """Test credential directories are blocked under /home (substring matching)."""
    # This is the key test - credential dirs anywhere in path are blocked
    with pytest.raises(UnsafeOperationError, match="credential directory"):
        validate_mount_path("/home/user/.ssh")  # ✅ BLOCKED
    with pytest.raises(UnsafeOperationError, match="credential directory"):
        validate_mount_path("/home/jmw/.aws")    # ✅ BLOCKED

Conclusion: The Codex P1 concern is incorrect. The implementation correctly blocks credential directories anywhere in the path via substring matching. The tests confirm this works as intended.


Minor Issues

1. Missing Coverage (4 lines in container_lifecycle_tools.py)

Codecov reports 4 lines missing coverage at 94.36%. Based on the container lifecycle tools implementation, these are likely in error handling paths. Consider adding tests for:

  • Volume validation failure paths
  • Edge cases in _validate_inputs() method

2. Code Clarity - Inconsistent Comments

The comment at safety.py:264-268 describes the blocklist behavior but could be clearer about the dual-system approach (prefix matching for system paths, substring matching for credentials).

Suggestion:

# Default blocklist: system paths (prefix matching)
# Credential directories are handled separately via substring matching below
if blocked_paths is None:
    blocked_paths = [
        "/etc",  # System configuration
        "/root",  # Root user home
        "/var/run/docker.sock",  # Docker socket (container escape)
    ]

3. Potential False Positives

The substring matching for credential dirs ("/.ssh" in normalized) could theoretically match legitimate paths like /home/user/projects/.sshfs-cache. However, this is acceptable given the philosophy - users can enable YOLO mode for such edge cases.


Design Feedback

Philosophy Validation

The simple approach is appropriate for the stated goals:

  • ✅ Blocks common Linux mistakes (/etc, /root, Docker socket)
  • ✅ Protects credentials anywhere in path
  • ✅ Provides escape hatch (YOLO mode)
  • ✅ Significantly simpler than 1000-line alternative

Path Normalization

The normalization is minimal but sufficient:

normalized = path.replace("\\", "/")  # Handle Windows paths
normalized = "/" + normalized.lstrip("/")  # Collapse duplicate leading slashes

Limitation: Doesn't handle:

  • Symbolic links (intentional - would require filesystem access)
  • Relative paths like ./etc (caught by path traversal check)
  • Case sensitivity variations (Linux is case-sensitive, so acceptable)

This aligns with the "simple validation" philosophy.

YOLO Mode Naming

The SAFETY_YOLO_MODE naming is clear and communicates risk well. Users who enable it understand they're taking responsibility.


Test Quality

Excellent test coverage with good organization:

  1. Named Volume Detection (lines 366-396): Comprehensive coverage of valid/invalid volume names
  2. Blocklist Testing (lines 424-443): Default blocklist enforcement
  3. Credential Directory Protection (lines 452-472): The key security tests - validates substring matching works
  4. Path Traversal (lines 550-575): Thorough coverage of .. bypass attempts
  5. YOLO Mode (lines 401-407, 570-575): Confirms escape hatch works

Recommendations

Must Fix

None - the Codex P1 concern is a false positive.

Should Consider

  1. Coverage: Add tests for the 4 missing lines in container lifecycle tools
  2. Documentation: Consider adding an example of a false positive case and how to handle it with YOLO mode

Nice to Have

  1. Error Messages: Already excellent - they mention the blocked path and suggest YOLO mode
  2. Config Validation: Good - empty blocklist still protects credentials (test_safety.py:487-496)

Conclusion

This is a well-designed, well-implemented, and well-tested feature. The simple approach is appropriate for the stated goals, and the implementation correctly handles the security concerns raised by automated tools.

Recommendation: ✅ Approve (after addressing the 4 lines of missing coverage if desired, though 94.36% is excellent)

The Codex P1 security concern is a false positive - the credential directory protection works correctly via substring matching as evidenced by both the implementation and comprehensive tests.


References

  • Core implementation: src/mcp_docker/utils/safety.py:364-465
  • Configuration: src/mcp_docker/config.py:249-276
  • Tool integration: src/mcp_docker/tools/container_lifecycle_tools.py:188-197
  • Tests: tests/unit/test_safety.py:366-575

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/mcp_docker/security/rate_limiter.py
@github-actions
Copy link
Copy Markdown

Code Review - PR #115

Summary: This PR implements simple volume mount validation and addresses multiple security vulnerabilities. Excellent security work with strong test coverage.

Strengths:

  1. Security Fixes (Critical) - Command injection prevention, secret redaction, rate limiter DoS protection, audit log permissions
  2. Volume Mount Validation (Well-Designed) - Named volume detection, credential directory blocking, path traversal protection, YOLO mode
  3. Test Coverage (Outstanding) - 41 new unit tests covering edge cases
  4. Code Quality - Type hints, proper error messages, Pydantic validation, good refactoring

Potential Issues:

  1. Environment Variable Validation May Be Too Strict - Blocking single ampersand will prevent legitimate query strings in DATABASE_URL. Consider blocking only double ampersand.
  2. Path Normalization Missing Case - Does not collapse internal duplicate slashes like /home//user////.ssh
  3. Version Bump - Should be .dev0 per CLAUDE.md workflow unless this is the actual PyPI release
  4. Edge cases: symlinks not resolved, case sensitivity on macOS, empty string paths

Recommendations:
Priority 1: Relax env var validation for ampersands, improve path normalization, clarify version intent
Priority 2: Add empty string check, document symlink limitation, case-insensitive matching

Final Verdict: APPROVE with minor recommendations

This PR significantly improves security posture. The volume mount validation strikes the right balance between safety and usability. Critical security fixes are solid. Test coverage is outstanding, code quality is high.

Great work!

CRITICAL BUG FIX: Rate limiter never removed client semaphores when
concurrent requests reached zero, causing permanent DoS after
max_clients unique clients had connected once.

The Problem:
- acquire_concurrent_slot() checks: len(self._semaphores) >= max_clients
- release_concurrent_slot() decremented counter but never removed semaphore
- After 10 clients connected once (max_clients=10), semaphores dict had 10 entries
- All subsequent clients rejected forever, even with zero active load
- Turned memory-exhaustion protection into permanent denial of service

The Fix:
- release_concurrent_slot() now removes semaphore when concurrent_requests == 0
- Cleanup happens automatically when client becomes idle
- max_clients now applies to ACTIVE clients, not "all clients ever seen"

Changes:
- src/mcp_docker/security/rate_limiter.py: Add cleanup logic in release_concurrent_slot()
- tests/unit/test_security/test_rate_limiter.py: 3 new tests

Tests Added (3 new, all passing in 0.27s):
- test_client_cleanup_when_idle: Verify cleanup when concurrent_requests reaches 0
- test_new_clients_allowed_after_cleanup: Verify no permanent DoS after max_clients
- test_partial_cleanup_with_multiple_slots: Verify cleanup only when ALL slots released

All 22 rate limiter tests passing in 1.49s.

Bug Impact: CRITICAL permanent DoS after N clients → Fixed
Status: P1 CRITICAL issue resolved

🤖 Generated with Claude Code
@williajm
Copy link
Copy Markdown
Owner Author

@codex review

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/mcp_docker/security/rate_limiter.py Outdated
Fixed race condition in rate limiter cleanup logic where concurrent
releases would delete semaphore entries while other threads were
still accessing them, causing KeyError in integration tests.

Solution: Instead of deleting idle client entries, count only ACTIVE
clients (concurrent_requests > 0) toward max_clients limit. This
prevents both race conditions and permanent DoS attacks.

Changes:
- Modified acquire_concurrent_slot() to count only active clients
- Simplified release_concurrent_slot() to remove cleanup logic
- Updated 3 unit tests to reflect new behavior (no cleanup)

Tests: All 22 unit tests and 83 integration tests passing

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
@williajm
Copy link
Copy Markdown
Owner Author

@codex review

Added race condition fix to v1.1.1 Fixed section documenting
the KeyError bug fix in concurrent operations.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/mcp_docker/security/rate_limiter.py Outdated
Comment thread src/mcp_docker/utils/safety.py
Fixed memory leak where attackers could cycle through unbounded unique
client IDs, each leaving an idle semaphore entry in memory.

Previous fix counted only active clients (count > 0), allowing attackers
to bypass the limit by using sequential unique IDs. Each would connect,
disconnect, and leave an idle entry that didn't count toward max_clients.

Solution: Check total dictionary size (len(_semaphores)) instead of
counting only active entries. This prevents unbounded memory growth.

Tradeoff: After max_clients unique clients connect, no new clients can
connect until server restart. But this is acceptable for DoS protection
that prioritizes server stability over perfect UX.

Changes:
- Modified acquire_concurrent_slot() to check len(_semaphores)
- Updated test to verify new clients blocked at max tracked clients
- Updated CHANGELOG to document the fix

Tests: All 22 unit tests and 2 integration tests passing

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
@williajm
Copy link
Copy Markdown
Owner Author

@codex review

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/mcp_docker/security/rate_limiter.py Outdated
williajm and others added 2 commits November 15, 2025 23:37
Fixes permanent client banning bug where new clients were permanently
rejected after max_clients capacity was reached, even when previous
clients were idle.

Solution: When at max_clients capacity, evict the first idle client
(count==0) to make room for new client. Only reject when all tracked
clients have active requests.

Changes:
- Rate limiter now evicts idle clients when at capacity (LRU policy)
- Only rejects new clients when all tracked clients are active
- Balances memory protection with normal multi-user operation
- Prevents permanent DoS from idle client accumulation

Tests:
- test_idle_client_eviction_allows_new_clients: Verifies eviction
- test_active_clients_block_new_clients: Verifies rejection logic
- All 23 rate limiter tests passing

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
@williajm
Copy link
Copy Markdown
Owner Author

@codex review

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/mcp_docker/utils/safety.py Outdated
Comment on lines +512 to +515
"`", # Backtick command substitution
";", # Command separator
"&", # Background execution / command chaining
"|", # Pipe to another command
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Allow ampersands and pipes in exec environment variables

The new validation treats & and | as "dangerous characters" and raises ValidationError whenever they appear in an environment variable value. However, environment variables passed to ExecCommandTool are sent to the Docker API as structured data, not through a shell, so characters such as & or | are perfectly safe and quite common in connection strings or URLs (e.g. postgres://…?sslmode=require&pool=10). After this change such legitimate values are rejected and the exec command cannot run. Consider limiting the validation to cases where the value is actually interpolated into a shell command, or removing these characters from the block list to avoid breaking normal usage.

Useful? React with 👍 / 👎.

williajm and others added 3 commits November 15, 2025 23:50
…nerTool

Code review identified that environment variable validation for command
injection was only applied in ExecCommandTool, not in CreateContainerTool.
This left the H1 command injection vulnerability exploitable when creating
containers with malicious environment variables.

Changes:
- Import validate_environment_variable in container_lifecycle_tools.py
- Add validation loop in CreateContainerTool._validate_inputs()
- Validates all environment variables for dangerous characters before creation
- Prevents command injection via $(cmd), backticks, semicolons, pipes, etc.

Impact:
- Closes H1 security gap in docker_create_container tool
- Validation now applied consistently across both container creation and exec

Testing:
- Existing unit tests validate the function works (12 tests)
- Integration test confirms validation is applied in container creation
- All tests passing

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
Removed overly strict validation that blocked & and | characters in
environment variable values. These are safe because Docker passes env
vars as structured data to the API, not through a shell.

Why this is safe:
- Docker SDK passes environment variables as dict to API
- Not interpolated into shell commands
- Only dangerous if value is later used in a shell command context

Why this matters:
- Connection strings commonly use & for parameters
  Example: postgres://localhost?sslmode=require&pool=10
- Filter strings may use | for OR logic
  Example: status=active|ready

Still blocking truly dangerous characters:
- $(  - command substitution
- `   - backtick substitution
- ;   - command separator
- \n  - newline injection
- \r  - carriage return injection

Changes:
- Removed & and | from dangerous_chars list in validate_environment_variable()
- Updated tests to verify ampersands and pipes are allowed
- Added realistic test cases with database connection strings

Testing:
- All 12 environment variable validation tests passing
- Tests verify legitimate connection strings now work

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
@williajm
Copy link
Copy Markdown
Owner Author

@codex review

@sonarqubecloud
Copy link
Copy Markdown

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/mcp_docker/utils/safety.py
@williajm williajm merged commit 4775d35 into main Nov 15, 2025
30 checks passed
@williajm williajm deleted the fix/simple-volume-validation branch November 15, 2025 23:58
williajm added a commit that referenced this pull request Nov 20, 2025
…urity (#68)

## Problem
OpenSSF Scorecard flagged 6 critical security alerts for unpinned dependencies
in the ClusterFuzzLite fuzzing infrastructure, creating potential supply chain
vulnerabilities.

## Changes

### Dockerfile (.clusterfuzzlite/Dockerfile)
1. **Pin Docker base image by SHA256 hash**
   - Before: `gcr.io/oss-fuzz-base/base-builder-python`
   - After: `gcr.io/oss-fuzz-base/base-builder-python@sha256:122afa...`
   - Ensures reproducible builds and prevents malicious image substitution

2. **Pin pip to specific version**
   - Pinned to pip==24.3.1
   - Ensures consistent package resolution across builds

3. **Pin all Python dependencies**
   - atheris==2.3.0 (fuzzing engine)
   - pydantic==2.10.4 (validation library)
   - pydantic-settings==2.6.1 (settings management)
   - loguru==0.7.3 (logging)
   - cryptography==44.0.0 (SSH authentication)

### Build Script (.clusterfuzzlite/build.sh)
1. **Add --no-deps flag to local install**
   - Prevents pip from installing unpinned transitive dependencies
   - All dependencies are already installed with pinned versions in Dockerfile

2. **Add documentation comments**
   - Explains why local project install doesn't need version pinning
   - Clarifies that external dependencies are pinned elsewhere

## Security Impact
- ✅ Resolves 6 Pinned-Dependencies security alerts
- ✅ Improves OpenSSF Scorecard rating
- ✅ Prevents supply chain attacks via dependency confusion
- ✅ Ensures reproducible fuzzing builds
- ✅ Maintains compatibility with existing fuzz tests

## Testing
- All pinned versions meet minimum requirements in pyproject.toml
- Dependencies tested in main CI pipeline
- No functional changes to fuzzing behavior

## Related
- Addresses: https://github.com/williajm/mcp_docker/security/code-scanning
- Alerts: #115, #116, #117, #118, #119, #120

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude <noreply@anthropic.com>
williajm added a commit that referenced this pull request Nov 20, 2025
* feat: Add simple volume mount validation for Linux

Simple, maintainable approach focused on preventing common mistakes:

- Named volumes: Always allowed (managed by Docker, safe)
- Default blocklist: /etc, /root, /var/run/docker.sock, credential dirs
- YOLO mode: SAFETY_YOLO_MODE=true bypasses all validation
- Configurable: SAFETY_VOLUME_MOUNT_BLOCKLIST, SAFETY_VOLUME_MOUNT_ALLOWLIST

Philosophy: Prevent accidents, trust users for advanced cases.
No complex Windows path normalization or edge case handling.
About 85 lines of code vs 1000+ in previous over-engineered approach.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* feat: Add simple volume mount validation for Linux

Simple, maintainable approach (~85 lines):
- Named volume detection (always safe)
- Basic path normalization (collapse slashes, handle Windows separators)
- Default Linux blocklist (/etc, /root, docker.sock, credential dirs)
- Optional allowlist for strict mode
- YOLO mode to bypass all checks

Philosophy: Prevent common Linux mistakes, trust users for advanced cases.
Not a security fortress - just accident prevention.

Configuration:
- SAFETY_YOLO_MODE: Bypass all checks (user responsibility)
- SAFETY_VOLUME_MOUNT_BLOCKLIST: Custom blocklist
- SAFETY_VOLUME_MOUNT_ALLOWLIST: Restrict to specific paths

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* chore: Remove accidental Gemini workflows

Removed 4 Gemini workflow files that were accidentally added:
- gemini-dispatch.yml
- gemini-invoke.yml
- gemini-triage.yml
- gemini-scheduled-triage.yml

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* chore: Remove security review files from git, keep locally

Removed security review markdown files from version control:
- security_review_gpt-5.md
- security_review_claude.md
- security_review_tasks.md
- security_review_gemini.md

Files are preserved locally and added to .gitignore.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* chore: Remove validation proposal from git, keep locally

Removed volume_mount_validation_proposal.md from version control.
File is preserved locally and added to .gitignore.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* test: Add comprehensive unit tests for volume mount validation

Added 20 new unit tests for the simple volume mount validation:

Named Volume Detection (4 tests):
- Simple alphanumeric volume names
- Paths with separators (not named volumes)
- Volumes starting with dot
- Special characters

Mount Path Validation (16 tests):
- YOLO mode bypasses all checks
- Named volumes always allowed
- Safe paths allowed
- Default blocklist (etc, root, docker socket, SSH, credentials)
- Custom blocklist
- Empty blocklist
- Path normalization (duplicate slashes, Windows separators)
- Allowlist restricts to specific paths
- Allowlist + blocklist interaction
- Error messages include path and suggest YOLO mode

All 78 safety unit tests passing.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* feat: Enhance credential directory protection with substring matching

Improved volume mount validation to catch credential directories
anywhere in the path, not just at root level.

Changes:
- Credential dirs (.ssh, .aws, .kube, .docker) now use substring matching
- Blocks /home/user/.ssh, /opt/app/.docker, etc.
- System paths (/etc, /root, docker.sock) use prefix matching
- Credential protection is always active (even with empty blocklist)

Tests:
- Added 3 new tests for credential directory substring matching
- Updated existing tests to verify new behavior
- All 79 safety unit tests passing

This closes the security gap where /home/user/.ssh was previously
allowed despite comments saying credential dirs were blocked.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* docs: Fix misleading claims about OAuth in startup scripts

The SECURITY.md documentation claimed the startup scripts enable
"TLS, OAuth, and security features" but both scripts explicitly
set SECURITY_OAUTH_ENABLED=false and display "Authentication: DISABLED".

Changes:
- Updated SECURITY.md to accurately describe what's enabled by default
- Added clear checkmarks showing OAuth is disabled by default
- Added note that OAuth must be manually enabled by editing scripts
- Scripts themselves were already honest (comments and output show disabled)

What the scripts actually enable:
✅ TLS/HTTPS (requires certs)
✅ Rate limiting (60 req/min)
✅ Audit logging
❌ OAuth/OIDC (disabled, must manually enable)

This fixes the discrepancy between docs and actual script behavior.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* security: Fix command injection via environment variables (H1)

CRITICAL SECURITY FIX: Environment variables in docker exec were not
validated, allowing command injection attacks.

Attack Vector:
- ExecCommandTool accepted arbitrary environment variables
- Combined with shell commands like ["sh", "-c", "$VAR"]
- Attacker payload: {"environment": {"X": "$(cat /etc/passwd)"}}
- Shell would execute the command substitution

Fix:
- Enhanced validate_environment_variable() to reject dangerous chars
- Blocks: $(, `, ;, &, |, \n, \r (command injection characters)
- Added validation loop in ExecCommandTool.execute()
- Safe values (paths, URLs, flags) still allowed

Tests:
- Added 8 new unit tests for command injection scenarios
- All 87 safety unit tests passing
- Verified attacks are blocked while legitimate values work

Security Impact: CVSS 8.1 → Mitigated
Status: H1 HIGH PRIORITY issue resolved

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* security: Redact environment variable values in prompts (H2)

CRITICAL SECURITY FIX: Environment variables were exposed in
generate_compose prompt, leaking secrets to remote LLM APIs.

Attack Vector:
- generate_compose prompt included full env var values
- Example: DATABASE_URL=postgresql://user:PASSWORD@db/app
- Secrets sent to Claude API, OpenAI API, etc.
- Potentially logged, used for training, or exposed

Fix:
- Redact all environment variable values in prompts
- Show only keys: DATABASE_URL=<REDACTED>
- Added warning in prompt description
- Documented protection in SECURITY.md

Implementation:
- Simple redaction: var.split("=", 1)[0] + "=<REDACTED>"
- Applied in GenerateComposePrompt.generate()
- LLM still knows which env vars exist (enough for compose generation)
- No secrets sent to remote APIs

Tests:
- Added comprehensive test with real secret examples
- Verifies passwords, API keys, AWS secrets NOT in output
- Verifies keys shown with <REDACTED>
- All 7 GenerateComposePrompt tests passing

Documentation:
- Added "Secret Redaction in Prompts" section to SECURITY.md
- Explains the risk, protection mechanism, and scope
- Updated overview to list this as security feature #10

Security Impact: HIGH severity credential disclosure → Mitigated
Status: H2 HIGH PRIORITY issue resolved

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* security: Prevent rate limiter memory exhaustion DoS (H5)

SECURITY FIX: Rate limiter created unbounded semaphores for each
unique client_id, allowing attackers to exhaust memory with many
fake client IDs.

Changes:
- Add max_clients limit to RateLimiter (default: 10, max: 100)
- Reject new clients when limit reached with clear error message
- Add SECURITY_RATE_LIMIT_MAX_CLIENTS config option (env var)
- Existing clients unaffected - can still acquire slots

Implementation:
- src/mcp_docker/security/rate_limiter.py: Add max_clients check
- src/mcp_docker/config.py: Add rate_limit_max_clients field
- src/mcp_docker/server.py: Pass config to RateLimiter
- tests/unit/test_security/test_rate_limiter.py: 3 new tests

Tests:
- test_init_max_clients: Verify max_clients is set
- test_max_clients_limit_enforced: Verify limit blocks new clients
- test_existing_client_can_acquire_at_max_clients: Existing clients OK

All 19 rate limiter tests passing.

Security Impact: HIGH severity DoS → Mitigated
Default: 10 max clients (configurable 1-100)
Status: H5 HIGH PRIORITY issue resolved

🤖 Generated with Claude Code

* test: Add list-based command validation tests

Add test coverage to verify that dangerous patterns are properly
detected in list-form commands (e.g., ['rm', '-rf', '/']).

While the implementation already correctly validates list commands
by joining them to strings before pattern matching, there were no
tests explicitly verifying this behavior.

Tests Added (6 new, all fast unit tests):
- test_sanitize_command_list_rm_rf_root: Verify rm -rf / blocked
- test_sanitize_command_list_shutdown: Verify shutdown blocked
- test_sanitize_command_list_curl_pipe_bash: Verify curl|bash blocked
- test_sanitize_command_list_chmod_777_root: Verify chmod 777 / blocked
- test_sanitize_command_list_dd_disk_wipe: Verify dd disk wipe blocked
- test_sanitize_command_list_safe_commands: Verify safe commands allowed

All 93 safety tests passing in 0.12s.

Addresses M5 concern from security review (no code changes needed,
implementation was already correct, just missing test coverage).

🤖 Generated with Claude Code

* security: Set restrictive permissions on audit log files (L2)

SECURITY FIX: Audit log directory and files were created with
world-readable permissions (0o755/0o644), allowing any user on
the system to read sensitive operation logs.

Changes:
- Set directory permissions to 0o700 (owner-only access)
- Set file permissions to 0o600 (owner-only read/write)
- Fix permissions on existing directories (chmod after mkdir)

Implementation:
- src/mcp_docker/security/audit.py: Add chmod calls after mkdir/logger.add
- tests/unit/test_security/test_audit.py: 3 new permission tests

Tests Added (3 new, all fast unit tests):
- test_audit_log_directory_permissions: Verify directory is 0o700
- test_audit_log_file_permissions: Verify file is 0o600
- test_audit_log_permissions_existing_directory: Verify existing dirs fixed

All 11 audit logger tests passing in 0.21s.

Security Impact: LOW severity information disclosure → Mitigated
Permissions: Directory 0o700, File 0o600 (owner-only)
Status: L2 LOW PRIORITY issue resolved

🤖 Generated with Claude Code

* chore: Release v1.1.1

Version bump and changelog update for v1.1.1 release.

This release includes critical security fixes and new safety features:
- Command injection prevention (H1)
- Secret redaction in prompts (H2)
- Rate limiter DoS protection (H5)
- Simple volume mount validation
- Audit log permissions hardening (L2)

Changes:
- pyproject.toml: version 1.1.1.dev0 → 1.1.1
- CHANGELOG.md: Move Unreleased to [1.1.1] - 2025-11-15
  - Added: Volume mount validation, rate limiter max clients, audit log permissions
  - Security: 4 security fixes (H1, H2, H5, L2)
  - Fixed: Documentation accuracy for OAuth
  - Tests: 41 new unit tests, all passing

Ready for release.

🤖 Generated with Claude Code

* refactor: Reduce cognitive complexity in generate_compose prompt

Extract container context building into separate helper method to
reduce cognitive complexity from 16 to 15.

Changes:
- Add _build_container_context() helper method
- Move all container data extraction and formatting logic to helper
- Simplify generate() method by calling helper instead of inline logic

Benefits:
- Reduced cognitive complexity (16 → 15)
- Better separation of concerns
- More testable code structure
- No functional changes

All 7 GenerateComposePrompt tests passing.

🤖 Generated with Claude Code

* security: Block path traversal in volume mount validation (P1)

SECURITY FIX: Volume mount validation was vulnerable to path
traversal bypass attacks using .. segments (e.g., ../../etc).

Attack Vector:
- Attacker provides: "../../etc" or "../../../../var/run/docker.sock"
- Previous validation: "/../../etc" doesn't start with "/etc" ✗ BYPASSED
- Docker resolves the path, potentially mounting blocked directories

Fix:
- Reject any mount path containing ".." segments
- Simple check: if ".." in normalized → raise UnsafeOperationError
- Clear error message directs users to use absolute paths
- YOLO mode bypass available for advanced users

Changes:
- src/mcp_docker/utils/safety.py: Add .. check after path normalization
- tests/unit/test_safety.py: 5 new tests for path traversal

Tests Added (5 new, all passing in 0.10s):
- test_validate_mount_path_blocks_path_traversal_leading: ../../etc
- test_validate_mount_path_blocks_path_traversal_middle: /home/user/../../etc
- test_validate_mount_path_blocks_path_traversal_multiple: ../../../../var/run/docker.sock
- test_validate_mount_path_blocks_path_traversal_docker_socket: ../../var/run/docker.sock
- test_validate_mount_path_yolo_mode_allows_path_traversal: YOLO mode bypass works

All 98 safety tests passing in 0.12s.

Security Impact: MEDIUM-HIGH severity path traversal → Mitigated
Status: P1 CRITICAL issue resolved

🤖 Generated with Claude Code

* fix: Cleanup idle clients in rate limiter to prevent permanent DoS

CRITICAL BUG FIX: Rate limiter never removed client semaphores when
concurrent requests reached zero, causing permanent DoS after
max_clients unique clients had connected once.

The Problem:
- acquire_concurrent_slot() checks: len(self._semaphores) >= max_clients
- release_concurrent_slot() decremented counter but never removed semaphore
- After 10 clients connected once (max_clients=10), semaphores dict had 10 entries
- All subsequent clients rejected forever, even with zero active load
- Turned memory-exhaustion protection into permanent denial of service

The Fix:
- release_concurrent_slot() now removes semaphore when concurrent_requests == 0
- Cleanup happens automatically when client becomes idle
- max_clients now applies to ACTIVE clients, not "all clients ever seen"

Changes:
- src/mcp_docker/security/rate_limiter.py: Add cleanup logic in release_concurrent_slot()
- tests/unit/test_security/test_rate_limiter.py: 3 new tests

Tests Added (3 new, all passing in 0.27s):
- test_client_cleanup_when_idle: Verify cleanup when concurrent_requests reaches 0
- test_new_clients_allowed_after_cleanup: Verify no permanent DoS after max_clients
- test_partial_cleanup_with_multiple_slots: Verify cleanup only when ALL slots released

All 22 rate limiter tests passing in 1.49s.

Bug Impact: CRITICAL permanent DoS after N clients → Fixed
Status: P1 CRITICAL issue resolved

🤖 Generated with Claude Code

* fix: Rate limiter race condition causing CI test failures

Fixed race condition in rate limiter cleanup logic where concurrent
releases would delete semaphore entries while other threads were
still accessing them, causing KeyError in integration tests.

Solution: Instead of deleting idle client entries, count only ACTIVE
clients (concurrent_requests > 0) toward max_clients limit. This
prevents both race conditions and permanent DoS attacks.

Changes:
- Modified acquire_concurrent_slot() to count only active clients
- Simplified release_concurrent_slot() to remove cleanup logic
- Updated 3 unit tests to reflect new behavior (no cleanup)

Tests: All 22 unit tests and 83 integration tests passing

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* docs: Add rate limiter race condition fix to CHANGELOG

Added race condition fix to v1.1.1 Fixed section documenting
the KeyError bug fix in concurrent operations.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* fix: P1 - Rate limiter memory exhaustion from unique client IDs

Fixed memory leak where attackers could cycle through unbounded unique
client IDs, each leaving an idle semaphore entry in memory.

Previous fix counted only active clients (count > 0), allowing attackers
to bypass the limit by using sequential unique IDs. Each would connect,
disconnect, and leave an idle entry that didn't count toward max_clients.

Solution: Check total dictionary size (len(_semaphores)) instead of
counting only active entries. This prevents unbounded memory growth.

Tradeoff: After max_clients unique clients connect, no new clients can
connect until server restart. But this is acceptable for DoS protection
that prioritizes server stability over perfect UX.

Changes:
- Modified acquire_concurrent_slot() to check len(_semaphores)
- Updated test to verify new clients blocked at max tracked clients
- Updated CHANGELOG to document the fix

Tests: All 22 unit tests and 2 integration tests passing

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* fix: P1 - Implement LRU eviction for idle clients in rate limiter

Fixes permanent client banning bug where new clients were permanently
rejected after max_clients capacity was reached, even when previous
clients were idle.

Solution: When at max_clients capacity, evict the first idle client
(count==0) to make room for new client. Only reject when all tracked
clients have active requests.

Changes:
- Rate limiter now evicts idle clients when at capacity (LRU policy)
- Only rejects new clients when all tracked clients are active
- Balances memory protection with normal multi-user operation
- Prevents permanent DoS from idle client accumulation

Tests:
- test_idle_client_eviction_allows_new_clients: Verifies eviction
- test_active_clients_block_new_clients: Verifies rejection logic
- All 23 rate limiter tests passing

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* docs: Clean up CHANGELOG - remove internal debugging details

* fix: CRITICAL - Apply environment variable validation in CreateContainerTool

Code review identified that environment variable validation for command
injection was only applied in ExecCommandTool, not in CreateContainerTool.
This left the H1 command injection vulnerability exploitable when creating
containers with malicious environment variables.

Changes:
- Import validate_environment_variable in container_lifecycle_tools.py
- Add validation loop in CreateContainerTool._validate_inputs()
- Validates all environment variables for dangerous characters before creation
- Prevents command injection via $(cmd), backticks, semicolons, pipes, etc.

Impact:
- Closes H1 security gap in docker_create_container tool
- Validation now applied consistently across both container creation and exec

Testing:
- Existing unit tests validate the function works (12 tests)
- Integration test confirms validation is applied in container creation
- All tests passing

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* docs: Clean up CHANGELOG - remove internal implementation details

* fix: Allow ampersands and pipes in environment variables

Removed overly strict validation that blocked & and | characters in
environment variable values. These are safe because Docker passes env
vars as structured data to the API, not through a shell.

Why this is safe:
- Docker SDK passes environment variables as dict to API
- Not interpolated into shell commands
- Only dangerous if value is later used in a shell command context

Why this matters:
- Connection strings commonly use & for parameters
  Example: postgres://localhost?sslmode=require&pool=10
- Filter strings may use | for OR logic
  Example: status=active|ready

Still blocking truly dangerous characters:
- $(  - command substitution
- `   - backtick substitution
- ;   - command separator
- \n  - newline injection
- \r  - carriage return injection

Changes:
- Removed & and | from dangerous_chars list in validate_environment_variable()
- Updated tests to verify ampersands and pipes are allowed
- Added realistic test cases with database connection strings

Testing:
- All 12 environment variable validation tests passing
- Tests verify legitimate connection strings now work

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

---------

Co-authored-by: Claude <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants