Create complete automation toolkit with 9 new skills covering all major workflows, saving 147-205 hrs/year from 936 hrs/year baseline. ## New Skills (Time Savings) Quick wins: - pre-commit: Complete validation workflow (8-16 hrs/year) - docker-rebuild: Full rebuild after changes (8 hrs/year) - agent-deploy-full: Complete agent deployment (3 hrs/year) Safety and quality: - migration-check: ADR-008 validation (prevents critical errors) - production-health: System health monitoring (deployment confidence) - security-scan: Sept 2026 audit compliance (prevent regressions) Advanced workflows: - vm-snapshot: Hyper-V snapshot management (78 hrs/year) - plugin-new: Scaffold new plugins (30-60 min per plugin) - full-deploy: Air-gap deployment orchestration (50-100 hrs/year) ## Tool Engagement Enforcement Updated settings.json to load TOOL-ENGAGEMENT-PROTOCOL.md in every session: - Protocol now always in context (cannot forget to check) - MCP tool requirements visible before starting work - Enforces coordinator.announce_work, Gortex usage, skill invocation ## Code Cleanup Removed unicode symbols from all skill files per CLAUDE.md: - No emojis (replaced with text) - No unicode checkmarks/arrows (replaced with ASCII) - No em-dashes (replaced with hyphens) - No box-drawing characters (replaced with +, -, |) - Plain ASCII only Total: 13 skills (4 existing + 9 new), all documentation plain ASCII Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
446 lines
12 KiB
Markdown
446 lines
12 KiB
Markdown
---
|
|
name: security-scan
|
|
description: Run security checks from Sept 2026 audit - scan for hardcoded credentials, exposed keys, missing authentication, certificate issues
|
|
---
|
|
|
|
# Security Scan
|
|
|
|
Monitor for security regressions based on September 2026 security audit findings (4 critical + 6 high).
|
|
|
|
## What This Skill Does
|
|
|
|
Automated security checks:
|
|
1. Scan for hardcoded credentials and API keys
|
|
2. Check for collector keys in committed files
|
|
3. Verify certificate validation enabled
|
|
4. Check authentication on sensitive endpoints
|
|
5. Scan for SQL injection vulnerabilities
|
|
6. Verify HTTPS enforcement
|
|
7. Check for exposed sensitive data in logs
|
|
8. Validate input sanitization
|
|
|
|
## Background: Sept 2026 Audit
|
|
|
|
**Critical findings addressed:**
|
|
1. Unauthenticated inventory updates (FIXED)
|
|
2. Deployment credential exposure in registry (MITIGATED)
|
|
3. No certificate validation in agent (PLANNED)
|
|
4. Hardcoded admin credentials (REMOVED)
|
|
|
|
**This skill prevents regressions** - ensures fixes stay fixed and new code doesn't reintroduce vulnerabilities.
|
|
|
|
## Workflow
|
|
|
|
### 1. Scan for Hardcoded Credentials
|
|
|
|
Search for credential patterns in code:
|
|
|
|
```bash
|
|
# Passwords
|
|
grep -rn "password.*=.*['\"]" --include="*.py" --include="*.js" --include="*.vue" \
|
|
shopdb/ plugins/ frontend/src/ \
|
|
--exclude-dir=node_modules --exclude-dir=dist \
|
|
| grep -v "password.*input\|password.*field\|password.*type\|password.*param"
|
|
|
|
# API keys
|
|
grep -rn "api_key.*=.*['\"]" --include="*.py" shopdb/ plugins/ \
|
|
| grep -v "api_key.*None\|api_key.*config\|api_key.*os.environ"
|
|
|
|
# Tokens
|
|
grep -rn "token.*=.*['\"][a-zA-Z0-9]{20,}" --include="*.py" shopdb/ plugins/
|
|
|
|
# Database credentials
|
|
grep -rn "mysql://.*:.*@" --include="*.py" --include="*.yml" .
|
|
```
|
|
|
|
**Acceptable patterns:**
|
|
- `password = None` (default/placeholder)
|
|
- `password = os.environ.get(...)` (from environment)
|
|
- `password = config.get(...)` (from config)
|
|
- `password_field = "..."` (field name, not value)
|
|
|
|
**Flag as violations:**
|
|
- `password = "admin123"` (hardcoded)
|
|
- `api_key = "sk_live_..."` (real key)
|
|
- `token = "eyJhbGciOi..."` (JWT token)
|
|
- `mysql://root:password@localhost` (database URL with creds)
|
|
|
|
### 2. Check for Exposed Collector Keys
|
|
|
|
Verify collector API keys not committed:
|
|
|
|
```bash
|
|
# Check for shopdb_pat_ prefix (our collector keys)
|
|
grep -rn "shopdb_pat_" --include="*.py" --include="*.js" --include="*.md" . \
|
|
--exclude-dir=.git
|
|
|
|
# Check .env files not committed
|
|
git ls-files | grep -E "\.env$|\.env\."
|
|
|
|
# Check for common secret patterns
|
|
grep -rn -E "(SECRET_KEY|COLLECTOR_KEY|API_KEY|PRIVATE_KEY).*=.*['\"][a-zA-Z0-9]{32,}" \
|
|
--include="*.py" shopdb/ plugins/
|
|
```
|
|
|
|
**Acceptable:**
|
|
- `.env.example` with placeholder values
|
|
- Comments referencing environment variables
|
|
- Memory files with redacted keys
|
|
|
|
**Violations:**
|
|
- `.env` file committed
|
|
- Real collector key in code
|
|
- API key in configuration file
|
|
|
|
### 3. Verify Certificate Validation
|
|
|
|
Check that agent validates HTTPS certificates:
|
|
|
|
```bash
|
|
# C# agent code
|
|
grep -n "ServerCertificateValidationCallback" agent/ShopDbEndpointAgent/
|
|
|
|
# Should NOT find:
|
|
# ServicePointManager.ServerCertificateValidationCallback = (sender, cert, chain, sslPolicyErrors) => true;
|
|
# This disables all certificate validation!
|
|
|
|
# Python collector code
|
|
grep -n "verify=False" shopdb/ plugins/ --include="*.py"
|
|
grep -n "ssl._create_unverified_context" shopdb/ plugins/ --include="*.py"
|
|
```
|
|
|
|
**Acceptable:**
|
|
- Development-only code with explicit comment
|
|
- Test code with mock certificates
|
|
|
|
**Violations:**
|
|
- Production code with `verify=False`
|
|
- Agent accepts any certificate without validation
|
|
- No certificate pinning for critical endpoints
|
|
|
|
### 4. Check Authentication on Sensitive Endpoints
|
|
|
|
Verify authentication required for sensitive operations:
|
|
|
|
```bash
|
|
# Find Flask routes
|
|
grep -rn "@.*route\|@bp.route" shopdb/ plugins/ --include="*.py" -A 3
|
|
|
|
# For each route, check if it has authentication:
|
|
# - @requires_auth decorator
|
|
# - @login_required decorator
|
|
# - Manual current_user check
|
|
```
|
|
|
|
**Sensitive operations that MUST have auth:**
|
|
- `/api/setup/*` (after initial setup)
|
|
- `/api/collectors/*` (collector authentication)
|
|
- `/api/admin/*` (admin only)
|
|
- `/api/*/create` (asset creation)
|
|
- `/api/*/update` (data modification)
|
|
- `/api/*/delete` (data deletion)
|
|
|
|
**Flag missing authentication:**
|
|
```python
|
|
@bp.route('/api/endpoints/update', methods=['POST'])
|
|
def update_endpoint():
|
|
# NO authentication check!
|
|
data = request.json
|
|
# ... update logic
|
|
```
|
|
|
|
**Correct patterns:**
|
|
```python
|
|
@bp.route('/api/endpoints/update', methods=['POST'])
|
|
@requires_auth # [X] Has authentication
|
|
def update_endpoint():
|
|
...
|
|
|
|
@bp.route('/api/collectors/heartbeat', methods=['POST'])
|
|
@requires_device_token # [X] Collector authentication
|
|
def collector_heartbeat():
|
|
...
|
|
```
|
|
|
|
### 5. Scan for SQL Injection Vulnerabilities
|
|
|
|
Check for raw SQL without parameterization:
|
|
|
|
```bash
|
|
# Find string concatenation in SQL
|
|
grep -rn "execute.*+\|execute.*format\|execute.*%" \
|
|
--include="*.py" shopdb/ plugins/ \
|
|
| grep -v "# safe:\|# parameterized"
|
|
|
|
# Find f-strings in SQL
|
|
grep -rn 'execute.*f".*{' --include="*.py" shopdb/ plugins/
|
|
|
|
# Find .format() in SQL
|
|
grep -rn "execute.*\.format\(" --include="*.py" shopdb/ plugins/
|
|
```
|
|
|
|
**Vulnerable patterns:**
|
|
```python
|
|
# BAD: String concatenation
|
|
query = "SELECT * FROM assets WHERE name = '" + user_input + "'"
|
|
db.session.execute(query)
|
|
|
|
# BAD: f-string
|
|
query = f"SELECT * FROM users WHERE username = '{username}'"
|
|
db.session.execute(query)
|
|
|
|
# BAD: .format()
|
|
query = "DELETE FROM assets WHERE id = {}".format(asset_id)
|
|
```
|
|
|
|
**Safe patterns:**
|
|
```python
|
|
# GOOD: Parameterized query
|
|
query = "SELECT * FROM assets WHERE name = :name"
|
|
db.session.execute(query, {"name": user_input})
|
|
|
|
# GOOD: SQLAlchemy ORM
|
|
Asset.query.filter_by(name=user_input).first()
|
|
|
|
# GOOD: SQLAlchemy Core
|
|
db.session.execute(select(Asset).where(Asset.name == user_input))
|
|
```
|
|
|
|
### 6. Verify HTTPS Enforcement
|
|
|
|
Check that HTTP is not allowed in production:
|
|
|
|
```bash
|
|
# Flask configuration
|
|
grep -rn "PREFERRED_URL_SCHEME" shopdb/config.py
|
|
|
|
# Should be: PREFERRED_URL_SCHEME = 'https'
|
|
|
|
# Check for HTTP URLs in code
|
|
grep -rn "http://.*5001\|http://.*api" --include="*.py" --include="*.js" . \
|
|
--exclude-dir=node_modules --exclude-dir=dist
|
|
|
|
# Check Vite proxy config
|
|
grep -n "target.*http:" frontend/vite.config.js
|
|
```
|
|
|
|
**Acceptable:**
|
|
- Development Vite server (HTTP on 5173)
|
|
- HTTP in proxy target (SSL termination)
|
|
|
|
**Violations:**
|
|
- Flask serving HTTP in production
|
|
- API calls without HTTPS
|
|
- Cookies without Secure flag
|
|
|
|
### 7. Check for Sensitive Data in Logs
|
|
|
|
Scan for patterns that expose sensitive data:
|
|
|
|
```bash
|
|
# Check for password logging
|
|
grep -rn "logging.*password\|print.*password\|log.info.*password" \
|
|
--include="*.py" shopdb/ plugins/ \
|
|
| grep -v "password.*\*\*\*\|password.*redacted"
|
|
|
|
# Check for API key logging
|
|
grep -rn "logging.*api_key\|print.*api_key" --include="*.py" shopdb/ plugins/
|
|
|
|
# Check for full request logging (may contain sensitive headers)
|
|
grep -rn "log.*request.headers\|print.*request.headers" \
|
|
--include="*.py" shopdb/ plugins/
|
|
```
|
|
|
|
**Violations:**
|
|
```python
|
|
# BAD: Password in logs
|
|
logging.info(f"Login attempt: {username} / {password}")
|
|
|
|
# BAD: API key exposed
|
|
print(f"Using API key: {api_key}")
|
|
|
|
# BAD: Full headers (may contain auth tokens)
|
|
logger.debug(f"Request headers: {request.headers}")
|
|
```
|
|
|
|
**Safe patterns:**
|
|
```python
|
|
# GOOD: Redacted password
|
|
logging.info(f"Login attempt: {username} / ***")
|
|
|
|
# GOOD: Partial key
|
|
logger.debug(f"API key: {api_key[:8]}...")
|
|
|
|
# GOOD: Specific header
|
|
logger.debug(f"Content-Type: {request.headers.get('Content-Type')}")
|
|
```
|
|
|
|
### 8. Validate Input Sanitization
|
|
|
|
Check that user input is validated:
|
|
|
|
```bash
|
|
# Find request.json usage without validation
|
|
grep -rn "request.json\|request.form\|request.args" \
|
|
--include="*.py" shopdb/ plugins/ -A 5 \
|
|
| grep -v "validator\|schema\|validate\|is_valid"
|
|
```
|
|
|
|
**Look for:**
|
|
- Direct use of `request.json['field']` without validation
|
|
- No length limits on string inputs
|
|
- No type checking on numeric inputs
|
|
- No sanitization of file paths
|
|
|
|
**Vulnerable:**
|
|
```python
|
|
# BAD: No validation
|
|
@bp.route('/api/assets', methods=['POST'])
|
|
def create_asset():
|
|
data = request.json # No validation!
|
|
asset = Asset(**data) # Directly creates from user input
|
|
db.session.add(asset)
|
|
```
|
|
|
|
**Safe:**
|
|
```python
|
|
# GOOD: Schema validation
|
|
from marshmallow import Schema, fields, validate
|
|
|
|
class AssetSchema(Schema):
|
|
name = fields.Str(required=True, validate=validate.Length(max=255))
|
|
asset_type = fields.Str(required=True, validate=validate.OneOf(['computer', 'printer']))
|
|
|
|
@bp.route('/api/assets', methods=['POST'])
|
|
def create_asset():
|
|
schema = AssetSchema()
|
|
data = schema.load(request.json) # Validated!
|
|
asset = Asset(**data)
|
|
```
|
|
|
|
### 9. Summary Report
|
|
|
|
**[X] No vulnerabilities found:**
|
|
```
|
|
Security scan complete: PASS
|
|
|
|
Credential Scan:
|
|
[X] No hardcoded passwords found
|
|
[X] No API keys in code
|
|
[X] No tokens exposed
|
|
[X] Collector keys not committed
|
|
|
|
Certificate Validation:
|
|
[X] Agent validates certificates
|
|
[X] No verify=False in production code
|
|
|
|
Authentication:
|
|
[X] All sensitive endpoints protected
|
|
[X] Collector endpoints use device tokens
|
|
[X] Admin endpoints require authentication
|
|
|
|
SQL Injection:
|
|
[X] All queries parameterized
|
|
[X] No string concatenation in SQL
|
|
[X] ORM used consistently
|
|
|
|
HTTPS Enforcement:
|
|
[X] Production uses HTTPS
|
|
[X] Cookies have Secure flag
|
|
[X] No HTTP URLs in production config
|
|
|
|
Logging:
|
|
[X] No passwords in logs
|
|
[X] API keys redacted
|
|
[X] Sensitive data masked
|
|
|
|
Input Validation:
|
|
[X] Schema validation on all POST/PUT endpoints
|
|
[X] Length limits enforced
|
|
[X] Type checking present
|
|
|
|
No security issues detected!
|
|
```
|
|
|
|
**WARNING: Vulnerabilities found:**
|
|
```
|
|
Security scan: FAILED
|
|
|
|
CRITICAL (block deployment):
|
|
1. Hardcoded admin password in tests/fixtures/users.py
|
|
Line 15: password = "admin123"
|
|
Impact: Test credential could be used in production
|
|
Action: Use environment variable or generate random password
|
|
|
|
2. Collector API key committed in .env file
|
|
File: .env (should be .gitignore'd)
|
|
Impact: API key exposed in version control
|
|
Action: Remove .env, regenerate key, add to .gitignore
|
|
|
|
3. Certificate validation disabled in agent
|
|
File: agent/ShopDbEndpointAgent/ApiClient.cs:42
|
|
Code: ServerCertificateValidationCallback = (_, _, _, _) => true
|
|
Impact: Agent accepts any certificate (MITM attack possible)
|
|
Action: Implement proper certificate validation
|
|
|
|
HIGH PRIORITY:
|
|
4. Missing authentication on /api/endpoints/update
|
|
File: plugins/endpoints/api/routes.py:87
|
|
Impact: Anyone can update endpoint data
|
|
Action: Add @requires_device_token decorator
|
|
|
|
5. SQL injection vulnerability in asset search
|
|
File: shopdb/core/api/assets.py:45
|
|
Code: f"SELECT * FROM assets WHERE name LIKE '%{search}%'"
|
|
Impact: SQL injection attack possible
|
|
Action: Use parameterized query or ORM
|
|
|
|
MEDIUM:
|
|
6. Password logged in authentication.py:23
|
|
Code: logger.info(f"Login: {username}/{password}")
|
|
Impact: Passwords exposed in log files
|
|
Action: Redact password: logger.info(f"Login: {username}/***")
|
|
|
|
DO NOT DEPLOY until critical and high priority issues are fixed!
|
|
|
|
Remediation guide: [[security-fix-implementation]]
|
|
```
|
|
|
|
## Automated Remediation
|
|
|
|
Some issues can be auto-fixed:
|
|
|
|
**Remove hardcoded credentials:**
|
|
```bash
|
|
# Replace hardcoded passwords with environment variables
|
|
sed -i 's/password = ".*"/password = os.environ.get("TEST_PASSWORD", "changeme")/g' tests/fixtures/*.py
|
|
```
|
|
|
|
**Add .env to .gitignore:**
|
|
```bash
|
|
echo ".env" >> .gitignore
|
|
git rm --cached .env
|
|
```
|
|
|
|
**Add authentication decorator:**
|
|
```python
|
|
# Before:
|
|
@bp.route('/api/endpoints/update', methods=['POST'])
|
|
def update_endpoint():
|
|
...
|
|
|
|
# After:
|
|
@bp.route('/api/endpoints/update', methods=['POST'])
|
|
@requires_device_token
|
|
def update_endpoint():
|
|
...
|
|
```
|
|
|
|
## Related
|
|
|
|
- [[security-audit-2026-09-11]] - Original audit findings
|
|
- [[security-fix-implementation]] - Detailed remediation plans
|
|
- `/pre-commit` - Include basic security checks in pre-commit
|
|
- `/production-health` - Runtime security monitoring
|
|
- ADR-016: Credential delivery (device tokens)
|