diff --git a/.github/GE-ENFORCE-COLLECTOR-FIX.html b/.github/GE-ENFORCE-COLLECTOR-FIX.html new file mode 100644 index 00000000..66568572 --- /dev/null +++ b/.github/GE-ENFORCE-COLLECTOR-FIX.html @@ -0,0 +1,748 @@ + + +
+ + +Issue Date: Identified September 14, 2026
+Last Check-in: August 28, 2026 at 9:55 PM
+Affected Systems: All GE-Enforce client machines
+ +On August 28, 2026, a major code deployment introduced a new collector authentication system + that supports per-plugin managed tokens. As part of this change:
+ +.env API key fallback via database settingCode Change: Commit 80bb7b5ba1 - "Merge wilm: endpoints plugin, agent, policies"
Affected File: shopdb/core/api/collector.py
if api_key == expected_key:
+ return None # Authorized
+ if expected_key and api_key == expected_key \
+ and not _env_fallback_disabled(pluginname): # NEW CHECK
+ return None # Authorized
+ -- Check if .env fallback is disabled
+SELECT * FROM settings
+WHERE key = 'plugin.geenforce.envkeyfallback';
+
+-- Expected result if disabled: value = 'false'
+-- Expected result if enabled: no rows or value = 'true'
+
+-- Check last check-in
+SELECT MAX(receivedat) as last_checkin,
+ COUNT(DISTINCT hostname) as unique_hosts
+FROM manifestenforcementreports
+WHERE iscurrent = 1;
+
+-- Check managed tokens
+SELECT * FROM apitokens
+WHERE scope LIKE '%geenforce%'
+ORDER BY createdat DESC;
+
+ Look for collector requests failing:
+POST /api/collector/geenforce
+Status: 401 Unauthorized
+Error: "Invalid API key" or "Token revoked"
+
+
+ -- Option A: Delete the setting (defaults to enabled)
+DELETE FROM settings
+WHERE key = 'plugin.geenforce.envkeyfallback';
+
+-- Option B: Explicitly set to true
+INSERT INTO settings (key, value, valuetype)
+VALUES ('plugin.geenforce.envkeyfallback', 'true', 'boolean')
+ON DUPLICATE KEY UPDATE value = 'true';
+ -- Wait 15-30 minutes, then check
+SELECT MAX(receivedat) as last_checkin
+FROM manifestenforcementreports
+WHERE receivedat > NOW() - INTERVAL 1 HOUR;
+ .env file has a valid COLLECTOR_API_KEY
+ and all GE-Enforce clients are configured with that key.
+ Deploy the API key to all GE-Enforce client machines via Intune.
+ +| Registry Path | +Value Name | +Value Type | +Purpose | +
|---|---|---|---|
HKLM:\SOFTWARE\GE\ShopDB |
+ BaseUrl |
+ REG_SZ | +ShopDB server URL | +
HKLM:\SOFTWARE\GE\ShopDB |
+ ApiToken |
+ REG_SZ | +Collector API key | +
Deployment Path: Intune Portal > Devices > Scripts > Add Windows 10 script
+ +Save as Deploy-GEEnforceAPIKey.ps1:
<#
+.SYNOPSIS
+ Deploy GE-Enforce collector API key via Intune
+
+.DESCRIPTION
+ Writes the ShopDB API token to registry for GE-Enforce client.
+ Run as SYSTEM, no user context needed.
+
+.NOTES
+ Deploy via: Intune > Devices > Scripts > Add
+ Run as: SYSTEM
+ Platform: Windows 10/11
+#>
+
+$ErrorActionPreference = 'Stop'
+
+# ============================================
+# CONFIGURATION - UPDATE THESE VALUES
+# ============================================
+$ShopdbBaseUrl = "https://shopdb.your-company.com"
+$ShopdbApiToken = "shopdb_pat_YOUR_NEW_TOKEN_HERE" # Replace with actual token
+
+# ============================================
+# REGISTRY CONFIGURATION
+# ============================================
+$RegPath = 'HKLM:\SOFTWARE\GE\ShopDB'
+
+try {
+ Write-Output "Starting GE-Enforce API key deployment..."
+
+ # Create registry key if it doesn't exist
+ if (-not (Test-Path $RegPath)) {
+ New-Item -Path $RegPath -Force | Out-Null
+ Write-Output "Created registry path: $RegPath"
+ }
+
+ # Set BaseUrl
+ Set-ItemProperty -Path $RegPath -Name 'BaseUrl' -Value $ShopdbBaseUrl -Type String
+ Write-Output "Set BaseUrl: $ShopdbBaseUrl"
+
+ # Set ApiToken (REG_SZ - plain text, registry ACL protects it)
+ Set-ItemProperty -Path $RegPath -Name 'ApiToken' -Value $ShopdbApiToken -Type String
+
+ # Verify token was written
+ $savedToken = Get-ItemProperty -Path $RegPath -Name 'ApiToken' -ErrorAction Stop
+ if ($savedToken.ApiToken -eq $ShopdbApiToken) {
+ Write-Output "Set ApiToken: [REDACTED - first 20 chars: $($ShopdbApiToken.Substring(0, 20))...]"
+ Write-Output "SUCCESS: GE-Enforce API configuration deployed"
+ exit 0
+ } else {
+ throw "Token verification failed"
+ }
+
+} catch {
+ Write-Error "FAILED: $_"
+ Write-Output "Registry path: $RegPath"
+ Write-Output "Error details: $($_.Exception.Message)"
+ exit 1
+}
+
+ Deploy-GEEnforceAPIKey.ps1Deployment Path: Intune Portal > Devices > Configuration profiles > Create > Settings catalog
+ +Setting 1: BaseUrl
+HKLM\SOFTWARE\GE\ShopDBBaseUrlhttps://shopdb.your-company.comSetting 2: ApiToken
+HKLM\SOFTWARE\GE\ShopDBApiTokenshopdb_pat_YOUR_TOKEN_HEREFor Azure Automation DSC environments:
+ +Configuration GEEnforceCollector {
+ param(
+ [Parameter(Mandatory)]
+ [string]$BaseUrl,
+
+ [Parameter(Mandatory)]
+ [string]$ApiToken
+ )
+
+ Import-DscResource -ModuleName 'PSDesiredStateConfiguration'
+
+ Node 'localhost' {
+
+ Registry ShopDBBaseUrl {
+ Key = 'HKLM:\SOFTWARE\GE\ShopDB'
+ ValueName = 'BaseUrl'
+ ValueData = $BaseUrl
+ ValueType = 'String'
+ Ensure = 'Present'
+ }
+
+ Registry ShopDBApiToken {
+ Key = 'HKLM:\SOFTWARE\GE\ShopDB'
+ ValueName = 'ApiToken'
+ ValueData = $ApiToken
+ ValueType = 'String'
+ Ensure = 'Present'
+ DependsOn = '[Registry]ShopDBBaseUrl'
+ }
+
+ Script VerifyGEEnforce {
+ GetScript = { @{ Result = Test-Path 'HKLM:\SOFTWARE\GE\ShopDB' } }
+ TestScript = {
+ $token = Get-ItemProperty -Path 'HKLM:\SOFTWARE\GE\ShopDB' -Name 'ApiToken' -ErrorAction SilentlyContinue
+ return ($null -ne $token)
+ }
+ SetScript = { Write-Verbose "Registry already configured by dependencies" }
+ DependsOn = '[Registry]ShopDBApiToken'
+ }
+ }
+}
+
+# Compile configuration
+GEEnforceCollector -BaseUrl "https://shopdb.your-company.com" `
+ -ApiToken "shopdb_pat_YOUR_TOKEN_HERE" `
+ -OutputPath "C:\DSC\GEEnforce"
+
+# Upload to Azure Automation and assign to nodes
+
+
+ On your production IIS server, check .env file:
# Look for:
+COLLECTOR_API_KEY=shopdb_pat_abc123def456...
+
+# Or plugin-specific:
+COLLECTOR_API_KEY_GEENFORCE=shopdb_pat_xyz789...
+
+ Use this token in your Intune deployment script.
+ +# SSH/RDP to IIS server
+cd C:\path\to\shopdb-flask
+
+# Generate new token
+flask token create collector.geenforce "GE-Enforce Collector - Intune" `
+ --scope "collector.ingest.geenforce" `
+ --userid 1
+
+# Output will be:
+# Token created: shopdb_pat_abc123def456...
+# Copy this token for Intune deployment
+ SELECT tokenprefix, scope, description, isactive, createdat
+FROM apitokens
+WHERE scope LIKE '%geenforce%'
+ORDER BY createdat DESC
+LIMIT 1;
+ Test on a single pilot machine first:
+ +# Run as Administrator on a test machine
+Set-ItemProperty -Path 'HKLM:\SOFTWARE\GE\ShopDB' `
+ -Name 'BaseUrl' -Value 'https://shopdb.your-company.com'
+
+Set-ItemProperty -Path 'HKLM:\SOFTWARE\GE\ShopDB' `
+ -Name 'ApiToken' -Value 'shopdb_pat_YOUR_TOKEN_HERE'
+
+# Verify registry
+Get-ItemProperty -Path 'HKLM:\SOFTWARE\GE\ShopDB'
+
+# Run GE-Enforce manually to test
+& "C:\Program Files\GE-Enforce\Invoke-ShopdbEnforce.ps1"
+
+# Check for errors in output
+
+ -- Wait 15-30 minutes after deployment, then check database
+SELECT hostname, scopename, status, lastcheckin, receivedat
+FROM manifestenforcementreports
+WHERE receivedat > NOW() - INTERVAL 1 HOUR
+ORDER BY receivedat DESC;
+
+ Monitor deployment in Intune portal:
+-- Run this query on a schedule (every 6 hours)
+-- Alert if any results returned
+SELECT
+ 'geenforce' as collector,
+ MAX(receivedat) as last_checkin,
+ TIMESTAMPDIFF(HOUR, MAX(receivedat), NOW()) as hours_since,
+ COUNT(DISTINCT hostname) as total_hosts
+FROM manifestenforcementreports
+WHERE iscurrent = 1
+HAVING hours_since > 24;
+
+-- Expected: 0 rows (all clients checking in)
+-- Alert if: > 24 hours since last check-in
+
+ -- GE-Enforce fleet health dashboard
+SELECT
+ DATE(receivedat) as report_date,
+ COUNT(DISTINCT hostname) as unique_hosts,
+ COUNT(*) as total_reports,
+ SUM(CASE WHEN status = 'success' THEN 1 ELSE 0 END) as successful,
+ SUM(CASE WHEN status = 'failed' THEN 1 ELSE 0 END) as failed
+FROM manifestenforcementreports
+WHERE receivedat > NOW() - INTERVAL 7 DAY
+GROUP BY DATE(receivedat)
+ORDER BY report_date DESC;
+
+
+ DELETE FROM settings
+WHERE key = 'plugin.geenforce.envkeyfallback';
+
+ Re-deploy the original API key from .env file using same Intune script.
SELECT MAX(receivedat) as last_checkin
+FROM manifestenforcementreports
+WHERE receivedat > NOW() - INTERVAL 1 HOUR;
+
+ Check Intune device output logs for errors:
+| Step | +Action | +Owner | +Status | +
|---|---|---|---|
| 1 | +Verify problem in production database | +Admin | +☐ | +
| 2 | +Re-enable .env fallback (emergency fix) | +Admin | +☐ | +
| 3 | +Generate new API token (or get from .env) | +Admin | +☐ | +
| 4 | +Test manually on 1 pilot machine | +Engineer | +☐ | +
| 5 | +Create Intune PowerShell script | +Engineer | +☐ | +
| 6 | +Deploy to pilot group (5-10 machines) | +Admin | +☐ | +
| 7 | +Verify pilot check-ins in database | +Engineer | +☐ | +
| 8 | +Deploy to all GE-Enforce clients | +Admin | +☐ | +
| 9 | +Monitor check-ins for 24 hours | +Engineer | +☐ | +
| 10 | +Create monitoring alert | +Engineer | +☐ | +
+ Document Version: 1.0
+ Created: September 14, 2026
+ Repository: flask-shopdb/.github/
+ Related Changes: Commit 80bb7b5ba1 (August 28, 2026)
+
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.
+ +| Severity | +Count | +Risk Level | +
|---|---|---|
| HIGH | +3 | +Critical attention required | +
| MEDIUM | +3 | +Important but lower priority | +
| LOW | +1 | +Informational | +
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.
plugins/endpoints/api/agentupload.py:127-131
def looks_like_pe(data):
+ """Whether data begins with the Windows PE 'MZ' magic."""
+ return len(data) >= 2 and data[:2] == _PE_MAGIC
+ 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".
+shopdb/core/api/auth.py:102-107
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.
shopdb/core/api/collector.py:135-137
# Line 135 - Non-constant-time comparison
+if expected_key and api_key == expected_key \
+ and not _env_fallback_disabled(pluginname):
+ return None
+ # setup.py:65, usb plugin, endpoints plugin
+if hmac.compare_digest(expected, provided):
+ # Constant-time comparison
+ 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
+ The /refresh endpoint does not rotate refresh tokens when issuing new access tokens. A compromised refresh token remains valid for 30 days and can be reused unlimited times.
+shopdb/core/api/auth.py:160-186
The /api/auth/refresh endpoint lacks rate limiting. In contrast, /api/auth/login implements fixed-window rate limiting (30 req/5min). Successfully executed 100 token refresh requests in 3.8 seconds (26.3 req/s).
+shopdb/core/api/auth.py:160-186
# Add rate limiting decorator
+@auth_bp.route('/refresh', methods=['POST'])
+@limiter.limit("10 per minute")
+@jwt_required(refresh=True)
+def refresh():
+ ...
+ File upload endpoints lack rate limiting. An authenticated user with upload permissions can repeatedly upload large files (up to 256MB per request) without throttling.
+shopdb/config.py:145-151
from flask_limiter import Limiter
+
+@upload_bp.before_request
+@limiter.limit("10 per hour")
+def rate_limit_uploads():
+ pass
+ Event deduplication check-then-insert (TOCTOU) race allows duplicate EndpointEvent records. Concurrent collector POSTs can both bypass the deduplication check, causing IntegrityError that aborts the entire transaction and loses ALL telemetry in the batch.
+plugins/endpoints/plugin.py:1259-1278
# Option 1: Row-level locking
+rows = db.session.query(...)
+ .with_for_update() # Acquire row locks
+ .all()
+
+# Option 2: PostgreSQL ON CONFLICT
+INSERT INTO endpointevent (...) VALUES (...)
+ON CONFLICT (assetid, eventchannel, eventrecordid) DO NOTHING;
+ | Timeline | +Vulnerability | +Action | +
|---|---|---|
| Week 1 (Immediate) |
+ #1: Agent Binary Injection | +Implement Authenticode signature verification | +
| Week 1 (Immediate) |
+ #2: Account Enumeration | +Fix constant-time responses (always 401) | +
| Week 1 (Immediate) |
+ #3: Timing Attack | +Replace == with hmac.compare_digest() | +
| Weeks 2-4 (Short-term) |
+ #5: Rate Limit Bypass | +Add rate limiting to /refresh endpoint | +
| Weeks 2-4 (Short-term) |
+ #4: Token Non-Rotation | +Implement refresh token rotation | +
| Weeks 2-4 (Short-term) |
+ #6: Upload DoS | +Add rate limiting to upload endpoints | +
| Month 2+ (Long-term) |
+ #7: Race Condition | +Implement SELECT FOR UPDATE or ON CONFLICT | +
Each finding was challenged by a second AI agent attempting to disprove it. Only 19% of initial findings survived validation (7 confirmed, 29 rejected as false positives). This high rejection rate demonstrates the effectiveness of adversarial validation in eliminating false positives.
+
+ Security Audit - September 12, 2026
+ Budget exhausted before adversarial validation could complete
+
These 40 potential vulnerabilities were discovered in Hunt Round 4 but were not validated due to budget exhaustion ($60.42 of $40.00 budget spent). Based on historical validation rates from Rounds 1-3:
+Recommendation: Budget for validation round or manually review based on priority.
++ Note: 3 related tasks found zero findings: + info_disclosure_asset_exists_1, + info_disclosure_script_exists_1, + info_disclosure_user_exists_1 +
+Resume the audit with additional budget to complete adversarial validation:
+python audit.py --resume --budget 80Review findings manually in priority order:
+Focus remediation efforts on the 7 confirmed vulnerabilities only. The unvalidated findings can be addressed in future audit cycles. This is reasonable if:
+Complete hunt results available at:
++ C:\Users\270015376\AppData\Local\Temp\audit-tool\results\shopdb-final\hunt\t_fb_*.jsonl +
+ + +