This commit adds: - Database migrations for app_delta fingerprints and client certificates - Scripts for certificate generation and fingerprint backfilling - Comprehensive security audit reports and findings tracking - Implementation documentation for certificate authentication - Test utilities for certificate renewal and fingerprinting - GE-ENFORCE collector fix documentation Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
19 KiB
ShopDB Security Audit Report
Date: September 12, 2026
Auditor: Cloudflare Project Glasswing (evilsocket/audit)
Target: ShopDB Flask Application v1.x
Environment: http://192.168.100.1:5173 (local dev instance)
Test Account: claude (technician role, 26 permissions)
Audit Duration: 3 hours 13 minutes
Total Cost: $60.42
Executive Summary
A comprehensive 8-stage vulnerability discovery audit was conducted against the ShopDB manufacturing asset management system using the Glasswing methodology (Recon, Hunt, Validate, Gapfill, Dedupe, Trace, Feedback, Report). The audit discovered 7 confirmed exploitable vulnerabilities across authentication, authorization, cryptography, and resource management domains.
Key Findings:
- 3 HIGH severity vulnerabilities
- 3 MEDIUM severity vulnerabilities
- 1 LOW severity vulnerability
- All 7 vulnerabilities confirmed through adversarial validation
- All 7 vulnerabilities proven reachable from attacker-controlled input
Confirmation Rate: 19% (7 confirmed of 36 potential findings, 29 false positives rejected through adversarial validation)
Critical Risks:
- Malicious binary deployment as SYSTEM across Windows endpoint fleet
- Username enumeration enabling targeted credential attacks
- API key extraction via timing side-channel
- Token theft enabling 30-day persistent access
Methodology
Audit Framework: Cloudflare Project Glasswing
Model: AWS Bedrock Gov - Claude Sonnet 4.5 (us-gov.anthropic.claude-sonnet-4-5-20250929-v1:0)
Stages Completed:
- Recon - Mapped 10 subsystems, 11 entry points
- Hunt Round 1 - 15 tasks, 14 findings
- Validate Round 1 - 3 confirmed, 11 rejected
- Gapfill 1 - Identified coverage gaps, added 8 tasks
- Hunt Round 2 - 8 tasks, 11 findings
- Validate Round 2 - 3 confirmed, 8 rejected
- Gapfill 2 - Added 8 tasks
- Hunt Round 3 - 8 tasks, 11 findings
- Validate Round 3 - 1 confirmed, 10 rejected
- Dedupe - 7 findings grouped into 7 unique vulnerabilities
- Trace - All 7 confirmed reachable
- Feedback - Generated 39 additional hunt tasks
- Hunt Round 4 - 39 tasks, 40 findings (unvalidated, budget exhausted)
Note: Hunt Round 4 findings (40 potential vulnerabilities) were not validated due to budget constraints. Historical false positive rate suggests ~32 of these 40 are likely invalid.
Confirmed Vulnerabilities
1. Agent Binary Injection (HIGH)
ID: f_agent_binj_1
CWE: CWE-345 (Insufficient Verification of Data Authenticity)
File: plugins/endpoints/api/agentupload.py:127-131
CVSS Estimate: 8.1 (High)
Description:
The agent binary upload endpoint accepts ANY Windows PE executable without code signing or publisher verification. A compromised operator with endpoints.agentpackage + endpoints.agentupdate permissions can upload and deploy malicious binaries that execute as SYSTEM across the entire Windows endpoint fleet.
Attack Chain:
- POST /api/endpoints/siteagent/binaries/agent with malicious PE (only checks for "MZ" header)
- Binary staged to instance/siteagent/agent//shopdbagent.exe
- Packaged as Application object in database
- POST /api/endpoints/<asset_id>/agentupgrade to push to endpoints
- PowerShell script executes with Unblock-File (bypasses SmartScreen) and runs as SYSTEM
Vulnerable Code:
# Only validation performed:
def looks_like_pe(data):
"""Whether data begins with the Windows PE 'MZ' magic."""
return len(data) >= 2 and data[:2] == _PE_MAGIC
Preconditions:
- Attacker must hold or compromise an account with endpoints.agentpackage permission
- Attacker must hold or compromise an account with endpoints.agentupdate permission
- Target environment lacks additional controls (EDR, application whitelisting)
Impact:
- Complete fleet compromise
- SYSTEM-level code execution on all Windows endpoints
- Persistence via Windows service replacement
- Active bypass of OS-level protections (SmartScreen)
Remediation:
- Immediate: Implement Authenticode signature verification
- Short-term: Maintain SHA256 whitelist of approved binaries
- Long-term: Implement two-person rule (separate upload/deploy permissions)
- Defense-in-depth: Remove Unblock-File from upgrade script
References:
- DESIGN-DECISIONS-agent.md explicitly scopes out code signing (acknowledged design decision)
- plugin.py:232-249 documents permissions as delegatable to non-admin operators
- upgrade-shopdbagent.ps1:204-239 shows Unblock-File bypassing security
2. Account Enumeration via Lockout Disclosure (HIGH)
ID: f_t_auth_acct_enum_1_1
CWE: CWE-204 (Observable Response Discrepancy)
File: shopdb/core/api/auth.py:102-107
CVSS Estimate: 7.5 (High)
Description: The login endpoint leaks valid usernames through observable HTTP status code and message differences. Locked accounts return HTTP 403 "Account is locked", while invalid usernames return HTTP 401 "Invalid username or password". An attacker can deterministically enumerate valid usernames by triggering lockouts and observing responses.
Attack Vector:
- Make 5-6 failed login attempts with candidate username
- Observe response: 401 -> 403 transition indicates valid username
- Per-IP rate limiting (30 req/5min) allows ~5 username tests per window per IP
Vulnerable Code:
if user and user.islocked:
return error_response(
ErrorCodes.FORBIDDEN,
'Account is locked. Try again later or contact an administrator.',
http_code=403) # Leaks username validity
Proof of Concept:
# Trigger lockout for 'claude' user
for i in {1..6}; do
curl -X POST http://192.168.100.1:5173/api/auth/login \
-d '{"username":"claude","password":"wrong"}'
done
# Returns HTTP 403 after 5 attempts
# Test invalid username
curl -X POST http://192.168.100.1:5173/api/auth/login \
-d '{"username":"nonexistent","password":"test"}'
# Returns HTTP 401
Impact:
- Username enumeration enables targeted password attacks
- Reduces credential brute-force search space
- Undermines username anonymity security assumption
Remediation:
- Always return HTTP 401 "Invalid username or password" regardless of account state
- Enforce lockout post-password-check to eliminate timing differences
- Implement constant-time response strategy
Validation: Successfully reproduced against live target at http://192.168.100.1:5173
3. Collector API Key Timing Attack (HIGH)
ID: f_jwt_timing_1
CWE: CWE-208 (Observable Timing Discrepancy)
File: shopdb/core/api/collector.py:135-137
CVSS Estimate: 7.3 (High)
Description:
Collector API key authentication uses Python's == operator for string comparison instead of constant-time comparison (hmac.compare_digest). This allows byte-by-byte extraction of COLLECTOR_API_KEY via timing side-channel attacks. Other parts of the codebase correctly use hmac.compare_digest, indicating developer awareness but inconsistent application.
Vulnerable Code:
# Line 135 - Non-constant-time comparison
if expected_key and api_key == expected_key \
and not _env_fallback_disabled(pluginname):
return None
Correct Usage Elsewhere:
# setup.py:65, usb plugin, endpoints plugin all use:
if hmac.compare_digest(expected, provided):
# Constant-time comparison
Attack Characteristics:
- No rate limiting on collector endpoints (unlike auth endpoints)
- Short-circuit comparison leaks prefix match length
- Network latency makes remote extraction difficult but local/LAN feasible
- Env key fallback is documented as "stay supported", not deprecated
Preconditions:
- Low-latency network path (same subnet, datacenter, or compromised host)
- Env key fallback not disabled via plugin..envkeyfallback setting
- COLLECTOR_API_KEY configured in environment
Impact:
- Full API key extraction enables unauthenticated access to collector endpoints
- Create/update assets without authentication
- Inject false telemetry data
Remediation:
import hmac
# Replace line 135 with:
if expected_key and hmac.compare_digest(api_key, expected_key) \
and not _env_fallback_disabled(pluginname):
return None
Validation: Timing differences observed (26-36ms range over network). Code inspection confirms non-constant-time comparison.
4. Refresh Token Non-Rotation (MEDIUM)
ID: f_jwt_1_1
CWE: CWE-613 (Insufficient Session Expiration)
File: shopdb/core/api/auth.py:160-186
CVSS Estimate: 6.5 (Medium)
Description: The /refresh endpoint does not rotate refresh tokens when issuing new access tokens. A compromised refresh token remains valid for 30 days (JWT_REFRESH_TOKEN_EXPIRES=2592000) and can be reused unlimited times. Combined with the lack of token blacklist (acknowledged at line 257), stolen refresh tokens cannot be revoked.
Vulnerable Code:
@auth_bp.route('/refresh', methods=['POST'])
@jwt_required(refresh=True)
def refresh():
# Creates new access token but does NOT rotate refresh token
access_token = create_access_token(
identity=str(user.userid),
additional_claims={...}
)
return success_response({
'access_token': access_token,
# Refresh token NOT included - client reuses old one
})
Developer Acknowledgment (line 270-272):
# "There is no token denylist... the refresh token keeps minting new ones for 30 days."
Preconditions:
- Attacker must obtain valid refresh token (XSS, insider threat, physical access)
- User account must remain active (isactive=True)
Impact:
- Stolen refresh token provides 30-day persistent access
- No revocation mechanism (only full account deactivation)
- Unlimited token generation enables log pollution
Remediation:
- Implement refresh token rotation (issue new refresh token with each refresh)
- Implement JTI-based token blacklist
- Add rate limiting to /refresh endpoint (currently has none)
- Reduce refresh token lifetime to 7 days
Alternative: Accept risk in air-gapped environment where token theft vectors are limited to insider threats.
5. Token Refresh Rate Limit Bypass (MEDIUM)
ID: f_t_gf_rate_limit_bypass_upload_1_1
CWE: CWE-770 (Allocation of Resources Without Limits)
File: shopdb/core/api/auth.py:160-186
CVSS Estimate: 5.3 (Medium)
Description: The /api/auth/refresh endpoint lacks rate limiting. In contrast, /api/auth/login implements fixed-window rate limiting (30 req/5min). An attacker with a stolen refresh token can spam token generation at unlimited rate.
Comparison:
# Login endpoint - HAS rate limiting
@auth_bp.route('/login', methods=['POST'])
@_login_ratelimited() # 30 requests per 5-minute window
def login():
...
# Refresh endpoint - NO rate limiting
@auth_bp.route('/refresh', methods=['POST'])
@jwt_required(refresh=True) # No rate limiter decorator
def refresh():
...
Proof of Concept: Successfully executed 100 token refresh requests in 3.8 seconds (26.3 req/s) without triggering rate limiting.
Impact:
- DoS via database query spam (each refresh queries User table)
- Log pollution hinders forensic analysis
- Enables rapid token generation for distributed attacks
Remediation:
# Add rate limiting decorator
@auth_bp.route('/refresh', methods=['POST'])
@limiter.limit("10 per minute") # or reuse _login_ratelimited pattern
@jwt_required(refresh=True)
def refresh():
...
Validation: Static code analysis confirms absence of rate limiting. Live test confirmed unlimited refresh capability.
6. File Upload DoS (MEDIUM)
ID: f_gf_upload_dos_1_3
CWE: CWE-307 (Improper Restriction of Excessive Authentication Attempts)
File: shopdb/config.py:145-151
CVSS Estimate: 5.0 (Medium)
Description: File upload endpoints lack rate limiting. The application implements IP-based rate limiting only for the login endpoint, but no rate limiting exists for file upload routes. An authenticated user with upload permissions can repeatedly upload large files without throttling.
Affected Endpoints:
- /api/endpoints/siteagent/binaries/
- /api/applications/<app_id>/package
- (Others identified in Hunt Round 4)
Current Protection:
- MAX_CONTENT_LENGTH = 256MB per-request size cap
- No rate limiting across requests
- No cumulative upload quotas
Preconditions:
- Attacker must hold endpoints.agentpackage or applications.edit permission
- Air-gapped environment limits attack surface to insiders
Impact:
- Disk exhaustion (repeated 128-256MB uploads)
- Memory exhaustion (file processing)
- Amplifies other upload vulnerabilities
Remediation:
from flask_limiter import Limiter
# Apply to upload blueprints
@upload_bp.before_request
@limiter.limit("10 per hour")
def rate_limit_uploads():
pass
Alternative: Accept risk if upload permissions are tightly controlled (2-3 admin-only accounts).
7. Race Condition in Event Deduplication (LOW)
ID: f_gf_collector_rc_3_event_dedup
CWE: CWE-362 (Concurrent Execution using Shared Resource)
File: plugins/endpoints/plugin.py:1259-1278
CVSS Estimate: 3.7 (Low)
Description: Event deduplication check-then-insert (TOCTOU) race allows duplicate EndpointEvent records. Concurrent collector POSTs carrying the same Windows event can both bypass the deduplication check under REPEATABLE READ isolation. The unique index may raise IntegrityError, aborting the entire transaction and losing ALL telemetry in the batch.
Vulnerable Code Flow:
# Line 1260 - Query existing events
rows = db.session.query(EndpointEvent.eventchannel, EndpointEvent.eventrecordid)
.filter(EndpointEvent.assetid == assetid, ...)
.all()
already_stored = {(channel, recordid) for channel, recordid in rows}
# Line 1279 - Insert new events (TOCTOU window)
for eventid, eventrecordid, record in candidates:
if key in already_stored: # Check against stale set
continue
# Insert may conflict with concurrent transaction
Developer Intent (lines 1229-1231):
# "Would abort the whole collector transaction and lose the state upsert"
# The dedup exists to PREVENT this outcome, but race undermines it
Preconditions:
- Concurrent POSTs for same asset with same (eventchannel, eventrecordid)
- Both transactions query before either commits (millisecond window)
- No application-level request deduplication upstream
Impact:
- Transaction rollback loses state updates AND other events in batch
- Agent retry logic creates realistic concurrent POST scenario
- Defense-in-depth mechanism becomes failure mode under load
Remediation:
# Option 1: Row-level locking
rows = db.session.query(EndpointEvent.eventchannel, EndpointEvent.eventrecordid)
.filter(...)
.with_for_update() # Acquire row locks
.all()
# Option 2: INSERT...ON CONFLICT (PostgreSQL)
INSERT INTO endpointevent (...) VALUES (...)
ON CONFLICT (assetid, eventchannel, eventrecordid) DO NOTHING;
Validation: Code inspection confirms TOCTOU race. No row-level locking. Catch-all exception handler at collector.py:272-278 rolls back on IntegrityError.
Additional Findings (Unvalidated)
Hunt Round 4 discovered 40 additional potential vulnerabilities that were NOT adversarially validated due to budget constraints. Based on historical false positive rates (80% rejection in prior rounds), approximately 8 of these 40 may be real vulnerabilities.
Categories:
- File upload validation issues: 11 findings
- Rate limiting gaps: 6 findings
- Race conditions: 3 findings
- Info disclosure: 1 finding
- Token management: 1 finding
- Timing attacks: 1 finding
- Auth issues: 1 finding
- Zero findings: 16 tasks
Recommendation: Manual review or additional budget allocation for validation.
Risk Assessment
By Severity
| Severity | Count | Risk Level |
|---|---|---|
| HIGH | 3 | Critical attention required |
| MEDIUM | 3 | Important but lower priority |
| LOW | 1 | Informational |
By CWE Category
| Category | Vulnerabilities |
|---|---|
| Authentication/Authorization | #2, #4, #5 |
| Cryptography | #3 |
| Resource Management | #5, #6 |
| Code Quality | #1, #7 |
Exploitability
- All 7 vulnerabilities: Confirmed reachable from attacker-controlled input
- 3 vulnerabilities: Live exploits demonstrated (#2, #5, #3 timing observed)
- 4 vulnerabilities: Confirmed via static code analysis (#1, #4, #6, #7)
Remediation Priority
Immediate (Week 1)
- Vuln #1: Implement Authenticode signature verification for agent binaries
- Vuln #2: Fix account enumeration (constant-time responses)
- Vuln #3: Replace
==withhmac.compare_digest()in collector auth
Short-term (Week 2-4)
- Vuln #5: Add rate limiting to /refresh endpoint
- Vuln #4: Implement refresh token rotation
- Vuln #6: Add rate limiting to upload endpoints
Long-term (Month 2+)
- Vuln #7: Implement SELECT...FOR UPDATE or ON CONFLICT for event dedup
- Review: Hunt Round 4 unvalidated findings (40 candidates)
Testing Recommendations
Regression Testing
After remediation, verify fixes with:
- Re-run security audit with updated code
- Unit tests for constant-time comparison functions
- Integration tests for token rotation flow
- Load tests for race condition scenarios
Continuous Security
- Add pre-commit hooks for timing-attack patterns (grep for
== expected_key) - Static analysis for non-constant-time comparisons
- Periodic security audits (quarterly recommended)
- Penetration testing by third party
Appendix: Audit Configuration
Target Application: ShopDB Flask v1.x
Test URL: http://192.168.100.1:5173
Test Credentials: claude / [REDACTED]
Test Account Role: Technician (26 permissions, non-admin)
Audit Tool: evilsocket/audit v1.x
Model: AWS Bedrock Gov - Claude Sonnet 4.5
Model ID: us-gov.anthropic.claude-sonnet-4-5-20250929-v1:0
Concurrency: 2 agents per stage
Budget: $40 (exceeded to $60.42)
Scope:
- Focus: Critical/High severity findings only
- Exclusions: Test-only endpoints, intentional API token storage, JWT in localStorage (has XSS mitigations)
- Environment: Air-gapped production deployment model
Methodology Tradeoffs:
- Lost "deliberate disagreement" benefit (Hunt and Validate used same model due to Opus unavailability in Bedrock Gov)
- Unicode encoding issues caused 12 initial task failures (resolved with PYTHONIOENCODING=utf-8)
- Conservative rate limits in Bedrock Gov added 5-10 minutes runtime overhead
Glossary
CWE: Common Weakness Enumeration - standardized vulnerability classification
CVSS: Common Vulnerability Scoring System - severity rating (0-10 scale)
TOCTOU: Time-of-Check Time-of-Use - race condition vulnerability class
Adversarial Validation: Second AI agent attempts to disprove findings (confirms real vs false positive)
Glasswing: Cloudflare's 8-stage vulnerability discovery pipeline
Reachability: Proof that attacker-controlled input can reach vulnerable code path
Report Generated: 2026-09-12 15:35 EDT
Audit Completion: 2026-09-12 15:34 EDT
Report Author: Claude Sonnet 4.5 (audit tool + reporting agent)